From b30b9bdce04cef044e4e31cd97cb933ae234c57a Mon Sep 17 00:00:00 2001 From: FullGas1 <51051389+FullGas1@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:33:13 +0200 Subject: [PATCH 1/2] fix(i18n): repay KO/ES debt from the revived stale keys (FIX-I18N-DEBT-REPAYMENT-2) Follow-up to FIX-I18N-STALE-COMMENT-PARSING (PR #119), which revived 56 keys wrongly marked -- STALE: in all four dictionaries. 22 KO + 7 ES entries among them were genuine debt - never translated even before that bug. EN/FR were already complete. Same method as FIX-I18N-DEBT-REPAYMENT: direct translation, no ANTHROPIC_API_KEY available and the Claude Code CLI fallback can't be invoked from a nested Claude Code session. FARP / FOB added to __keep_en for both languages (acronym, no translatable content). pytest tools/build/ 24/24 unchanged; generate_i18n_dicts.ps1 dry-run reports OK on all four dictionaries. Lot: .backlog/FIX-I18N-DEBT-REPAYMENT-2/. --- .backlog/FIX-I18N-DEBT-REPAYMENT-2/PRD.md | 64 +++++++++++++++++++ .../tickets/01-repay-ko-es-i18n-debt.md | 32 ++++++++++ .backlog/README.md | 1 + CHANGELOG.md | 10 +++ src/CTLD_i18n_es.lua | 15 +++-- src/CTLD_i18n_ko.lua | 45 ++++++------- 6 files changed, 138 insertions(+), 29 deletions(-) create mode 100644 .backlog/FIX-I18N-DEBT-REPAYMENT-2/PRD.md create mode 100644 .backlog/FIX-I18N-DEBT-REPAYMENT-2/tickets/01-repay-ko-es-i18n-debt.md diff --git a/.backlog/FIX-I18N-DEBT-REPAYMENT-2/PRD.md b/.backlog/FIX-I18N-DEBT-REPAYMENT-2/PRD.md new file mode 100644 index 0000000..aeb6088 --- /dev/null +++ b/.backlog/FIX-I18N-DEBT-REPAYMENT-2/PRD.md @@ -0,0 +1,64 @@ +Status: ready + +# FIX-I18N-DEBT-REPAYMENT-2 — Repay the i18n debt revived by FIX-I18N-STALE-COMMENT-PARSING + +Follow-up to `FIX-I18N-STALE-COMMENT-PARSING` (PR #119, merged), which revived 56 keys wrongly +marked `-- STALE:` in all four dictionaries. Reuses the decisions grilled for +`FIX-I18N-DEBT-REPAYMENT` (PR #116) without re-grilling — same task shape, same constraints. + +## Problem Statement + +Reviving those 56 keys restored English text everywhere and recovered every FR translation that +had been sitting inert in dead comments, but left genuine translation gaps behind: entries that +were never translated even before the STALE-marking bug existed. A Mission Maker playing in +Korean or Spanish still hits English fallback text for these. + +## Solution + +Fill the remaining empty stubs directly (translations produced and written into the dictionary +files without an external API/CLI call — no `ANTHROPIC_API_KEY` available, and the Claude Code CLI +fallback added by `TOOLING-I18N-CLAUDE-CODE-TRANSLATE` can't be invoked from inside the Claude Code +session doing this work, same nested-session constraint hit during `FIX-I18N-DEBT-REPAYMENT`). + +## User Stories + +1. As a Mission Maker playing CTLD in Korean, I want the remaining F10/vehicle-category menu + entries translated, so I don't hit English fallback text for labels that have been live (just + never translated) since before the STALE-marking bug. +2. As a Mission Maker playing CTLD in Spanish, I want the same for the 7 vehicle-category labels + still empty there. +3. As a maintainer, I want the repayment scoped to whatever is empty at execution time (22 KO + 7 + ES counted right after `FIX-I18N-STALE-COMMENT-PARSING` merged), not a number frozen at lot + creation — consistent with `FIX-I18N-DEBT-REPAYMENT`'s dynamic-scope decision. +4. As a maintainer, I want no dedicated KO/ES linguistic review, consistent with the accepted + mechanism from `FIX-I18N-DEBT-REPAYMENT` and `BUILD-DICT-AI-TRANSLATE` — verification is + mechanical (zero remaining empty stubs) plus normal PR diff review. + +## Implementation Decisions + +- Same as `FIX-I18N-DEBT-REPAYMENT`: translations produced directly (no `translate_i18n.py` + invocation), written into `src/CTLD_i18n_ko.lua` / `src/CTLD_i18n_es.lua` only — `fr` and `en` + are already complete after `FIX-I18N-STALE-COMMENT-PARSING`. +- Dynamic scope: every entry still empty at execution time, not a frozen list. +- No new tooling, no change to `translate_i18n.py`, `i18n_dict_utils.py`, + `generate_i18n_dicts.ps1`, `check_i18n_diff.py`, or the `i18n-guard` CI job. + +## Testing Decisions + +- No new or changed logic — existing `tools/build/` test suite (24 tests) must stay green, + untouched by this lot. +- Completeness verified mechanically: zero remaining empty entries in `CTLD_i18n_ko.lua` / + `CTLD_i18n_es.lua` (outside `__keep_en`), and a clean `generate_i18n_dicts.ps1` dry-run (no + `MISSING`, no unexpected `STALE`). + +## Out of Scope + +- Any change to i18n tooling code (translation content only). +- `CTLD_i18n_en.lua` / `CTLD_i18n_fr.lua` (already complete). +- The companion asset-check deprecation (separate roadmap item, David's call to defer). + +## Further Notes + +- Prior art: `FIX-I18N-DEBT-REPAYMENT` (PR #116) — identical task shape and decisions. +- Source of the debt: `FIX-I18N-STALE-COMMENT-PARSING` (PR #119) revived 56 keys; 29 of their + KO/ES translations (22 KO, 7 ES) were never produced in the first place. diff --git a/.backlog/FIX-I18N-DEBT-REPAYMENT-2/tickets/01-repay-ko-es-i18n-debt.md b/.backlog/FIX-I18N-DEBT-REPAYMENT-2/tickets/01-repay-ko-es-i18n-debt.md new file mode 100644 index 0000000..098448e --- /dev/null +++ b/.backlog/FIX-I18N-DEBT-REPAYMENT-2/tickets/01-repay-ko-es-i18n-debt.md @@ -0,0 +1,32 @@ +Status: ready + +# 01 — Repay KO + ES i18n stub debt (round 2) + +## Parent + +`.backlog/FIX-I18N-DEBT-REPAYMENT-2/PRD.md` + +## What to build + +Translate every remaining empty stub in `src/CTLD_i18n_ko.lua` and `src/CTLD_i18n_es.lua` directly +(no `ANTHROPIC_API_KEY`, Claude Code CLI unusable from a nested session), matching the exact method +used in `FIX-I18N-DEBT-REPAYMENT` ticket 01. Scope is dynamic — whatever is empty at execution time. + +## Acceptance criteria + +- [x] `src/CTLD_i18n_ko.lua` has zero entries equal to `""` outside its `__keep_en` block (22 + translated; `FARP / FOB` added to `__keep_en` instead of translated — acronym, no + translatable content). +- [x] `src/CTLD_i18n_es.lua` has zero entries equal to `""` outside its `__keep_en` block (7 + translated; `FARP / FOB` added to `__keep_en` same as KO). +- [x] `src/CTLD_i18n_en.lua` / `src/CTLD_i18n_fr.lua` unchanged. +- [x] No key added, removed, or renamed — only existing empty values filled in, plus one + `__keep_en` addition per language. +- [x] `pytest tools/build/` still passes (24/24, unchanged). +- [x] `generate_i18n_dicts.ps1` dry-run reports `OK` on all four dictionaries. +- [x] `CHANGELOG.md` `[Unreleased]` updated. +- [ ] PR opened against `develop`. + +## Blocked by + +None - can start immediately diff --git a/.backlog/README.md b/.backlog/README.md index 47eb001..62484f7 100644 --- a/.backlog/README.md +++ b/.backlog/README.md @@ -17,6 +17,7 @@ authored **per lot, when the lot is started** (not in batch). | `CHORE-UNTRACK-BUILT-ENGINE` | merged (PR #110) | `CTLD.lua` is generated and committed anyway. `.gitignore` line 5 calls it deliberate — *"available at repo root for DCS missions"* — a bootstrap-era reason that no longer holds: nothing points at it, and **VMCT, the assumed consumer, does not** (its `vendored.yaml` pins `2.0.0-rc3` with *"re-download the CTLD.lua asset from the matching release"* and watches `github-release`). The cost is paid every time: **26 of the 28 merges touching `src/` over 30 days carried the regenerated file** — a one-megabyte generated diff nobody reviews, and a guaranteed conflict between parallel PRs. The trap, measured rather than assumed: `python-quality` runs on ubuntu and never builds the engine, so deleting the file alone would drop the suite from **262 passed** to **234 passed / 27 skipped / 1 failed** while CI stayed green. So: build the engine in that job first (`merge_CTLD.ps1` made portable — two `\` paths), fix `test_inject_into_miz` (it crashes instead of skipping), then untrack. Depends on `FEAT-DEV-BUILD-CHANNEL`, which is what keeps the engine downloadable. No history rewriting (471 blobs = 2.8 MiB packed). | `chore/untrack-built-engine` | | `FEAT-CUSTOM-BEACON-SOUNDS` | merged (PR #112) | A beacon sound the Mission Maker chooses, instead of a text box naming a file the tool never installs. Grilled with Zip on 2026-08-08: custom is **derived** from `radioSound` (no second key that could disagree with the engine); a chosen file enters the mission under a **reserved name** (**ADR 0012**) because a Mission Maker whose own file is called `beacon.ogg` would otherwise see it silently overwritten; the original name survives as a schema-only label (`FIX-TOOL-I18N-LANG`'s lesson — a catalogue key would make every pre-lot configuration report a missing setting at mission start); the bytes are read at selection and live in the session, so reopening a `.miz` reinstalls them **on another machine with the original file gone**. `OggS` checked, no size cap, nothing deleted from the archive. | `feature/custom-beacon-sounds` | | `FEAT-DEV-BUILD-CHANNEL` | merged (PR #109) | An exe to hand a tester between two releases. Zip's first idea — the exe grafting an arbitrary `CTLD.lua` into a copy of itself — **works** (verified: rc6 + 1.17 MB appended still runs) and was dropped anyway: it pairs a new engine with the exe's older schema and interface, an unsigned exe altered after the build reads as tampered, and `--version` would keep lying. The `build-exe` job already produces a complete exe from a commit in **2 min 06 s** on free public-repo runners; it only lacked a trigger. Built on every merge into `develop`, published as an artifact **and** a floating `dev` pre-release (an artifact answers `401` to an anonymous download), versioned `-`. | `feature/dev-build-channel` | +| [`FIX-I18N-DEBT-REPAYMENT-2`](FIX-I18N-DEBT-REPAYMENT-2/PRD.md) | ready | Follow-up to `FIX-I18N-STALE-COMMENT-PARSING` (PR #119), which revived 56 keys wrongly marked `-- STALE:` in all four dictionaries — 29 of their KO/ES translations (22 KO, 7 ES) were never produced in the first place (genuine debt, not part of the bug). Same method as `FIX-I18N-DEBT-REPAYMENT`: direct translation (no `ANTHROPIC_API_KEY`, Claude Code CLI unusable from a nested session), dynamic scope, no dedicated linguistic review. | `fix/i18n-debt-repayment-2` | | [`FIX-I18N-STALE-COMMENT-PARSING`](FIX-I18N-STALE-COMMENT-PARSING/PRD.md) | merged (PR #119) | The shared i18n dict parser (`i18n_dict_utils.py`), `generate_i18n_dicts.ps1`'s `Get-DictKeys`, and `translate_i18n.py`'s `_apply_translations` all matched a `ctld.i18n[...] = "..."` line regardless of a `-- STALE:` comment prefix. Fixing it and re-running `-Apply` for real surfaced a production bug much bigger than the KO/ES cosmetic drift this lot was scoped for: **56 keys — every AA system component label plus several F10 menu labels — were wrongly marked `-- STALE:` in all four dictionaries including English**, breaking F10/AA-system text in every language. `-Apply` revived all 56; the FR/ES/KO translations sitting in the old dead comments were hand-recovered into the fresh entries rather than lost. No ADR — a bug fix, not a design trade-off. | `fix/i18n-stale-comment-parsing` | | [`TOOLING-I18N-CLAUDE-CODE-TRANSLATE`](TOOLING-I18N-CLAUDE-CODE-TRANSLATE/PRD.md) | merged (PR #118) | `tools/build/translate_i18n.py` only auto-translates i18n stubs when `ANTHROPIC_API_KEY` is set (separate Anthropic Console billing) — a contributor with a Claude Code subscription but no such key gets nothing, lived concretely on `FIX-I18N-DEBT-REPAYMENT` (140 entries translated by hand for want of a key). Adds a fallback: when the API key is absent, shell out to the Claude Code CLI (`claude -p`, `--model claude-haiku-4-5-20251001` pinned) instead — dual mode, API path unchanged/first-priority, no pre-flight availability check (same non-blocking `try/except` as today), combined warning when neither is available. CI (`i18n-guard`) untouched — inspects dictionary content only, never the translation mechanism. See **ADR 0014**. | `tooling/i18n-claude-code-translate` | | [`FIX-I18N-DEBT-REPAYMENT`](FIX-I18N-DEBT-REPAYMENT/PRD.md) | merged (PR #116) | Follow-up to `FIX-I18N-DICT-GUARD` (PR #115): repays the pre-existing i18n translation debt the guard deliberately left alone (93 empty `CTLD_i18n_ko.lua` entries + 78 empty `CTLD_i18n_es.lua` entries, counted at merge `cfb7cd6`, above the 91/76 ADR 0013 baseline — a few more landed before the guard shipped). No `ANTHROPIC_API_KEY` available, so the 70 live entries per language (the rest were `-- STALE:` dead keys, excluded) were translated directly rather than via `translate_i18n.py`; `JTAC` and `%1 [%2] %3.` added to `__keep_en`. No code changes — translation content only, no new tooling. | `fix/i18n-debt-repayment` | diff --git a/CHANGELOG.md b/CHANGELOG.md index 972baf9..febd9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ Versioning follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Fixed — remaining KO/ES i18n debt from the revived stale keys repaid (FIX-I18N-DEBT-REPAYMENT-2) + +- **22 KO + 7 ES entries translated**: genuine debt among the 56 keys `FIX-I18N-STALE-COMMENT-PARSING` + revived — never translated even before that bug, not part of it. `CTLD_i18n_en.lua` and + `CTLD_i18n_fr.lua` were already complete after that fix. +- `FARP / FOB` added to KO's and ES's `__keep_en` block (acronym, no translatable content) — + the same mechanism already used for `JTAC`, `CTLD`, etc. +- No tooling changed, translation content only. `pytest tools/build/` 24/24 green (unchanged); + `generate_i18n_dicts.ps1` dry-run reports `OK` on all four dictionaries. + ### Fixed — 56 i18n keys wrongly marked stale, breaking AA system / crate labels in every language (FIX-I18N-STALE-COMMENT-PARSING) - **The i18n tooling's dictionary parser matched a `-- STALE:`-commented line the same as a live diff --git a/src/CTLD_i18n_es.lua b/src/CTLD_i18n_es.lua index d000745..8faf33f 100644 --- a/src/CTLD_i18n_es.lua +++ b/src/CTLD_i18n_es.lua @@ -543,6 +543,7 @@ ctld.i18n["es"].__keep_en = { ["%1 [%2] %3."] = true, ["%1\nFOB @ %2"] = true, ["CTLD"] = true, + ["FARP / FOB"] = true, ["JTAC"] = true, } @@ -553,13 +554,13 @@ ctld.i18n["es"]["Cannot deploy %1: AA system limit reached (%2/%3)"] = "No se pu ctld.i18n["es"]["Cannot repair %1. No damaged %1 within %2m"] = "No se puede reparar %1. No hay %1 dañado en %2m" --- Keys added by generate_i18n_dicts.ps1 on 2026-07-22 -ctld.i18n["es"]["Air Defense (AA)"] = "" -ctld.i18n["es"]["Aircraft"] = "" -ctld.i18n["es"]["FARP / FOB"] = "" -ctld.i18n["es"]["Ground Vehicles"] = "" -ctld.i18n["es"]["Helicopters"] = "" -ctld.i18n["es"]["Infantry"] = "" -ctld.i18n["es"]["Ships"] = "" +ctld.i18n["es"]["Air Defense (AA)"] = "Defensa aérea (AA)" +ctld.i18n["es"]["Aircraft"] = "Aeronaves" +ctld.i18n["es"]["FARP / FOB"] = "FARP / FOB" +ctld.i18n["es"]["Ground Vehicles"] = "Vehículos terrestres" +ctld.i18n["es"]["Helicopters"] = "Helicópteros" +ctld.i18n["es"]["Infantry"] = "Infantería" +ctld.i18n["es"]["Ships"] = "Barcos" --- Keys added by generate_i18n_dicts.ps1 on 2026-07-30 ctld.i18n["es"]["%1 setting(s) absent from the mission config — CTLD default used: %2"] = "%1 ajuste(s) ausente(s) de la configuración de la misión — se usó el valor por defecto de CTLD: %2" diff --git a/src/CTLD_i18n_ko.lua b/src/CTLD_i18n_ko.lua index 90c08f1..3db84e7 100644 --- a/src/CTLD_i18n_ko.lua +++ b/src/CTLD_i18n_ko.lua @@ -335,16 +335,16 @@ ctld.i18n["ko"]["%1 crate safely released."] = "%1 화물이 안전하게 분리 ctld.i18n["ko"]["%1 packed into %2 crate(s)."] = "%1이(가) %2개의 화물로 포장되었습니다." ctld.i18n["ko"]["[activate]"] = "[활성화]" ctld.i18n["ko"]["[deactivate]"] = "[비활성화]" -ctld.i18n["ko"]["Cannot build %1\n%2\n\nOr the crates are not close enough together"] = "" +ctld.i18n["ko"]["Cannot build %1\n%2\n\nOr the crates are not close enough together"] = "%1을(를) 건설할 수 없습니다\n%2\n\n또는 화물들이 충분히 가깝게 모여 있지 않습니다" ctld.i18n["ko"]["Cargo: [%1] — %2 troops, %3 kg"] = "화물: [%1] — 병력 %2명, %3 kg" ctld.i18n["ko"]["Clear Mine Field (%1 mines, ~%2 m)"] = "지뢰밭 제거 (지뢰 %1개, ~%2 m)" ctld.i18n["ko"]["Crate Commands"] = "화물 명령" ctld.i18n["ko"]["Cut Slingload"] = "슬링로드 절단" -ctld.i18n["ko"]["Drop Blue Smoke"] = "" -ctld.i18n["ko"]["Drop Crate(s)"] = "" -ctld.i18n["ko"]["Drop Green Smoke"] = "" -ctld.i18n["ko"]["Drop Orange Smoke"] = "" -ctld.i18n["ko"]["Drop Red Smoke"] = "" +ctld.i18n["ko"]["Drop Blue Smoke"] = "청색 연막 투하" +ctld.i18n["ko"]["Drop Crate(s)"] = "화물 투하" +ctld.i18n["ko"]["Drop Green Smoke"] = "녹색 연막 투하" +ctld.i18n["ko"]["Drop Orange Smoke"] = "주황색 연막 투하" +ctld.i18n["ko"]["Drop Red Smoke"] = "적색 연막 투하" ctld.i18n["ko"]["Extracted [%1] (%2 troops)."] = "추출됨 [%1] (%2명)." ctld.i18n["ko"]["FOB 90% complete - final installation in progress..."] = "FOB 90% 완료 - 최종 설치 진행 중..." ctld.i18n["ko"]["FOB construction started by %1."] = "%1이(가) FOB 건설을 시작했습니다." @@ -356,22 +356,22 @@ ctld.i18n["ko"]["into %1"] = "%1(으)로" ctld.i18n["ko"]["into combat"] = "전투로" ctld.i18n["ko"]["JTAC"] = "JTAC" ctld.i18n["ko"]["JTAC Group 2"] = "JTAC 그룹 2" -ctld.i18n["ko"]["JTAC Status"] = "" +ctld.i18n["ko"]["JTAC Status"] = "JTAC 상태" ctld.i18n["ko"]["Land near troops to extract them (%1m away)."] = "병력 근처에 착륙하여 추출하세요 (%1m 거리)." -ctld.i18n["ko"]["Load "] = "" -ctld.i18n["ko"]["Load / Extract Vehicles"] = "" +ctld.i18n["ko"]["Load "] = "적재 " +ctld.i18n["ko"]["Load / Extract Vehicles"] = "차량 적재 / 추출" ctld.i18n["ko"]["Load from %1"] = "%1에서 적재" ctld.i18n["ko"]["Loaded: %1 (%2 troops)."] = "적재됨: %1 (%2명)." ctld.i18n["ko"]["Mine Field"] = "지뢰밭" ctld.i18n["ko"]["Mine Field cleared by %1."] = "%1이(가) 지뢰밭을 제거했습니다." -ctld.i18n["ko"]["Missing %1\n"] = "" +ctld.i18n["ko"]["Missing %1\n"] = "누락: %1\n" ctld.i18n["ko"]["No active JTACs."] = "활성화된 JTAC 없음." ctld.i18n["ko"]["No crates loaded."] = "적재된 화물 없음." -ctld.i18n["ko"]["No extractable troops nearby!"] = "" +ctld.i18n["ko"]["No extractable troops nearby!"] = "근처에 추출 가능한 병력이 없습니다!" ctld.i18n["ko"]["No slingloaded crate on board."] = "탑재된 슬링로드 화물 없음." ctld.i18n["ko"]["no target"] = "목표 없음" ctld.i18n["ko"]["No troops onboard and no extractable troops nearby."] = "탑승 병력 없음, 근처에 추출 가능한 병력도 없음." -ctld.i18n["ko"]["Out of parts for AA Systems. Current limit is %1\n"] = "" +ctld.i18n["ko"]["Out of parts for AA Systems. Current limit is %1\n"] = "AA 시스템 부품 부족. 현재 한도는 %1\n" ctld.i18n["ko"]["Parachute Crates"] = "화물 낙하산 투하" ctld.i18n["ko"]["Parachute Troops"] = "병력 낙하산 투하" ctld.i18n["ko"]["Parachute Vehicle"] = "차량 낙하산 투하" @@ -395,7 +395,7 @@ ctld.i18n["ko"]["Too high! %1 crate destroyed on impact."] = "너무 높습니 ctld.i18n["ko"]["Too low to hook crate.\n\nHold hover for %1 seconds"] = "너무 낮아서 화물을 걸 수 없습니다.\n\n%1초 동안 정지비행을 유지하세요" ctld.i18n["ko"]["Troop Commands"] = "병력 명령" ctld.i18n["ko"]["Troops returned to base."] = "병력이 기지로 복귀했습니다." -ctld.i18n["ko"]["Unload Vehicles"] = "" +ctld.i18n["ko"]["Unload Vehicles"] = "차량 하역" ctld.i18n["ko"]["Vehicle Commands"] = "차량 명령" ctld.i18n["ko"]["You must land inside the pickup zone to load troops."] = "병력을 태우려면 픽업 구역 안에 착륙해야 합니다." ctld.i18n["ko"]["You must land to extract troops."] = "병력을 추출하려면 착륙해야 합니다." @@ -407,6 +407,7 @@ ctld.i18n["ko"].__keep_en = { ["BRDM-2"] = true, ["BTR-D"] = true, ["CTLD"] = true, + ["FARP / FOB"] = true, ["JTAC"] = true, ["LPWS C-RAM"] = true, ["MLRS"] = true, @@ -415,20 +416,20 @@ ctld.i18n["ko"].__keep_en = { --- Keys added by generate_i18n_dicts.ps1 on 2026-07-22 ctld.i18n["ko"]["%1 successfully deployed a full %2 in the field.\n\nAA Active System limit: %3\nActive: %4"] = "%1이(가) %2을(를) 현장에 성공적으로 배치했습니다.\n\nAA 활성 시스템 한도: %3\n활성: %4" -ctld.i18n["ko"]["%1 successfully rearmed a full %2 in the field"] = "" -ctld.i18n["ko"]["%1 successfully repaired a full %2 in the field."] = "" +ctld.i18n["ko"]["%1 successfully rearmed a full %2 in the field"] = "%1이(가) 현장에서 %2을(를) 완전히 재무장했습니다" +ctld.i18n["ko"]["%1 successfully repaired a full %2 in the field."] = "%1이(가) 현장에서 %2을(를) 완전히 수리했습니다." ctld.i18n["ko"]["AI deployed a full %1.\n\nAA Active System limit: %2\nActive: %3"] = "AI가 %1을(를) 완전히 배치했습니다.\n\nAA 활성 시스템 한도: %2\n활성: %3" ctld.i18n["ko"]["Cannot deploy %1: AA system limit reached (%2/%3)"] = "%1을(를) 배치할 수 없습니다: AA 시스템 한도 도달 (%2/%3)" ctld.i18n["ko"]["Cannot repair %1. No damaged %1 within %2m"] = "%1을(를) 수리할 수 없습니다. %2m 이내에 손상된 %1 없음" --- Keys added by generate_i18n_dicts.ps1 on 2026-07-22 -ctld.i18n["ko"]["Air Defense (AA)"] = "" -ctld.i18n["ko"]["Aircraft"] = "" -ctld.i18n["ko"]["FARP / FOB"] = "" -ctld.i18n["ko"]["Ground Vehicles"] = "" -ctld.i18n["ko"]["Helicopters"] = "" -ctld.i18n["ko"]["Infantry"] = "" -ctld.i18n["ko"]["Ships"] = "" +ctld.i18n["ko"]["Air Defense (AA)"] = "방공 (AA)" +ctld.i18n["ko"]["Aircraft"] = "항공기" +ctld.i18n["ko"]["FARP / FOB"] = "FARP / FOB" +ctld.i18n["ko"]["Ground Vehicles"] = "지상 차량" +ctld.i18n["ko"]["Helicopters"] = "헬리콥터" +ctld.i18n["ko"]["Infantry"] = "보병" +ctld.i18n["ko"]["Ships"] = "함선" --- Keys added by generate_i18n_dicts.ps1 on 2026-07-30 ctld.i18n["ko"]["%1 setting(s) absent from the mission config — CTLD default used: %2"] = "미션 설정에 %1 항목이 없음 — CTLD 기본값 사용: %2" From 4d776ffcc25810814cfa1a57d0649be0529701dc Mon Sep 17 00:00:00 2001 From: FullGas1 <51051389+FullGas1@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:33:41 +0200 Subject: [PATCH 2/2] chore(backlog): mark FIX-I18N-DEBT-REPAYMENT-2 as merged (PR #120) --- .backlog/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.backlog/README.md b/.backlog/README.md index 62484f7..1f55084 100644 --- a/.backlog/README.md +++ b/.backlog/README.md @@ -17,7 +17,7 @@ authored **per lot, when the lot is started** (not in batch). | `CHORE-UNTRACK-BUILT-ENGINE` | merged (PR #110) | `CTLD.lua` is generated and committed anyway. `.gitignore` line 5 calls it deliberate — *"available at repo root for DCS missions"* — a bootstrap-era reason that no longer holds: nothing points at it, and **VMCT, the assumed consumer, does not** (its `vendored.yaml` pins `2.0.0-rc3` with *"re-download the CTLD.lua asset from the matching release"* and watches `github-release`). The cost is paid every time: **26 of the 28 merges touching `src/` over 30 days carried the regenerated file** — a one-megabyte generated diff nobody reviews, and a guaranteed conflict between parallel PRs. The trap, measured rather than assumed: `python-quality` runs on ubuntu and never builds the engine, so deleting the file alone would drop the suite from **262 passed** to **234 passed / 27 skipped / 1 failed** while CI stayed green. So: build the engine in that job first (`merge_CTLD.ps1` made portable — two `\` paths), fix `test_inject_into_miz` (it crashes instead of skipping), then untrack. Depends on `FEAT-DEV-BUILD-CHANNEL`, which is what keeps the engine downloadable. No history rewriting (471 blobs = 2.8 MiB packed). | `chore/untrack-built-engine` | | `FEAT-CUSTOM-BEACON-SOUNDS` | merged (PR #112) | A beacon sound the Mission Maker chooses, instead of a text box naming a file the tool never installs. Grilled with Zip on 2026-08-08: custom is **derived** from `radioSound` (no second key that could disagree with the engine); a chosen file enters the mission under a **reserved name** (**ADR 0012**) because a Mission Maker whose own file is called `beacon.ogg` would otherwise see it silently overwritten; the original name survives as a schema-only label (`FIX-TOOL-I18N-LANG`'s lesson — a catalogue key would make every pre-lot configuration report a missing setting at mission start); the bytes are read at selection and live in the session, so reopening a `.miz` reinstalls them **on another machine with the original file gone**. `OggS` checked, no size cap, nothing deleted from the archive. | `feature/custom-beacon-sounds` | | `FEAT-DEV-BUILD-CHANNEL` | merged (PR #109) | An exe to hand a tester between two releases. Zip's first idea — the exe grafting an arbitrary `CTLD.lua` into a copy of itself — **works** (verified: rc6 + 1.17 MB appended still runs) and was dropped anyway: it pairs a new engine with the exe's older schema and interface, an unsigned exe altered after the build reads as tampered, and `--version` would keep lying. The `build-exe` job already produces a complete exe from a commit in **2 min 06 s** on free public-repo runners; it only lacked a trigger. Built on every merge into `develop`, published as an artifact **and** a floating `dev` pre-release (an artifact answers `401` to an anonymous download), versioned `-`. | `feature/dev-build-channel` | -| [`FIX-I18N-DEBT-REPAYMENT-2`](FIX-I18N-DEBT-REPAYMENT-2/PRD.md) | ready | Follow-up to `FIX-I18N-STALE-COMMENT-PARSING` (PR #119), which revived 56 keys wrongly marked `-- STALE:` in all four dictionaries — 29 of their KO/ES translations (22 KO, 7 ES) were never produced in the first place (genuine debt, not part of the bug). Same method as `FIX-I18N-DEBT-REPAYMENT`: direct translation (no `ANTHROPIC_API_KEY`, Claude Code CLI unusable from a nested session), dynamic scope, no dedicated linguistic review. | `fix/i18n-debt-repayment-2` | +| [`FIX-I18N-DEBT-REPAYMENT-2`](FIX-I18N-DEBT-REPAYMENT-2/PRD.md) | merged (PR #120) | Follow-up to `FIX-I18N-STALE-COMMENT-PARSING` (PR #119), which revived 56 keys wrongly marked `-- STALE:` in all four dictionaries — 29 of their KO/ES translations (22 KO, 7 ES) were never produced in the first place (genuine debt, not part of the bug). Same method as `FIX-I18N-DEBT-REPAYMENT`: direct translation (no `ANTHROPIC_API_KEY`, Claude Code CLI unusable from a nested session), dynamic scope, no dedicated linguistic review. | `fix/i18n-debt-repayment-2` | | [`FIX-I18N-STALE-COMMENT-PARSING`](FIX-I18N-STALE-COMMENT-PARSING/PRD.md) | merged (PR #119) | The shared i18n dict parser (`i18n_dict_utils.py`), `generate_i18n_dicts.ps1`'s `Get-DictKeys`, and `translate_i18n.py`'s `_apply_translations` all matched a `ctld.i18n[...] = "..."` line regardless of a `-- STALE:` comment prefix. Fixing it and re-running `-Apply` for real surfaced a production bug much bigger than the KO/ES cosmetic drift this lot was scoped for: **56 keys — every AA system component label plus several F10 menu labels — were wrongly marked `-- STALE:` in all four dictionaries including English**, breaking F10/AA-system text in every language. `-Apply` revived all 56; the FR/ES/KO translations sitting in the old dead comments were hand-recovered into the fresh entries rather than lost. No ADR — a bug fix, not a design trade-off. | `fix/i18n-stale-comment-parsing` | | [`TOOLING-I18N-CLAUDE-CODE-TRANSLATE`](TOOLING-I18N-CLAUDE-CODE-TRANSLATE/PRD.md) | merged (PR #118) | `tools/build/translate_i18n.py` only auto-translates i18n stubs when `ANTHROPIC_API_KEY` is set (separate Anthropic Console billing) — a contributor with a Claude Code subscription but no such key gets nothing, lived concretely on `FIX-I18N-DEBT-REPAYMENT` (140 entries translated by hand for want of a key). Adds a fallback: when the API key is absent, shell out to the Claude Code CLI (`claude -p`, `--model claude-haiku-4-5-20251001` pinned) instead — dual mode, API path unchanged/first-priority, no pre-flight availability check (same non-blocking `try/except` as today), combined warning when neither is available. CI (`i18n-guard`) untouched — inspects dictionary content only, never the translation mechanism. See **ADR 0014**. | `tooling/i18n-claude-code-translate` | | [`FIX-I18N-DEBT-REPAYMENT`](FIX-I18N-DEBT-REPAYMENT/PRD.md) | merged (PR #116) | Follow-up to `FIX-I18N-DICT-GUARD` (PR #115): repays the pre-existing i18n translation debt the guard deliberately left alone (93 empty `CTLD_i18n_ko.lua` entries + 78 empty `CTLD_i18n_es.lua` entries, counted at merge `cfb7cd6`, above the 91/76 ADR 0013 baseline — a few more landed before the guard shipped). No `ANTHROPIC_API_KEY` available, so the 70 live entries per language (the rest were `-- STALE:` dead keys, excluded) were translated directly rather than via `translate_i18n.py`; `JTAC` and `%1 [%2] %3.` added to `__keep_en`. No code changes — translation content only, no new tooling. | `fix/i18n-debt-repayment` |