style(copy): punctuation rules and site-wide fix with a CI check (QA U11)

- glossary: 10 punctuation rules (full-width in Chinese, CJK/Latin spacing,
  full-width parens around Chinese, single ellipsis, 「」 quotes, period rules)
- scripts/check-copy-punctuation.mjs: checks string literals, templates and JSX
  text (not comments, types, keys, match/compare strings); --fix, --strict,
  --by-file; wired into deno task ci as check:copy in warn mode
- --fix applied to web, server and contracts; files other lanes were editing
  (subscriptions, sites, browser, agent, auth/account, inventory) left for them
- tests updated for the changed messages

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 20:09:29 +08:00
1 parent 9b017906cf
commit 52402864d6
143 files changed
+985 -606

No files matched your search

+16 -16
View File
@@ -42,8 +42,8 @@ export const CUTS = [
] as const;
export const CONTENT_TYPES = ["main", "extra", "behind_the_scenes", "performance_only", "preview", "reaction"] as const;
const SOURCE_LABEL: Record<string, string> = { BluRay: "BluRay(压制)", "BluRay-Disc": "原盘" };
const CUT_LABEL: Record<string, string> = { default: "默认(无剪辑标记)" };
const SOURCE_LABEL: Record<string, string> = { BluRay: "BluRay(压制)", "BluRay-Disc": "原盘" };
const CUT_LABEL: Record<string, string> = { default: "默认(无剪辑标记)" };
const CT_LABEL: Record<string, string> = {
main: "正片",
extra: "花絮",
@@ -52,7 +52,7 @@ const CT_LABEL: Record<string, string> = {
preview: "预告",
reaction: "reaction",
};
const UNKNOWN_LABEL = { hold: "等待补充(不下载)", reject: "拒绝", accept: "放行" } as const;
const UNKNOWN_LABEL = { hold: "等待补充(不下载)", reject: "拒绝", accept: "放行" } as const;
const GB = 1024 ** 3;
const o = <T extends string>(list: readonly T[], labels: Record<string, string> = {}) =>
@@ -235,7 +235,7 @@ export function summarizeHard(j: JsonObject | undefined, fallback?: JsonObject):
if (!h) return ["规则无法解析"];
if (h.resolutions?.length) out.push(`分辨率 ${h.resolutions.join(" / ")}`);
if (h.sources?.length) out.push(`来源 ${h.sources.map((s) => SOURCE_LABEL[s] ?? s).join(" / ")}`);
if (h.requireEncode) out.push("仅蓝光压制(不含原盘/Remux)");
if (h.requireEncode) out.push("仅蓝光压制(不含原盘/Remux)");
if (h.minSizeBytes !== undefined || h.maxSizeBytes !== undefined)
out.push(
`体积 ${h.minSizeBytes !== undefined ? formatBytes(h.minSizeBytes, 1) : "0"} – ${
@@ -282,7 +282,7 @@ export function summarizeUpgrade(j: JsonObject | undefined): string[] {
export function summarizeCompletion(j: JsonObject | undefined): string {
const c = Completion.safeParse(j ?? {}).data;
if (!c) return "未设置";
return c.kind === "movie" ? "按内容版本核对(电影)" : "按内容单元覆盖核对(剧集/综艺)";
return c.kind === "movie" ? "按内容版本核对(电影)" : "按内容单元覆盖核对(剧集/综艺)";
}
// ── 编辑器 ──
@@ -312,7 +312,7 @@ export function SlotRulesEditor({
onChange={(v) => set("resolutions", v)}
/>
</FormField>
<FormField label="交付来源" hint="BluRay 指蓝光压制;原盘与 Remux 单列">
<FormField label="交付来源" hint="BluRay 指蓝光压制;原盘与 Remux 单列">
<ChipToggleGroup
label="交付来源"
options={o(SOURCES, SOURCE_LABEL)}
@@ -354,7 +354,7 @@ export function SlotRulesEditor({
</FormField>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<FormField label="必需字幕语言" htmlFor={`${id}-subs`} hint="逗号分隔;zh 可被 zh-Hans/zh-Hant 满足">
<FormField label="必需字幕语言" htmlFor={`${id}-subs`} hint="逗号分隔;zh 可被 zh-Hans/zh-Hant 满足">
<Input
id={`${id}-subs`}
value={value.requiredSubtitles}
@@ -370,7 +370,7 @@ export function SlotRulesEditor({
/>
</FormField>
</div>
<FormField label="内容版本(剪辑)" hint="不选即不限">
<FormField label="内容版本(剪辑)" hint="不选即不限">
<ChipToggleGroup
label="内容版本"
options={o(CUTS, CUT_LABEL)}
@@ -386,7 +386,7 @@ export function SlotRulesEditor({
onChange={(v) => set("contentTypes", v)}
/>
</FormField>
<FormField label="字段未知时" hint="资源缺少判断字段(如字幕)时">
<FormField label="字段未知时" hint="资源缺少判断字段(如字幕)时">
<Select
value={value.unknownPolicy || "__none"}
onValueChange={(v) => set("unknownPolicy", v === "__none" ? "" : (v as SlotRulesDraft["unknownPolicy"]))}
@@ -408,7 +408,7 @@ export function SlotRulesEditor({
<fieldset className="space-y-3 border-t border-border/60 pt-4">
<legend className="mb-1 text-label font-semibold tracking-[0.18em] text-muted-foreground uppercase">
软偏好 · 通过硬条件后排序,越靠前越优先
软偏好 · 通过硬条件后排序,越靠前越优先
</legend>
<FormField label="HDR">
<ChipToggleGroup
@@ -437,14 +437,14 @@ export function SlotRulesEditor({
onChange={(v) => set("softCodecs", v)}
/>
</FormField>
<FormField label="偏好发布组" htmlFor={`${id}-grp`} hint="逗号分隔,越靠前越好">
<FormField label="偏好发布组" htmlFor={`${id}-grp`} hint="逗号分隔,越靠前越好">
<Input id={`${id}-grp`} value={value.softGroups} onChange={(e) => set("softGroups", e.target.value)} />
</FormField>
</fieldset>
<fieldset className="space-y-3 border-t border-border/60 pt-4">
<legend className="mb-1 text-label font-semibold tracking-[0.18em] text-muted-foreground uppercase">
升级 · 生成替换计划,不直接覆盖旧文件
升级 · 生成替换计划,不直接覆盖旧文件
</legend>
<div className="flex items-center justify-between gap-3 rounded-lg border border-border px-3 py-2">
<p className="text-caption font-medium text-foreground">允许自动升级</p>
@@ -459,7 +459,7 @@ export function SlotRulesEditor({
label="最小来源等级提升"
htmlFor={`${id}-tier`}
error={errors.minTierSteps}
hint="默认 1;同等级不反复更换"
hint="默认 1;同等级不反复更换"
>
<Input
id={`${id}-tier`}
@@ -481,7 +481,7 @@ export function SlotRulesEditor({
/>
</FormField>
</div>
<FormField label="合格压制组名单" htmlFor={`${id}-allow`} hint="设置后,名单外的压制不触发自动升级">
<FormField label="合格压制组名单" htmlFor={`${id}-allow`} hint="设置后,名单外的压制不触发自动升级">
<Input
id={`${id}-allow`}
value={value.encodeGroupAllowlist}
@@ -511,8 +511,8 @@ export function SlotRulesEditor({
</SelectTrigger>
<SelectContent position="popper">
<SelectItem value="__none">未设置</SelectItem>
<SelectItem value="movie">按内容版本核对(电影)</SelectItem>
<SelectItem value="units">按内容单元覆盖核对(剧集/综艺)</SelectItem>
<SelectItem value="movie">按内容版本核对(电影)</SelectItem>
<SelectItem value="units">按内容单元覆盖核对(剧集/综艺)</SelectItem>
</SelectContent>
</Select>
{errors.form ? <p className="text-meta text-destructive">{errors.form}</p> : null}