Files
iptvnator/tools/i18n
4grayandClaude Opus 5.5 afd7a5f331 fix(i18n): translate source cleanup, source health and diagnostics strings (#1884)
* fix(i18n): translate source cleanup, source health and diagnostics strings

Translate every English-identical SOURCE_CLEANUP, SOURCE_HEALTH and
PLAYBACK_DIAGNOSTICS value in all 18 locales, plus the external playback
dock statuses (the same strings as the diagnostic ones), the EPG source
list strings, and leftover English in hu, ko and el. The identical-English
baseline drops 973 entries (2106 -> 1133) and gains none.

Add tools/i18n/check-duplicates.mjs (pnpm run i18n:duplicates), a report
of English strings defined under several keys whose translations differ
per locale, and align drifted wordings: 101 diverging groups -> 65.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): call cleaned-up zh/zhtw sources invalid, not unactivated

The cleanup dialog lists expired and disabled accounts too, so 未激活 /
未啟用 ("not activated") misexplained why a source is offered for deletion.
Use 失效 ("no longer valid") in the title and the confirmed group heading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): one zh/zhtw wording for copying the stream URL

The playback diagnostics button said 复制 URL / 複製 URL while the
channel details dialog (new on master) says 复制流 URL / 複製串流網址
for the same action; use the details dialog wording in both places.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:59:50 +02:00
..

i18n fill-missing tool

One-shot helper for bringing every locale file in apps/web/src/assets/i18n/ to full coverage of en.json.

How it works

en.json is the source of truth. For every other locale, the script:

  1. Walks en.json in order (top-level + nested).
  2. For each leaf key:
    • Keeps the locale's existing translation if present.
    • Otherwise pulls the translation from tools/i18n/patches/<lang>.json (a flat object keyed by dotted path).
    • If neither exists, leaves the key absent and exits non-zero.
  3. Writes the merged tree back to the locale file with 4-space indent and a trailing newline.

Existing translations are never overwritten.

Workflow

# 1. Generate one missing-strings dump per locale (flat dotted-path → EN string)
node tools/i18n/fill-missing.mjs --emit-missing
# → tools/i18n/missing/<lang>.json

# 2. Translate each missing/<lang>.json into the equivalent patch file
#    in tools/i18n/patches/<lang>.json (same shape: { dotted.path: translated }).

# 3. Merge patches into the locale files
node tools/i18n/fill-missing.mjs

# 4. Verify
node -e "
const fs=require('fs');
const en=JSON.parse(fs.readFileSync('apps/web/src/assets/i18n/en.json','utf8'));
const collect=(d,p='')=>Object.entries(d).flatMap(([k,v])=>v&&typeof v==='object'?collect(v,p?p+'.'+k:k):[p?p+'.'+k:k]);
const enKeys=new Set(collect(en));
for(const f of fs.readdirSync('apps/web/src/assets/i18n').filter(x=>x.endsWith('.json')&&x!=='en.json')){
  const k=new Set(collect(JSON.parse(fs.readFileSync('apps/web/src/assets/i18n/'+f,'utf8'))));
  console.log(f,'missing:',[...enKeys].filter(x=>!k.has(x)).length,'extra:',[...k].filter(x=>!enKeys.has(x)).length);
}"

For a non-mutating CI/agent check, run:

pnpm run i18n:check

The check fails on missing or extra keys against en.json, and on values that are identical to English unless identical-en-baseline.json lists that exact English text for the locale and key. Do not copy English into a locale as a placeholder: translate it. The contract, including when an entry stops covering a key, is in the validation map.

Baseline entries that are no longer identical are reported, not fatal. After translating baselined values, or for a value that is legitimately the same in a language (a brand, a technical name, a loanword), rewrite the baseline and review every added entry in the diff:

pnpm run i18n:baseline:update

pnpm run i18n:check also runs check-usage.mjs, which fails on a key the renderer uses but en.json lacks (ngx-translate would show the raw key). It scans production .ts and .html files under apps/web/src, apps/remote-control-web/src and libs for piped keys (including the branches of a parenthesised operand), translate calls and dotted literals in an en.json namespace; run it alone with pnpm run i18n:usage. Keys built entirely at runtime are not checked.

The checkers' own tests run with pnpm run i18n:test; CI runs both through pnpm run i18n:validate. For a full audit that ignores the baseline, run:

node tools/i18n/check-drift.mjs --fail-on-identical

pnpm run i18n:duplicates lists English strings that en.json defines under several keys while a locale translates those keys differently, such as one "Close" button saying "Afsluiten" and another "Sluiten". Add --verbose for each locale's variants and keys, or --max <n> to fail above n groups. It is a report, not a CI gate: a group can be legitimate when context needs another grammatical form. When aligning, change the drifted values rather than removing keys.

Changing existing translations

fill-missing.mjs never overwrites, so rewording an existing value or paying down baseline debt means editing the value in the locale file directly (keep the key order, 4-space indent and trailing newline), then running pnpm run i18n:baseline:update and checking that the baseline diff only removes entries.

Translation rules

  • Preserve {{interpolation}} placeholders verbatim.
  • Preserve any inline HTML tags.
  • Match the locale's existing punctuation, casing, and terminology.
  • Keep brand names (IPTVnator, Xtream, Stalker, M3U, EPG, MPV, VLC) untranslated.
  • Short UI strings stay short — no length blow-up.