fix(i18n): Translate UI text built from template literals

Template literals such as `Remove data source '${name}'?` reached props and
calls that look text up exactly (dialog messages, labels, aria-labels, menu
items, action titles), so they always showed in English. Some notification
titles also spliced lowercase English words into a sentence.

- `tx` also works as a tag: tx`Remove data source '${name}'?` looks up
  "Remove data source '{name}'?" by its literal parts, fills in the values
  (translated when the catalog has them) and remembers the result, so the
  primitives that pass strings through `tx` translate it again after a locale
  change or once the catalog has loaded (actions registered at startup).
- 269 template literals at exact-lookup sites now use the tag; text that
  spliced words or plural suffixes is written as one sentence per variant
  (safe delete, Docker delete/prune, rebase, conflicts, VCS "No … in
  progress", database copy errors, inspection descriptions, ...).
- zh: 280 new entries; stale and duplicate-shape entries removed.
- textCatalog guard: untagged template literals at exact-lookup sites fail
  (with an allowlist for code-like text), tagged text is checked against the
  catalog by shape, case-changing values and `c ? "a" : "b"` suffixes inside
  translated templates fail, and a catalog may hold one entry per template shape.

Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
l0ng-ai committed 2026-10-27 18:43:53 +08:00
1 джерело 2616e3fe6d
коміт b03983847c
142 files changed
+1032 -363

No files matched your search

+2 -1
Переглянути файл
@@ -9,6 +9,7 @@ import type { Location, Pos, Range, SymbolKind, SymbolMatch } from "@/bindings";
import { call, commands } from "@/platform/ipc";
import { structure, type Locals, type LocalSymbol, type NodeAt } from "@/editor/structure";
import { comparePos, rangeContains, rangeToOffsets } from "./positions";
import { tx } from "@/platform/i18n";
/** Subtle marker for answers that did not come from the language server. */
export const SYNTAX_NOTE = "Based on syntax (language server not ready)";
@@ -237,7 +238,7 @@ export async function planLocalRename(
if (locals.status === "notLocal")
return {
kind: "refused",
message: `Can't rename '${locals.name}' without the language server: only local variables can be renamed based on syntax`,
message: tx`Can't rename '${locals.name}' without the language server: only local variables can be renamed based on syntax`,
};
const { symbol } = locals;
const ranges = [symbol.definition, ...symbol.references].sort((a, b) => comparePos(a.start, b.start));