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
@@ -19,8 +19,8 @@ import { errorMessage } from "~/lib/api.ts";
|
||||
import { formatBytes } from "~/lib/utils.ts";
|
||||
|
||||
const STEPS = [
|
||||
"生成下载计划,预留版本目标和预算;每个版本目标同时只有一个在途计划",
|
||||
"已有相同资源或相同 hash 的任务时直接接管,不重复提交",
|
||||
"生成下载计划,预留版本目标和预算;每个版本目标同时只有一个在途计划",
|
||||
"已有相同资源或相同 hash 的任务时直接接管,不重复提交",
|
||||
"缓存卷超过硬阈值或读不到磁盘状态时无法提交",
|
||||
"提交前复核免费、做种等易变字段",
|
||||
];
|
||||
@@ -56,7 +56,7 @@ export function DownloadDialog({
|
||||
if (r.submitted && r.plan) {
|
||||
setRejected(null);
|
||||
setDone(r.plan.id);
|
||||
toast.success(`已提交${slotName(r.slotId) ? `(${slotName(r.slotId)})` : ""},进度见“下载队列”`);
|
||||
toast.success(`已提交${slotName(r.slotId) ? `(${slotName(r.slotId)})` : ""},进度见「下载队列」`);
|
||||
} else {
|
||||
setRejected(r.rejected);
|
||||
toast.warning("没有生成下载计划");
|
||||
@@ -102,12 +102,14 @@ export function DownloadDialog({
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-meta text-muted-foreground">
|
||||
{release.entity ? "这个作品没有版本目标。先在订阅里新建。" : "资源未关联本地作品。先在“身份与元数据”指定。"}
|
||||
{release.entity
|
||||
? "这个作品没有版本目标。先在订阅里新建。"
|
||||
: "资源未关联本地作品。先在「身份与元数据」指定。"}
|
||||
</p>
|
||||
)}
|
||||
{rejected ? (
|
||||
<div className="rounded-xl border border-warning/40 bg-warning/10 px-3 py-2.5 text-caption text-warning">
|
||||
<p className="mb-1">没有生成下载计划:</p>
|
||||
<p className="mb-1">没有生成下载计划:</p>
|
||||
<ul className="list-disc space-y-0.5 pl-5">
|
||||
{rejected.map((r, i) => (
|
||||
<li key={`${r.slotId}-${r.code}-${i}`} className="break-words">
|
||||
@@ -132,11 +134,11 @@ export function DownloadDialog({
|
||||
onClick={() => {
|
||||
onOpenChange(false);
|
||||
openAgentDrawer({
|
||||
label: `资源:${release.title}`,
|
||||
label: `资源:${release.title}`,
|
||||
releaseId: release.id,
|
||||
entityId: release.entity?.id,
|
||||
siteId: release.siteId,
|
||||
goal: "判断这个资源是否值得下载;合适就按正常流程提交",
|
||||
goal: "判断这个资源是否值得下载;合适就按正常流程提交",
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -130,7 +130,7 @@ export function FeedFormDialog({
|
||||
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{feed ? "编辑 Feed" : "新建 Feed"}</DialogTitle>
|
||||
<DialogDescription>URL 加密保存,之后只显示脱敏地址。</DialogDescription>
|
||||
<DialogDescription>URL 加密保存,之后只显示脱敏地址。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form
|
||||
className="grid gap-1"
|
||||
@@ -152,7 +152,7 @@ export function FeedFormDialog({
|
||||
htmlFor="feed-url"
|
||||
required={!feed}
|
||||
error={errors.url}
|
||||
hint={feed ? `当前:${feed.urlDisplay};留空表示不变` : "RSS / Atom 地址"}
|
||||
hint={feed ? `当前:${feed.urlDisplay};留空表示不变` : "RSS / Atom 地址"}
|
||||
>
|
||||
<Input
|
||||
id="feed-url"
|
||||
@@ -179,7 +179,7 @@ export function FeedFormDialog({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormField>
|
||||
<FormField label="轮询间隔(分钟)" htmlFor="feed-poll" error={errors.pollMin}>
|
||||
<FormField label="轮询间隔(分钟)" htmlFor="feed-poll" error={errors.pollMin}>
|
||||
<Input
|
||||
id="feed-poll"
|
||||
type="number"
|
||||
@@ -233,7 +233,7 @@ export function FeedFormDialog({
|
||||
</FormField>
|
||||
</div>
|
||||
<FormField
|
||||
label="过滤条件(JSON,可选)"
|
||||
label="过滤条件(JSON,可选)"
|
||||
htmlFor="feed-filter"
|
||||
error={errors.filter}
|
||||
hint='如 {"include":["2160p"],"exclude":["CAM"]}'
|
||||
@@ -248,7 +248,7 @@ export function FeedFormDialog({
|
||||
/>
|
||||
</FormField>
|
||||
<div className="flex items-center justify-between gap-3 py-1 text-caption text-foreground">
|
||||
<label htmlFor="feed-enabled">启用(关闭后停止轮询)</label>
|
||||
<label htmlFor="feed-enabled">启用(关闭后停止轮询)</label>
|
||||
<Switch id="feed-enabled" checked={d.enabled} onCheckedChange={(v) => set("enabled", v)} />
|
||||
</div>
|
||||
<DialogFooter className="mt-2 gap-2">
|
||||
|
||||
@@ -59,7 +59,7 @@ function FeedActions({ f }: { f: FeedSource }) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
title="立即抓取(受站点限流)"
|
||||
title="立即抓取(受站点限流)"
|
||||
aria-label="立即抓取"
|
||||
disabled={!f.enabled || fetchNow.isPending}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
@@ -207,7 +207,7 @@ export function FeedTable({
|
||||
empty={{
|
||||
icon: Rss,
|
||||
title: filtered ? "没有符合筛选的 Feed" : "还没有 Feed",
|
||||
description: filtered ? undefined : "新建 Feed 后按轮询间隔抓取,新资源进入识别和收录评估。",
|
||||
description: filtered ? undefined : "新建 Feed 后按轮询间隔抓取,新资源进入识别和收录评估。",
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ import { categoryLabel, hrOf, ORIGIN_LABEL, relTime } from "./release-fields.ts"
|
||||
import { InventoryOverlay, ReleaseBadges } from "./release-list.tsx";
|
||||
|
||||
const DECISION_LABEL = {
|
||||
accept: { label: "符合,可收录", tone: "success" },
|
||||
accept: { label: "符合,可收录", tone: "success" },
|
||||
upgrade: { label: "可升级", tone: "success" },
|
||||
reject: { label: "不收录", tone: "destructive" },
|
||||
unknown_field: { label: "缺字段,待复核", tone: "warning" },
|
||||
unknown_field: { label: "缺字段,待复核", tone: "warning" },
|
||||
skipped: { label: "被跳过", tone: "neutral" },
|
||||
deferred: { label: "延后:已有计划在途", tone: "warning" },
|
||||
deferred: { label: "延后:已有计划在途", tone: "warning" },
|
||||
} as const;
|
||||
|
||||
/** 本地库存 + 候选理由(来自作品详情:Slot / CandidateEvaluation),不从标题猜 */
|
||||
@@ -62,7 +62,7 @@ function LocalInventory({ release, entry }: { release: WorkbenchRelease; entry:
|
||||
{detail.isPending ? (
|
||||
<LoadingState rows={2} />
|
||||
) : detail.error ? (
|
||||
<p className="text-meta text-destructive">作品详情读取失败:{errorMessage(detail.error)}</p>
|
||||
<p className="text-meta text-destructive">作品详情读取失败:{errorMessage(detail.error)}</p>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-meta text-muted-foreground">
|
||||
@@ -140,7 +140,7 @@ export function ReleaseDetailSheet({
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
openAgentDrawer({
|
||||
label: `资源:${r.title}`,
|
||||
label: `资源:${r.title}`,
|
||||
releaseId: r.id,
|
||||
entityId: r.entity?.id,
|
||||
siteId,
|
||||
@@ -177,7 +177,7 @@ export function ReleaseDetailSheet({
|
||||
{
|
||||
onSuccess: (d) =>
|
||||
d.refreshError
|
||||
? toast.warning(`复核失败,显示上次数据:${d.refreshError}`)
|
||||
? toast.warning(`复核失败,显示上次数据:${d.refreshError}`)
|
||||
: toast.success("已复核"),
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
},
|
||||
@@ -197,7 +197,7 @@ export function ReleaseDetailSheet({
|
||||
<>
|
||||
{detail.data.refreshError ? (
|
||||
<p className="mb-2 rounded-lg border border-warning/40 bg-warning/10 px-3 py-2 text-caption text-warning">
|
||||
最近一次复核失败,以下是过期数据:{detail.data.refreshError}
|
||||
最近一次复核失败,以下是过期数据:{detail.data.refreshError}
|
||||
</p>
|
||||
) : null}
|
||||
<KvList
|
||||
@@ -206,7 +206,7 @@ export function ReleaseDetailSheet({
|
||||
{ label: "促销截止", value: formatDateTime(detail.data.volatile.promotionExpiresAt) },
|
||||
{
|
||||
label: "做种 / 下载",
|
||||
value: `${r.seeders ?? "?"} / ${r.leechers ?? "?"}(${relTime(detail.data.volatile.peersObservedAt)}观察)`,
|
||||
value: `${r.seeders ?? "?"} / ${r.leechers ?? "?"}(${relTime(detail.data.volatile.peersObservedAt)}观察)`,
|
||||
},
|
||||
{ label: "详情抓取于", value: formatDateTime(detail.data.volatile.detailFetchedAt) },
|
||||
]}
|
||||
@@ -225,7 +225,7 @@ export function ReleaseDetailSheet({
|
||||
items={[
|
||||
{ label: "分类", value: categoryLabel(r) },
|
||||
{ label: "大小", value: r.sizeBytes === null ? "未知" : formatBytes(r.sizeBytes, 2) },
|
||||
{ label: "H&R", value: { yes: "有", no: "无", unknown: "未知(按有义务处理)" }[hrOf(r)] },
|
||||
{ label: "H&R", value: { yes: "有", no: "无", unknown: "未知(按有义务处理)" }[hrOf(r)] },
|
||||
{ label: "站点上传", value: formatDateTime(r.sourcePublishedAt) },
|
||||
{ label: "首次发现", value: formatDateTime(r.firstSeenAt) },
|
||||
{ label: "最近确认", value: `${formatDateTime(r.dataUpdatedAt)}(${ORIGIN_LABEL[r.origin]})` },
|
||||
|
||||
@@ -91,6 +91,6 @@ export function accountSummary(accounts: SiteAccount[]): { label: string; tone:
|
||||
|
||||
export function circuitLabel(site: Pick<Site, "circuitState" | "circuitOpenUntil">): string | null {
|
||||
if (site.circuitState === "closed") return null;
|
||||
if (site.circuitState === "half_open") return "熔断半开(试探中)";
|
||||
if (site.circuitState === "half_open") return "熔断半开(试探中)";
|
||||
return `熔断中${site.circuitOpenUntil ? `,${relTime(site.circuitOpenUntil)}恢复` : ""}`;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export function ReleaseBadges({ r }: { r: WorkbenchRelease }) {
|
||||
{promo ? (
|
||||
<StatusBadge tone={promo.expired ? "neutral" : promo.free ? "success" : "neutral"}>
|
||||
{promo.label}
|
||||
{promo.expired ? "(已过期)" : r.promotionExpiresAt ? ` · ${relTime(r.promotionExpiresAt)}截止` : ""}
|
||||
{promo.expired ? "(已过期)" : r.promotionExpiresAt ? ` · ${relTime(r.promotionExpiresAt)}截止` : ""}
|
||||
</StatusBadge>
|
||||
) : null}
|
||||
{hr === "yes" ? <StatusBadge tone="warning">H&R</StatusBadge> : null}
|
||||
@@ -114,7 +114,7 @@ export function ReleaseRow({
|
||||
{r.leechers ?? "?"}
|
||||
</span>
|
||||
<span title="站点上传时间">上传 {relTime(r.sourcePublishedAt)}</span>
|
||||
<span className="inline-flex items-center gap-1" title={`数据最近确认:${r.dataUpdatedAt}`}>
|
||||
<span className="inline-flex items-center gap-1" title={`数据最近确认:${r.dataUpdatedAt}`}>
|
||||
<Clock className="h-3 w-3" />
|
||||
{ORIGIN_LABEL[r.origin]} · {relTime(r.dataUpdatedAt)}
|
||||
</span>
|
||||
|
||||
@@ -64,7 +64,7 @@ export function SnapshotPanel({ site }: { site: Site }) {
|
||||
snap.mutate(
|
||||
{ id: site.id, maxPages: 2 },
|
||||
{
|
||||
onSuccess: () => toast.success(`${site.name}:快照已排队`),
|
||||
onSuccess: () => toast.success(`${site.name}:快照已排队`),
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
},
|
||||
)
|
||||
@@ -90,7 +90,7 @@ export function SnapshotPanel({ site }: { site: Site }) {
|
||||
{s.query ? <span className="min-w-0 truncate text-muted-foreground">“{s.query}”</span> : null}
|
||||
<span className="text-muted-foreground tabular-nums">
|
||||
{s.pages.length ? `第 ${s.pages.join(",")} 页 · ` : ""}
|
||||
{s.itemCount} 条(新 {s.newCount})
|
||||
{s.itemCount} 条(新 {s.newCount})
|
||||
</span>
|
||||
<span className="ml-auto text-meta text-muted-foreground" title={formatDateTime(s.startedAt)}>
|
||||
{relTime(s.startedAt)}
|
||||
|
||||
Reference in new issue
Block a user