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:
1 parent
9b017906cf
commit
52402864d6
143 files changed
+985
-606
No files matched your search
@@ -184,14 +184,14 @@ export function MkdirDialog({
|
||||
// ─── 重命名 ─────────────────────────────────────────────────────
|
||||
|
||||
const SUGGEST_REASON: Record<string, string> = {
|
||||
identity_uncertain: "识别不可信,没有推荐名",
|
||||
identity_uncertain: "识别不可信,没有推荐名",
|
||||
};
|
||||
const TEMPLATE_SOURCE: Record<string, string> = { entity: "作品覆盖", root: "库根", global: "全局", override: "覆写" };
|
||||
|
||||
function suggestReasonText(r: string | null): string {
|
||||
if (!r) return "没有推荐名";
|
||||
if (SUGGEST_REASON[r]) return SUGGEST_REASON[r] as string;
|
||||
if (r.startsWith("naming_failed")) return `命名模板渲染失败:${r.replace(/^naming_failed:\s*/, "")}`;
|
||||
if (r.startsWith("naming_failed")) return `命名模板渲染失败:${r.replace(/^naming_failed:\s*/, "")}`;
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -291,13 +291,13 @@ export function RenameDialog({
|
||||
ev.preventDefault();
|
||||
if (problem) return setErr(problem);
|
||||
if (!changed) return onClose();
|
||||
if (taken) return setErr("同名已存在,不会覆盖");
|
||||
if (taken) return setErr("同名已存在,不会覆盖");
|
||||
exec.mutate(
|
||||
{ idempotencyKey: key, root: root.id, path: entry.path, newName: name.trim(), withSidecars, dryRun: false },
|
||||
{
|
||||
onSuccess: (r) => {
|
||||
toast.success(
|
||||
`已重命名${r.renames.length > 1 ? `(含 ${r.renames.length - 1} 个附属文件)` : ""}`,
|
||||
`已重命名${r.renames.length > 1 ? `(含 ${r.renames.length - 1} 个附属文件)` : ""}`,
|
||||
r.recordId
|
||||
? {
|
||||
action: {
|
||||
@@ -313,9 +313,9 @@ export function RenameDialog({
|
||||
const reason = filesErrorReason(e);
|
||||
setErr(
|
||||
reason === "exists"
|
||||
? "同名已存在,不会覆盖"
|
||||
? "同名已存在,不会覆盖"
|
||||
: reason === "download_active"
|
||||
? "做种中,改名会让下载器找不到文件"
|
||||
? "做种中,改名会让下载器找不到文件"
|
||||
: filesErrorText(e),
|
||||
);
|
||||
},
|
||||
@@ -338,7 +338,7 @@ export function RenameDialog({
|
||||
<FormField
|
||||
label="新名称"
|
||||
htmlFor="rename-name"
|
||||
error={err ?? (problem && changed ? problem : taken ? "同名已存在,不会覆盖" : null)}
|
||||
error={err ?? (problem && changed ? problem : taken ? "同名已存在,不会覆盖" : null)}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
@@ -378,9 +378,9 @@ export function RenameDialog({
|
||||
? "当前名称与命名模板一致"
|
||||
: usingSuggestion
|
||||
? "已填入推荐名"
|
||||
: `推荐名:${suggested.name}`}
|
||||
: `推荐名:${suggested.name}`}
|
||||
{suggested.templateSource
|
||||
? `· 模板来源:${TEMPLATE_SOURCE[suggested.templateSource] ?? suggested.templateSource}`
|
||||
? `· 模板来源:${TEMPLATE_SOURCE[suggested.templateSource] ?? suggested.templateSource}`
|
||||
: ""}
|
||||
{usingSuggestion && name.trim() !== entry.name ? (
|
||||
<button
|
||||
@@ -434,10 +434,10 @@ export function RenameDialog({
|
||||
))}
|
||||
</ul>
|
||||
{pv.assetLocations > 0 ? (
|
||||
<Notice tone="neutral">同步 {pv.assetLocations} 条资产位置,写一条整理记录。</Notice>
|
||||
<Notice tone="neutral">同步 {pv.assetLocations} 条资产位置,写一条整理记录。</Notice>
|
||||
) : null}
|
||||
{pv.bindings.length > 0 ? (
|
||||
<Notice tone="warning">这是作品或季的绑定目录。改名后旧绑定标为已替代,之后入库用新目录。</Notice>
|
||||
<Notice tone="warning">这是作品或季的绑定目录。改名后旧绑定标为已替代,之后入库用新目录。</Notice>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
@@ -532,7 +532,7 @@ export function TransferDialog({
|
||||
onError: (e) => {
|
||||
const reason = filesErrorReason(e);
|
||||
if (reason === "plan_stale") {
|
||||
setErr("源文件在预览后有变化,已重新预览");
|
||||
setErr("源文件在预览后有变化,已重新预览");
|
||||
runPreview(target, mode);
|
||||
} else setErr(filesErrorText(e));
|
||||
},
|
||||
@@ -613,13 +613,15 @@ export function TransferDialog({
|
||||
{plan.freeBytes != null ? ` · 剩余 ${formatBytes(plan.freeBytes, 1)}` : ""}
|
||||
</StatusBadge>
|
||||
{conflicts.length ? (
|
||||
<StatusBadge tone="warning">冲突 {conflicts.length}(跳过,不覆盖)</StatusBadge>
|
||||
<StatusBadge tone="warning">冲突 {conflicts.length}(跳过,不覆盖)</StatusBadge>
|
||||
) : null}
|
||||
{plan.pairs.some((p) => p.registered) ? (
|
||||
<StatusBadge>含已登记资产,执行后同步位置</StatusBadge>
|
||||
) : null}
|
||||
{plan.pairs.some((p) => p.registered) ? <StatusBadge>含已登记资产,执行后同步位置</StatusBadge> : null}
|
||||
</div>
|
||||
{hnrBlocked ? (
|
||||
<Notice tone="destructive">
|
||||
{plan.hnrBlocks.length} 项做种中且 H&R 未满足,不能移动。改用复制,或等 H&R 满足后再移动。
|
||||
{plan.hnrBlocks.length} 项做种中且 H&R 未满足,不能移动。改用复制,或等 H&R 满足后再移动。
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 underline"
|
||||
@@ -633,7 +635,9 @@ export function TransferDialog({
|
||||
</button>
|
||||
</Notice>
|
||||
) : null}
|
||||
{plan.truncated ? <Notice tone="warning">源文件过多,预览只列出前一部分,执行时处理全部。</Notice> : null}
|
||||
{plan.truncated ? (
|
||||
<Notice tone="warning">源文件过多,预览只列出前一部分,执行时处理全部。</Notice>
|
||||
) : null}
|
||||
<ul className="space-y-1.5">
|
||||
{plan.pairs.map((p) => {
|
||||
const sr = rootById.get(p.source.root);
|
||||
@@ -761,7 +765,7 @@ export function DeleteDialog({
|
||||
reason === "permanent_disabled"
|
||||
? "设置里未开启永久删除(files.allowPermanentDelete)"
|
||||
: reason === "approval_required"
|
||||
? "用 API Key 删除,每次都要审批"
|
||||
? "用 API Key 删除,每次都要审批"
|
||||
: filesErrorText(e),
|
||||
);
|
||||
},
|
||||
@@ -772,7 +776,7 @@ export function DeleteDialog({
|
||||
<Dialog open onOpenChange={(o) => !o && onClose()}>
|
||||
<DialogContent className="max-h-[92dvh] grid-rows-[auto_minmax(0,1fr)_auto] sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{single ? `删除 ${single}?` : `删除 ${items.length} 项?`}</DialogTitle>
|
||||
<DialogTitle>{single ? `删除 ${single}?` : `删除 ${items.length} 项?`}</DialogTitle>
|
||||
<DialogDescription>已检查做种任务、已登记资产和路径绑定。下载器里的任务不受影响。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="min-h-0 space-y-3 overflow-y-auto">
|
||||
@@ -820,15 +824,15 @@ export function DeleteDialog({
|
||||
</p>
|
||||
))}
|
||||
{c.assets.length ? (
|
||||
<p>已登记资产 {c.assets.length} 个(删除后退役,已满足的版本目标退回待处理)</p>
|
||||
<p>已登记资产 {c.assets.length} 个(删除后退役,已满足的版本目标退回待处理)</p>
|
||||
) : null}
|
||||
{c.bindings.map((bd) => (
|
||||
<p key={bd.bindingId}>
|
||||
路径绑定:{bd.title}
|
||||
路径绑定:{bd.title}
|
||||
{bd.seasonNumber != null ? ` 第 ${bd.seasonNumber} 季` : ""}
|
||||
</p>
|
||||
))}
|
||||
{c.sharedInode ? <p>共享 inode(硬链接):只删这一处链接,其他位置不受影响</p> : null}
|
||||
{c.sharedInode ? <p>共享 inode(硬链接):只删这一处链接,其他位置不受影响</p> : null}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
@@ -841,7 +845,7 @@ export function DeleteDialog({
|
||||
<span>
|
||||
同时退役资产
|
||||
<span className="block text-meta text-muted-foreground">
|
||||
资产位置标为已移除,资产退役;已满足的版本目标退回待处理。
|
||||
资产位置标为已移除,资产退役;已满足的版本目标退回待处理。
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -851,7 +855,9 @@ export function DeleteDialog({
|
||||
<input type="checkbox" className="mt-1" checked={unbind} onChange={(e) => setUnbind(e.target.checked)} />
|
||||
<span>
|
||||
解除路径绑定
|
||||
<span className="block text-meta text-muted-foreground">绑定标为已替代,该作品下次入库重新选目录。</span>
|
||||
<span className="block text-meta text-muted-foreground">
|
||||
绑定标为已替代,该作品下次入库重新选目录。
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
) : null}
|
||||
@@ -931,8 +937,8 @@ export function CopyPathDialog({ root, entry, onClose }: { root: FileRoot; entry
|
||||
const local = `${root.hostPath}${entry.path ? `/${entry.path}` : ""}`;
|
||||
const rows = [
|
||||
{ label: "本机路径", value: local },
|
||||
{ label: "真实路径(解析链接后)", value: stat.data?.realPath ?? null },
|
||||
{ label: "登记目录内相对路径", value: entry.path || "(登记目录本身)" },
|
||||
{ label: "真实路径(解析链接后)", value: stat.data?.realPath ?? null },
|
||||
{ label: "登记目录内相对路径", value: entry.path || "(登记目录本身)" },
|
||||
];
|
||||
const copy = async (v: string) => {
|
||||
try {
|
||||
@@ -969,7 +975,7 @@ export function CopyPathDialog({ root, entry, onClose }: { root: FileRoot; entry
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-meta text-muted-foreground">下载器与媒体服务器看到的路径按“设置 → 路径映射”换算。</p>
|
||||
<p className="text-meta text-muted-foreground">下载器与媒体服务器看到的路径按「设置 → 路径映射」换算。</p>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -994,7 +1000,7 @@ export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[];
|
||||
onSuccess: (r) => {
|
||||
setResult(r);
|
||||
(r.skipped.length ? toast.warning : toast.success)(
|
||||
`已提交 ${r.submitted.length} 个作品,锁定字段不变${r.skipped.length ? `;${r.skipped.length} 个未提交` : ""}`,
|
||||
`已提交 ${r.submitted.length} 个作品,锁定字段不变${r.skipped.length ? `;${r.skipped.length} 个未提交` : ""}`,
|
||||
);
|
||||
},
|
||||
onError: (e) => toast.error(filesErrorText(e, "提交失败")),
|
||||
@@ -1005,7 +1011,7 @@ export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[];
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>刷新元数据{single ? ` · ${single.title}` : ` · ${entities.length} 个作品`}</DialogTitle>
|
||||
<DialogDescription>按字段刷新,锁定的字段和路径不变。即 MP 的“刮削”。</DialogDescription>
|
||||
<DialogDescription>按字段刷新,锁定的字段和路径不变。即 MP 的「刮削」。</DialogDescription>
|
||||
</DialogHeader>
|
||||
{!single && !result ? (
|
||||
<ul className="max-h-40 space-y-0.5 overflow-auto text-caption">
|
||||
@@ -1028,7 +1034,7 @@ export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[];
|
||||
</Link>
|
||||
</p>
|
||||
<p className="text-meta text-muted-foreground">
|
||||
{s.lockedFields.length ? `锁定字段:${s.lockedFields.join("、")}` : "没有锁定字段"}
|
||||
{s.lockedFields.length ? `锁定字段:${s.lockedFields.join("、")}` : "没有锁定字段"}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -132,7 +132,7 @@ export function FileDetail({
|
||||
label: "链接数",
|
||||
value:
|
||||
stat.data?.nlink != null
|
||||
? `${stat.data.nlink}${stat.data.sharedInode ? "(共享 inode,不能原地改写)" : ""}`
|
||||
? `${stat.data.nlink}${stat.data.sharedInode ? "(共享 inode,不能原地改写)" : ""}`
|
||||
: null,
|
||||
},
|
||||
{ label: "本机路径", value: `${root.hostPath}${e.path ? `/${e.path}` : ""}`, mono: true, wide: true },
|
||||
@@ -223,7 +223,7 @@ export function FileDetail({
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="mt-1 text-meta text-muted-foreground">改名或移动此目录会同步更新绑定;刷新元数据不改路径。</p>
|
||||
<p className="mt-1 text-meta text-muted-foreground">改名或移动此目录会同步更新绑定;刷新元数据不改路径。</p>
|
||||
</DetailSection>
|
||||
) : null}
|
||||
|
||||
@@ -253,7 +253,7 @@ export function FileDetail({
|
||||
{
|
||||
onSuccess: (r) => {
|
||||
const it = r.items[0];
|
||||
if (it?.status !== "ok") toast.warning(`探测不到媒体信息:${it?.reason ?? "未知原因"}`);
|
||||
if (it?.status !== "ok") toast.warning(`探测不到媒体信息:${it?.reason ?? "未知原因"}`);
|
||||
},
|
||||
onError: (err) => toast.error(filesErrorText(err, "探测失败")),
|
||||
},
|
||||
@@ -286,7 +286,7 @@ export function FileDetail({
|
||||
/>
|
||||
) : (
|
||||
<p className="text-caption text-muted-foreground">
|
||||
未登记文件不自动探测。点“探测”读取 ffprobe 摘要,结果缓存 24 小时。
|
||||
未登记文件不自动探测。点「探测」读取 ffprobe 摘要,结果缓存 24 小时。
|
||||
</p>
|
||||
)}
|
||||
</DetailSection>
|
||||
|
||||
@@ -94,7 +94,7 @@ function Subline({ e }: { e: FileEntry }) {
|
||||
"block truncate text-meta",
|
||||
s.tone === "hint" ? "text-muted-foreground/70" : "text-muted-foreground",
|
||||
)}
|
||||
title={s.tone === "hint" ? `按文件名解析:${s.text}` : s.text}
|
||||
title={s.tone === "hint" ? `按文件名解析:${s.text}` : s.text}
|
||||
>
|
||||
{s.text}
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "~/components/ui/dialog.tsx";
|
||||
|
||||
const KEYS: [string, string][] = [
|
||||
["↑ ↓ / Home End", "移动焦点(Shift 扩选)"],
|
||||
["↑ ↓ / Home End", "移动焦点(Shift 扩选)"],
|
||||
["Enter", "打开目录 / 看详情"],
|
||||
["Backspace / Alt+↑", "上一级"],
|
||||
["Space", "勾选当前行"],
|
||||
@@ -9,11 +9,11 @@ const KEYS: [string, string][] = [
|
||||
["Ctrl/⌘+A", "全选"],
|
||||
["Ctrl/⌘+Enter", "整理选中项"],
|
||||
["F2", "重命名"],
|
||||
["Del", "删除(先进回收站)"],
|
||||
["Del", "删除(先进回收站)"],
|
||||
["/", "搜索当前目录"],
|
||||
["Esc", "清除选择"],
|
||||
["Shift+F10 / 菜单键", "打开操作菜单"],
|
||||
["拖到目录", "移动到(按住 Alt 复制),先出预览"],
|
||||
["拖到目录", "移动到(按住 Alt 复制),先出预览"],
|
||||
["?", "显示这张表"],
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user