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

@@ -70,7 +70,7 @@ function EntityNamingDialog({ entityId, kind, onClose }: { entityId: string; kin
} catch (err) {
const split = splitNamingErrors(errorMessage(err));
if (split) setErrors(split);
toast.error(split ? "模板有错误,未保存" : errorMessage(err, "保存失败"));
toast.error(split ? "模板有错误,未保存" : errorMessage(err, "保存失败"));
}
};
@@ -89,7 +89,7 @@ function EntityNamingDialog({ entityId, kind, onClose }: { entityId: string; kin
<div className="min-w-0 space-y-3">
<div className="flex flex-wrap items-center gap-2">
<StatusBadge tone={saved[kind] ? "success" : "neutral"}>
当前:{saved[kind] ? "作品覆盖" : `沿用${inheritedSource}`}
当前:{saved[kind] ? "作品覆盖" : `沿用${inheritedSource}`}
</StatusBadge>
</div>
<NamingTemplateEditor
+1 -1
View File
@@ -11,7 +11,7 @@ export function splitNamingErrors(message: string): Partial<Record<NamingKind, s
for (const m of message.matchAll(/(movie|tv|variety):\s*([^;]+)/g)) {
const [, k, msg] = m;
const pos = /^(\d+):(\d+)\s*(.*)$/.exec((msg ?? "").trim());
out[k as NamingKind] = pos ? `第 ${pos[1]} 行第 ${pos[2]} 列:${pos[3]}` : (msg ?? "").trim();
out[k as NamingKind] = pos ? `第 ${pos[1]} 行第 ${pos[2]} 列:${pos[3]}` : (msg ?? "").trim();
}
return Object.keys(out).length ? out : null;
}
@@ -31,7 +31,7 @@ const ROLE: Record<NamingPreviewResult["segments"][number]["role"], { label: str
file: { label: "文件名", className: "text-foreground font-medium" },
};
export const NAMING_NOTE = "已建立目录的作品不受模板影响,模板只决定新目录和新文件名。";
export const NAMING_NOTE = "已建立目录的作品不受模板影响,模板只决定新目录和新文件名。";
function useDebounced<T>(v: T, ms = 300): T {
const [d, setD] = useState(v);
@@ -166,8 +166,8 @@ export function NamingTemplateEditor({
<div className="flex flex-wrap items-center justify-between gap-2 text-meta text-muted-foreground">
<span>
{override && !value.trim()
? `留空:沿用${inherited?.[active]?.source ?? "上级"}模板`
: "/ 分目录,最后一段是文件名,须以 {{fileExt}} 结尾"}
? `留空:沿用${inherited?.[active]?.source ?? "上级"}模板`
: "/ 分目录,最后一段是文件名,须以 {{fileExt}} 结尾"}
</span>
<span className="tabular-nums">{value.length} / 1024</span>
</div>
@@ -268,9 +268,9 @@ function NamingPreview({
/>
) : null}
{!input ? (
<p className="text-caption text-muted-foreground">模板为空,没有可预览的内容。</p>
<p className="text-caption text-muted-foreground">模板为空,没有可预览的内容。</p>
) : q.isError && !r ? (
<p className="text-caption text-destructive">预览失败:{(q.error as Error).message}</p>
<p className="text-caption text-destructive">预览失败:{(q.error as Error).message}</p>
) : !r ? (
<p className="text-caption text-muted-foreground">预览中…</p>
) : (
@@ -293,7 +293,7 @@ function PreviewBody({
return (
<>
<p className="text-meta break-all text-muted-foreground">
样本:<span className="font-mono">{r.sample.fileName}</span>
样本:<span className="font-mono">{r.sample.fileName}</span>
{r.sample.entityTitle ? ` · 作品 ${r.sample.entityTitle}` : ""}
</p>
{r.errors.length ? (
@@ -317,10 +317,10 @@ function PreviewBody({
))}
</div>
)}
{r.usesBinding ? <Note>这部作品已有目录,目录沿用已建立的路径,模板只决定文件名。</Note> : null}
{r.usesBinding ? <Note>这部作品已有目录,目录沿用已建立的路径,模板只决定文件名。</Note> : null}
{r.missing.length ? (
<p className="text-meta text-muted-foreground">
无值的变量(输出为空):<span className="font-mono">{r.missing.join("、")}</span>
无值的变量(输出为空):<span className="font-mono">{r.missing.join("、")}</span>
</p>
) : null}
{r.warnings.map((w) => (
@@ -422,7 +422,7 @@ function VariablePalette({
{loading ? (
<p className="text-meta text-muted-foreground">加载中…</p>
) : error ? (
<p className="text-meta text-destructive">变量表加载失败:{(error as Error).message}</p>
<p className="text-meta text-destructive">变量表加载失败:{(error as Error).message}</p>
) : (
<div className="max-h-96 space-y-3 overflow-y-auto pr-1">
{GROUP_ORDER.map((g) => {
@@ -478,7 +478,7 @@ function VariablePalette({
{filtersOpen ? (
<ul className="mt-2 space-y-1.5">
<li className="text-meta text-muted-foreground">
<span className="font-mono text-foreground">{"{% if 变量 %}…{% endif %}"}</span> 条件;
<span className="font-mono text-foreground">{"{% if 变量 %}…{% endif %}"}</span> 条件;
<span className="font-mono text-foreground">{"{{ a or b }}"}</span> 取第一个有值的
</li>
{data.filters.map((f) => (