From be0fcf5dec7b2054d6f349581d61602d5b6777af Mon Sep 17 00:00:00 2001 From: FullGas1 <51051389+FullGas1@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:53:03 +0200 Subject: [PATCH 1/2] fix(i18n): repay pre-existing KO/ES translation debt Follow-up to FIX-I18N-DICT-GUARD (PR #115), which explicitly deferred this work. 93 KO / 78 ES empty entries counted at merge; 23 / 8 turned out to be STALE dead keys in CTLD_i18n_en.lua (no longer referenced by src/), left untouched. The 70 live entries per language (same key set for both) are now translated. No ANTHROPIC_API_KEY was available, so translations were produced and written directly rather than via translate_i18n.py - the script itself is untouched, still the documented mechanism for next time. JTAC and %1 [%2] %3. (no translatable content) added to each dictionary's __keep_en block rather than left flagged as stubs forever. pytest tools/build/ 17/17 green; generate_i18n_dicts.ps1 dry-run reports 0 MISSING. Lot: .backlog/FIX-I18N-DEBT-REPAYMENT/. --- .backlog/FIX-I18N-DEBT-REPAYMENT/PRD.md | 129 ++++++++++++++++ .../tickets/01-repay-ko-es-i18n-debt.md | 46 ++++++ .backlog/README.md | 1 + CHANGELOG.md | 15 ++ src/CTLD_i18n_es.lua | 142 +++++++++--------- src/CTLD_i18n_ko.lua | 142 +++++++++--------- 6 files changed, 335 insertions(+), 140 deletions(-) create mode 100644 .backlog/FIX-I18N-DEBT-REPAYMENT/PRD.md create mode 100644 .backlog/FIX-I18N-DEBT-REPAYMENT/tickets/01-repay-ko-es-i18n-debt.md diff --git a/.backlog/FIX-I18N-DEBT-REPAYMENT/PRD.md b/.backlog/FIX-I18N-DEBT-REPAYMENT/PRD.md new file mode 100644 index 0000000..cbae489 --- /dev/null +++ b/.backlog/FIX-I18N-DEBT-REPAYMENT/PRD.md @@ -0,0 +1,129 @@ +Status: ready + +# FIX-I18N-DEBT-REPAYMENT — Repay the pre-existing i18n translation debt + +Follow-up to `FIX-I18N-DICT-GUARD` (PR #115, merged `cfb7cd6`), which explicitly deferred this +work: *"A follow-up lot to repay the existing debt must follow immediately"* (ADR 0013). + +## Problem Statement + +A Mission Maker running CTLD in Korean or Spanish still hits raw English fallback text on some +F10 menu entries today. `FIX-I18N-DICT-GUARD` stopped the bleeding — CI now blocks any *new* +untranslated key — but it deliberately left the dictionaries as they already stood on `develop`: +93 empty entries in `CTLD_i18n_ko.lua` and 78 in `CTLD_i18n_es.lua` (counted at `cfb7cd6`, slightly +above the 91/76 ADR 0013 baseline — a few more landed between the ADR's snapshot and the guard's +merge, which the ADR anticipated and explicitly put in this lot's scope). `CTLD_i18n_fr.lua` has +none — filled by hand in a prior commit, outside the auto-translate path. + +## Solution + +Run `tools/build/translate_i18n.py` — now fixed by `FIX-I18N-DICT-GUARD` to actually detect an +empty (`""`) entry as a stub, not only one identical to the English text — locally with +`ANTHROPIC_API_KEY` set, against `develop`. It batches every stub per language to Claude Haiku and +writes the translations back in place. Re-run until no stub remains (idempotent: each pass only +resends what's still empty), commit the resulting dictionary files, and open a PR. + +No code changes: this lot touches only translation *content* in `src/CTLD_i18n_ko.lua` and +`src/CTLD_i18n_es.lua`. + +## User Stories + +1. As a Mission Maker playing CTLD in Korean, I want every F10 menu entry translated, so that I + never see raw English fallback text mid-mission. +2. As a Mission Maker playing CTLD in Spanish, I want the same guarantee. +3. As a maintainer, I want the debt repaid using the same tool the project already trusts for this + job (`translate_i18n.py`, `BUILD-DICT-AI-TRANSLATE`), so this lot introduces no new translation + mechanism to review or maintain. +4. As a maintainer, I want the repayment scoped to whatever is actually empty at execution time — + not a number frozen at the ADR 0013 snapshot — so debt that accrued between the ADR and the + guard's merge is repaid in the same pass instead of being left for yet another lot. +5. As a reviewer, I want a PR whose diff is exclusively translation-value changes inside the KO/ES + dictionary files, so review is a scan for corruption (broken escaping, wrong key touched, + malformed Lua) rather than a linguistic audit none of us is qualified to do. +6. As a maintainer, I want confirmation that zero stubs remain in KO and ES once this lot lands, so + "debt repaid" is a verifiable fact, not an approximation. +7. As a future contributor adding a new `ctld.tr()` key, I want this lot to leave `i18n-guard` and + `translate_i18n.py` themselves untouched, so the guard's already-reviewed behavior isn't + revisited as a side effect of a translation-content lot. + +## Implementation Decisions + +- **Tool**: intended to be `tools/build/translate_i18n.py`, unmodified, run locally with + `ANTHROPIC_API_KEY` set — matching `BUILD-DICT-AI-TRANSLATE`'s deliberate "local-only" decision, + preserved by `FIX-I18N-DICT-GUARD`/ADR 0013. **Deviation**: no `ANTHROPIC_API_KEY` was available + (a separate Anthropic Console API key with its own pay-as-you-go billing, distinct from the + Claude Code subscription used to run this lot) — the maintainer chose to translate directly + rather than acquire one. Translations were produced and written into the dictionary files + without invoking the script or any external API call; `translate_i18n.py` itself is still + untouched and remains the documented mechanism for the next time this debt needs repaying. +- **STALE keys excluded from scope, discovered during execution**: `tools/build/i18n_dict_utils.py`'s + parser (and by extension `translate_i18n.py`'s stub predicate) does not distinguish a live + `ctld.i18n[...] = "..."` line from one commented out with a `-- STALE:` prefix — both match the + same regex. Of the 93 KO / 78 ES stubs counted at merge, 23 (KO) / 8 (ES) keys turned out to be + `-- STALE:` in `CTLD_i18n_en.lua` itself (no longer referenced by any `ctld.tr()`/config-YAML + call in `src/`) — dead debt that translating would not fix any live menu entry for. Excluded; + **70 live entries per language** (same key set for both) were the actual scope. This STALE/live + distinction is a latent gap in the shared parser, not something this content-only lot fixes — + worth a separate tooling follow-up. +- **`__keep_en` extended**: two of the 70 live keys (`JTAC`, a proper noun/sigil, and + `%1 [%2] %3.`, a placeholder-only string with no translatable content) would otherwise stay + flagged as "stub" forever under the tool's own definition (value identical to the EN text). + Added to each dictionary's existing `__keep_en` block instead — the same sanctioned mechanism + already used for `CTLD`, `MLRS`, `%1\nFOB @ %2`, etc. +- **Scope is dynamic, not the ADR 0013 snapshot**: repay every entry that is still an empty stub in + `CTLD_i18n_ko.lua` and `CTLD_i18n_es.lua` at the time the script runs (93 + 78 as last counted at + `cfb7cd6`, expected to shift slightly by execution time), not a hardcoded list of the 91/76 keys + named in ADR 0013. `translate_i18n.py` already scans the full dictionary on every run — targeting + a frozen list would add complexity for no benefit. +- **Idempotent re-run to exhaustion**: run the script, inspect what (if anything) is still empty, + re-run — repeat until both `CTLD_i18n_ko.lua` and `CTLD_i18n_es.lua` have zero empty entries + outside their `__keep_en` block. The script already only resends what's still a stub, so repeated + runs cost nothing extra for already-translated keys. +- **No new tooling, no changes to `translate_i18n.py`, `i18n_dict_utils.py`, `check_i18n_diff.py`, + or the `i18n-guard` CI job.** This lot is a data-only repayment on top of already-reviewed + machinery. +- **`CTLD_i18n_fr.lua` untouched**: already at zero empty entries, out of this lot's scope by + construction (nothing for `translate_i18n.py` to pick up there). +- **Review model**: no dedicated KO/ES linguistic review (no native speaker on the team; Claude + Haiku via `translate_i18n.py` is the already-accepted mechanism for this exact case). Verification + is mechanical: confirm the stub count reaches zero, and a normal PR diff review for structural + correctness (each hunk touches exactly one dictionary value, no key added/removed, no broken Lua + string escaping). + +## Testing Decisions + +- No new or changed logic ships in this lot — `translate_i18n.py` and its unit tests + (`tools/build/test_translate_i18n.py`, `tools/build/test_i18n_dict_utils.py`) already cover the + stub-detection predicate this lot relies on, and stay green untouched (`pytest tools/build/`). +- Repayment completeness is verified mechanically, not via a new automated test: after each + `translate_i18n.py` run, confirm zero remaining empty entries in `CTLD_i18n_ko.lua` and + `CTLD_i18n_es.lua` (outside `__keep_en`) by inspection — e.g. reusing the existing dict parser + (`tools/build/i18n_dict_utils.py`'s `parse_dict`/`parse_keep_en`) in a throwaway check, or a + direct grep for `= ""` cross-checked against each file's `__keep_en` block. This is a one-time + verification for this lot's PR, not new permanent test coverage. +- `generate_i18n_dicts.ps1`'s dry-run (already run by the existing `i18n-guard` CI job on the PR) + confirms no `MISSING` key was introduced by this lot — expected to report clean since no key is + added or removed, only stub values filled in. +- No `src/` *logic* changes, so `busted tests/ci/` and `luacheck` are unaffected — run as routine + CI, no new failures expected. + +## Out of Scope + +- **Any change to `translate_i18n.py`, `i18n_dict_utils.py`, `check_i18n_diff.py`, or the + `i18n-guard` CI job.** Already delivered and reviewed in `FIX-I18N-DICT-GUARD`. +- **Linguistic review of the generated KO/ES text.** No native-speaker review process exists for + this project; Claude Haiku via the existing tool is the accepted translation mechanism. +- **`CTLD_i18n_fr.lua`.** Already fully translated, nothing for this lot to do there. +- **Adding or removing a supported language.** fr/es/ko stays the complete non-EN set. +- **New automated test coverage.** No new logic is introduced; existing `tools/build/` tests + already cover the mechanism this lot exercises. + +## Further Notes + +- Full rationale for why this debt was deferred rather than blocked immediately: ADR 0013 + (`dev/adr/0013-ci-i18n-dict-guard.md`). +- Prior art: `BUILD-DICT-AI-TRANSLATE` (PR #60) introduced `translate_i18n.py` and the + local-only/`ANTHROPIC_API_KEY` decision this lot relies on without modification. +- `FIX-I18N-DICT-SYNC` (PR #57) is the precedent for a prior i18n debt-repayment pass (60+ FR menu + labels filled by hand after a path bug), confirming this project has repaid dictionary debt as + its own lot before. diff --git a/.backlog/FIX-I18N-DEBT-REPAYMENT/tickets/01-repay-ko-es-i18n-debt.md b/.backlog/FIX-I18N-DEBT-REPAYMENT/tickets/01-repay-ko-es-i18n-debt.md new file mode 100644 index 0000000..fcc4731 --- /dev/null +++ b/.backlog/FIX-I18N-DEBT-REPAYMENT/tickets/01-repay-ko-es-i18n-debt.md @@ -0,0 +1,46 @@ +Status: ready + +# 01 — Repay KO + ES i18n stub debt + +## Parent + +`.backlog/FIX-I18N-DEBT-REPAYMENT/PRD.md` + +## What to build + +Originally scoped to run `tools/build/translate_i18n.py` locally with `ANTHROPIC_API_KEY` set. No +key was available (separate Anthropic Console billing, not the Claude Code subscription used to +run this lot) — translations were produced and written directly into +`src/CTLD_i18n_ko.lua` / `src/CTLD_i18n_es.lua` instead, without invoking the script or any +external API. `translate_i18n.py` itself is untouched. + +Of the 93 KO / 78 ES stubs counted at `FIX-I18N-DICT-GUARD`'s merge (`cfb7cd6`), 23 / 8 turned out +to be `-- STALE:` in `CTLD_i18n_en.lua` (no longer referenced by any `ctld.tr()`/config-YAML call +in `src/` — the shared parser doesn't distinguish a live entry from a commented-out one, so they +still counted as raw stubs). Translating dead keys fixes no live menu entry, so they were excluded; +**70 live entries per language** (identical key set for both) were the actual scope. + +Two of those 70 keys (`JTAC`, `%1 [%2] %3.`) have no translatable content and would stay flagged +as stubs forever under the tool's own "value == EN text" definition — added to each dictionary's +`__keep_en` block instead, the existing mechanism for exactly this case. + +`CTLD_i18n_fr.lua` is already fully translated and was left untouched. + +## Acceptance criteria + +- [x] All 70 live (non-`STALE`) empty entries in `src/CTLD_i18n_ko.lua` translated. +- [x] All 70 live (non-`STALE`) empty entries in `src/CTLD_i18n_es.lua` translated. +- [x] `src/CTLD_i18n_ko.lua` has zero live entries equal to `""` outside its `__keep_en` block. +- [x] `src/CTLD_i18n_es.lua` has zero live entries equal to `""` outside its `__keep_en` block. +- [x] `src/CTLD_i18n_fr.lua` is unchanged (already fully translated). +- [x] No key added, removed, or renamed in any of the four dictionaries — only existing empty + values filled in, plus two `__keep_en` additions (`JTAC`, `%1 [%2] %3.`) in KO and ES. +- [x] `pytest tools/build/` still passes (no logic touched by this ticket) — 17/17 green. +- [x] `generate_i18n_dicts.ps1` dry-run reports no `MISSING` key (nothing added/removed). +- [x] `CHANGELOG.md` `[Unreleased]` updated noting the debt repayment. +- [ ] PR opened against `develop`; diff contains only translation-value changes in + `CTLD_i18n_ko.lua` / `CTLD_i18n_es.lua` (plus `CHANGELOG.md` and the backlog index line). + +## Blocked by + +None - can start immediately (`FIX-I18N-DICT-GUARD` already merged on `develop`) diff --git a/.backlog/README.md b/.backlog/README.md index d432297..12bcfae 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`](FIX-I18N-DEBT-REPAYMENT/PRD.md) | ready | 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` | | [`FIX-I18N-DICT-GUARD`](FIX-I18N-DICT-GUARD/PRD.md) | merged (PR #115) | Reported by **FullGas**: some F10 menu entries stay untranslated in Korean. Root cause: `translate_i18n.py`'s stub detection never matches the `""` empty-stub convention `generate_i18n_dicts.ps1 -Apply` actually writes, so freshly-added keys are never picked up for translation even with `ANTHROPIC_API_KEY` set — and the only existing guard (`.githooks/pre-push`, opt-in, `MISSING`-only) doesn't check translation content. New CI job `i18n-guard`, diff-scoped like `changelog-guard`: blocks unconditionally on `MISSING`, blocks by default (bypassable via `skip-i18n` label) on a newly-introduced empty non-EN entry. Fixes `translate_i18n.py`'s stub detection in the same lot. Pre-existing debt (91 KO + 76 ES empty entries) explicitly out of scope — a follow-up repayment lot must follow immediately. See **ADR 0013**. | `fix/i18n-dict-guard` | | [`FIX-FIELD-EXTRACT-CASUALTIES`](FIX-FIELD-EXTRACT-CASUALTIES/PRD.md) | merged (PR #111) | Field extraction (`embarkFromField`) returns the troop count frozen at deploy time instead of the survivor count — an undeclared legacy-parity deviation. Fix counts live DCS units (excluding `SVNT_*` servants) at extraction time; adds troop counts to the "Extract from field" menu labels; auto-despawns an orphaned mortar servant when its operator dies leaving zero real troops. Also fixed a pre-existing bug where `onUnitDead` never fired in-game. | `fix/field-extract-casualties` | | [`FIX-MENU-DOUBLE-MULTICREW`](FIX-MENU-DOUBLE-MULTICREW/PRD.md) | merged (PR #106) | F10 menu duplication on multi-crew aircraft (CH-47 pilot + copilot); menu loss when one crew member leaves a shared group. | — | diff --git a/CHANGELOG.md b/CHANGELOG.md index f7148ca..7bf5cc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ Versioning follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Fixed — pre-existing KO/ES i18n translation debt repaid (FIX-I18N-DEBT-REPAYMENT) + +- **`CTLD_i18n_ko.lua` and `CTLD_i18n_es.lua` had 93 and 78 empty entries** respectively on + `develop` — F10 menu entries silently falling back to English for Korean/Spanish Mission Makers. + Follow-up repayment lot promised by `FIX-I18N-DICT-GUARD` (ADR 0013). +- **70 live entries translated per language** (the same set for both — every key still referenced + by a current `ctld.tr()`/config-YAML call). The remaining 23/8 empty entries counted at + `FIX-I18N-DICT-GUARD`'s merge were `-- STALE:` in `CTLD_i18n_en.lua` (no longer referenced + anywhere in `src/`) — repaying dead keys would help no one, so they were left alone. +- **`JTAC` and `%1 [%2] %3.` added to each dictionary's `__keep_en` block**: a sigil and a + placeholder-only string respectively, neither translatable — the existing sanctioned mechanism + for "intentionally kept as English", already used for `CTLD`, `MLRS`, etc. +- No tooling changed: `translate_i18n.py`, `i18n_dict_utils.py`, `check_i18n_diff.py` and the + `i18n-guard` CI job are untouched. Translation content only. + ### Fixed — CI now catches untranslated i18n menu entries before merge (FIX-I18N-DICT-GUARD) - **`translate_i18n.py`'s stub detection was broken since it shipped**: it only recognised a diff --git a/src/CTLD_i18n_es.lua b/src/CTLD_i18n_es.lua index 3a7efbe..d7191f2 100644 --- a/src/CTLD_i18n_es.lua +++ b/src/CTLD_i18n_es.lua @@ -472,83 +472,85 @@ ctld.i18n["es"][" AIZ[%1] WARN '%2': all vehicleTypes entries are unknown in lo ctld.i18n["es"][" AIZ WARN: '%1' (P) overlaps '%2' (D) same coalition — risk of instant pickup+dropoff loop"] = " AIZ AVISO: '%1' (P) se superpone con '%2' (D) misma coalición — riesgo de bucle pickup+dropoff instantáneo" ctld.i18n["es"]["[CTLD] Zone validation — %1 error(s), %2 warning(s):"] = "[CTLD] Validación de zonas — %1 error(es), %2 aviso(s):" ctld.i18n["es"]["CTLDZoneManager: zone config valid"] = "CTLDZoneManager: configuración de zonas válida" -ctld.i18n["es"]["INIT-E: extractableGroup '%1' not found in mission — skipped"] = "" -ctld.i18n["es"]["[CTLD] Config errors detected — search CTLD_STARTUP_REPORT in DCS.log"] = "" +ctld.i18n["es"]["INIT-E: extractableGroup '%1' not found in mission — skipped"] = "INIT-E: extractableGroup '%1' no encontrado en la misión — omitido" +ctld.i18n["es"]["[CTLD] Config errors detected — search CTLD_STARTUP_REPORT in DCS.log"] = "[CTLD] Errores de configuración detectados — buscar CTLD_STARTUP_REPORT en DCS.log" --- Keys added by generate_i18n_dicts.ps1 on 2026-07-22 -ctld.i18n["es"]["--- Countryside FARP Deployment by %1 : Complete! ---"] = "" -ctld.i18n["es"]["--- FARP Dynamic Deployment by %1 : Complete! ---"] = "" -ctld.i18n["es"]["--- mineField Deployed by %1 ---"] = "" -ctld.i18n["es"]["%1 [%2] %3."] = "" -ctld.i18n["es"]["%1 crate dropped below you."] = "" -ctld.i18n["es"]["%1 crate safely released."] = "" -ctld.i18n["es"]["%1 packed into %2 crate(s)."] = "" -ctld.i18n["es"]["[activate]"] = "" -ctld.i18n["es"]["[deactivate]"] = "" -ctld.i18n["es"]["Cargo: [%1] — %2 troops, %3 kg"] = "" -ctld.i18n["es"]["Clear Mine Field (%1 mines, ~%2 m)"] = "" -ctld.i18n["es"]["Crate Commands"] = "" -ctld.i18n["es"]["Cut Slingload"] = "" -ctld.i18n["es"]["Extracted [%1] (%2 troops)."] = "" -ctld.i18n["es"]["FOB 90% complete - final installation in progress..."] = "" -ctld.i18n["es"]["FOB construction started by %1."] = "" -ctld.i18n["es"]["FOB established by %1 - logistics hub now active."] = "" -ctld.i18n["es"]["Group too large for this aircraft (%1/%2 troops)."] = "" -ctld.i18n["es"]["Hovering above %s crate.\n\nHold hover for %d seconds!\n\nIf the countdown stops you're too far away!"] = "" -ctld.i18n["es"]["Infantry coalition limit reached, cannot load more troops."] = "" -ctld.i18n["es"]["into %1"] = "" -ctld.i18n["es"]["into combat"] = "" -ctld.i18n["es"]["JTAC"] = "" -ctld.i18n["es"]["JTAC Group 2"] = "" -ctld.i18n["es"]["Land near troops to extract them (%1m away)."] = "" -ctld.i18n["es"]["Load from %1"] = "" -ctld.i18n["es"]["Loaded: %1 (%2 troops)."] = "" -ctld.i18n["es"]["Mine Field"] = "" -ctld.i18n["es"]["Mine Field cleared by %1."] = "" -ctld.i18n["es"]["No active JTACs."] = "" -ctld.i18n["es"]["No crates loaded."] = "" -ctld.i18n["es"]["No slingloaded crate on board."] = "" -ctld.i18n["es"]["no target"] = "" -ctld.i18n["es"]["No troops onboard and no extractable troops nearby."] = "" -ctld.i18n["es"]["Parachute Crates"] = "" -ctld.i18n["es"]["Parachute Troops"] = "" -ctld.i18n["es"]["Parachute Vehicle"] = "" -ctld.i18n["es"]["RECON is disabled (set reconF10Menu=true in config)."] = "" -ctld.i18n["es"]["Release Slingload"] = "" -ctld.i18n["es"]["Request 9-Line"] = "" -ctld.i18n["es"]["Request Equipment"] = "" -ctld.i18n["es"]["Request Smoke on Target"] = "" -ctld.i18n["es"]["Slingloaded %1 crate!"] = "" -ctld.i18n["es"]["Smoke"] = "" -ctld.i18n["es"]["This pickup zone is empty."] = "" -ctld.i18n["es"]["This pickup zone is not active."] = "" -ctld.i18n["es"]["This pickup zone is not available to your coalition."] = "" -ctld.i18n["es"]["Too fast! Slingloaded crate lost: %1"] = "" +ctld.i18n["es"]["--- Countryside FARP Deployment by %1 : Complete! ---"] = "--- Despliegue de FARP rural por %1 : ¡Completado! ---" +ctld.i18n["es"]["--- FARP Dynamic Deployment by %1 : Complete! ---"] = "--- Despliegue dinámico de FARP por %1 : ¡Completado! ---" +ctld.i18n["es"]["--- mineField Deployed by %1 ---"] = "--- Campo minado desplegado por %1 ---" +ctld.i18n["es"]["%1 [%2] %3."] = "%1 [%2] %3." +ctld.i18n["es"]["%1 crate dropped below you."] = "La caja %1 ha caído debajo de ti." +ctld.i18n["es"]["%1 crate safely released."] = "La caja %1 se ha soltado de forma segura." +ctld.i18n["es"]["%1 packed into %2 crate(s)."] = "%1 empaquetado en %2 caja(s)." +ctld.i18n["es"]["[activate]"] = "[activar]" +ctld.i18n["es"]["[deactivate]"] = "[desactivar]" +ctld.i18n["es"]["Cargo: [%1] — %2 troops, %3 kg"] = "Carga: [%1] — %2 tropas, %3 kg" +ctld.i18n["es"]["Clear Mine Field (%1 mines, ~%2 m)"] = "Despejar campo minado (%1 minas, ~%2 m)" +ctld.i18n["es"]["Crate Commands"] = "Comandos de cajas" +ctld.i18n["es"]["Cut Slingload"] = "Cortar eslinga" +ctld.i18n["es"]["Extracted [%1] (%2 troops)."] = "Extraído [%1] (%2 tropas)." +ctld.i18n["es"]["FOB 90% complete - final installation in progress..."] = "FOB al 90% completado - instalación final en curso..." +ctld.i18n["es"]["FOB construction started by %1."] = "%1 inició la construcción de un FOB." +ctld.i18n["es"]["FOB established by %1 - logistics hub now active."] = "FOB establecido por %1 - centro logístico activo." +ctld.i18n["es"]["Group too large for this aircraft (%1/%2 troops)."] = "El grupo es demasiado grande para esta aeronave (%1/%2 tropas)." +ctld.i18n["es"]["Hovering above %s crate.\n\nHold hover for %d seconds!\n\nIf the countdown stops you're too far away!"] = "En estacionario sobre la caja %s.\n\n¡Mantén el estacionario durante %d segundos!\n\n¡Si la cuenta atrás se detiene, estás demasiado lejos!" +ctld.i18n["es"]["Infantry coalition limit reached, cannot load more troops."] = "Límite de infantería de la coalición alcanzado, no se pueden cargar más tropas." +ctld.i18n["es"]["into %1"] = "en %1" +ctld.i18n["es"]["into combat"] = "al combate" +ctld.i18n["es"]["JTAC"] = "JTAC" +ctld.i18n["es"]["JTAC Group 2"] = "Grupo JTAC 2" +ctld.i18n["es"]["Land near troops to extract them (%1m away)."] = "Aterriza cerca de las tropas para extraerlas (a %1m)." +ctld.i18n["es"]["Load from %1"] = "Cargar desde %1" +ctld.i18n["es"]["Loaded: %1 (%2 troops)."] = "Cargado: %1 (%2 tropas)." +ctld.i18n["es"]["Mine Field"] = "Campo minado" +ctld.i18n["es"]["Mine Field cleared by %1."] = "Campo minado despejado por %1." +ctld.i18n["es"]["No active JTACs."] = "Sin JTAC activos." +ctld.i18n["es"]["No crates loaded."] = "No hay cajas cargadas." +ctld.i18n["es"]["No slingloaded crate on board."] = "No hay carga en eslinga a bordo." +ctld.i18n["es"]["no target"] = "sin objetivo" +ctld.i18n["es"]["No troops onboard and no extractable troops nearby."] = "Sin tropas a bordo y sin tropas extraíbles cerca." +ctld.i18n["es"]["Parachute Crates"] = "Lanzar cajas en paracaídas" +ctld.i18n["es"]["Parachute Troops"] = "Lanzar tropas en paracaídas" +ctld.i18n["es"]["Parachute Vehicle"] = "Lanzar vehículo en paracaídas" +ctld.i18n["es"]["RECON is disabled (set reconF10Menu=true in config)."] = "RECON está desactivado (establece reconF10Menu=true en la configuración)." +ctld.i18n["es"]["Release Slingload"] = "Soltar eslinga" +ctld.i18n["es"]["Request 9-Line"] = "Solicitar 9-Line" +ctld.i18n["es"]["Request Equipment"] = "Solicitar equipo" +ctld.i18n["es"]["Request Smoke on Target"] = "Solicitar humo sobre el objetivo" +ctld.i18n["es"]["Slingloaded %1 crate!"] = "¡Caja %1 enganchada en eslinga!" +ctld.i18n["es"]["Smoke"] = "Humo" +ctld.i18n["es"]["This pickup zone is empty."] = "Esta zona de recogida está vacía." +ctld.i18n["es"]["This pickup zone is not active."] = "Esta zona de recogida no está activa." +ctld.i18n["es"]["This pickup zone is not available to your coalition."] = "Esta zona de recogida no está disponible para tu coalición." +ctld.i18n["es"]["Too fast! Slingloaded crate lost: %1"] = "¡Demasiado rápido! Caja en eslinga perdida: %1" -- STALE: ctld.i18n["es"]["Too high or too fast to drop troops! Hover below %1 ft or land."] = "" -ctld.i18n["es"]["Too high to fast-rope! Descend below %1 ft."] = "" -ctld.i18n["es"]["Too fast to fast-rope! Slow down and hover."] = "" -ctld.i18n["es"]["Too high to hook crate.\n\nHold hover for %1 seconds"] = "" -ctld.i18n["es"]["Too high to release slingload. Descend below %1m AGL (current: %2m AGL)."] = "" -ctld.i18n["es"]["Too high! %1 crate destroyed on impact."] = "" -ctld.i18n["es"]["Too low to hook crate.\n\nHold hover for %1 seconds"] = "" -ctld.i18n["es"]["Troop Commands"] = "" -ctld.i18n["es"]["Troops returned to base."] = "" -ctld.i18n["es"]["Vehicle Commands"] = "" -ctld.i18n["es"]["You must land inside the pickup zone to load troops."] = "" -ctld.i18n["es"]["You must land to extract troops."] = "" -ctld.i18n["es"]["Zone not found."] = "" +ctld.i18n["es"]["Too high to fast-rope! Descend below %1 ft."] = "¡Demasiado alto para descolgarse con cuerda rápida! Desciende por debajo de %1 pies." +ctld.i18n["es"]["Too fast to fast-rope! Slow down and hover."] = "¡Demasiado rápido para descolgarse con cuerda rápida! Reduce la velocidad y mantente en estacionario." +ctld.i18n["es"]["Too high to hook crate.\n\nHold hover for %1 seconds"] = "Demasiado alto para enganchar la caja.\n\nMantén el estacionario durante %1 segundos" +ctld.i18n["es"]["Too high to release slingload. Descend below %1m AGL (current: %2m AGL)."] = "Demasiado alto para soltar la eslinga. Desciende por debajo de %1m AGL (actual: %2m AGL)." +ctld.i18n["es"]["Too high! %1 crate destroyed on impact."] = "¡Demasiado alto! La caja %1 se destruyó al impactar." +ctld.i18n["es"]["Too low to hook crate.\n\nHold hover for %1 seconds"] = "Demasiado bajo para enganchar la caja.\n\nMantén el estacionario durante %1 segundos" +ctld.i18n["es"]["Troop Commands"] = "Comandos de tropas" +ctld.i18n["es"]["Troops returned to base."] = "Tropas devueltas a la base." +ctld.i18n["es"]["Vehicle Commands"] = "Comandos de vehículos" +ctld.i18n["es"]["You must land inside the pickup zone to load troops."] = "Debes aterrizar dentro de la zona de recogida para cargar tropas." +ctld.i18n["es"]["You must land to extract troops."] = "Debes aterrizar para extraer tropas." +ctld.i18n["es"]["Zone not found."] = "Zona no encontrada." -- Keys intentionally kept in English: DCS equipment designations, military acronyms, proper nouns. ctld.i18n["es"].__keep_en = { + ["%1 [%2] %3."] = true, ["%1\nFOB @ %2"] = true, ["CTLD"] = true, + ["JTAC"] = true, } --- Keys added by generate_i18n_dicts.ps1 on 2026-07-22 -ctld.i18n["es"]["%1 successfully deployed a full %2 in the field.\n\nAA Active System limit: %3\nActive: %4"] = "" -ctld.i18n["es"]["AI deployed a full %1.\n\nAA Active System limit: %2\nActive: %3"] = "" -ctld.i18n["es"]["Cannot deploy %1: AA system limit reached (%2/%3)"] = "" -ctld.i18n["es"]["Cannot repair %1. No damaged %1 within %2m"] = "" +ctld.i18n["es"]["%1 successfully deployed a full %2 in the field.\n\nAA Active System limit: %3\nActive: %4"] = "%1 desplegó con éxito un %2 completo en el campo.\n\nLímite de sistemas AA activos: %3\nActivos: %4" +ctld.i18n["es"]["AI deployed a full %1.\n\nAA Active System limit: %2\nActive: %3"] = "La IA desplegó un %1 completo.\n\nLímite de sistemas AA activos: %2\nActivos: %3" +ctld.i18n["es"]["Cannot deploy %1: AA system limit reached (%2/%3)"] = "No se puede desplegar %1: límite de sistemas AA alcanzado (%2/%3)" +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)"] = "" @@ -560,18 +562,18 @@ ctld.i18n["es"]["Infantry"] = "" ctld.i18n["es"]["Ships"] = "" --- 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"] = "" +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" --- Keys added by generate_i18n_dicts.ps1 on 2026-07-30 --- Keys added by generate_i18n_dicts.ps1 on 2026-07-30 -ctld.i18n["es"]["specificParams is ignored on crates (%1) — drone orbit altitude, radii and speed now come from the JTAC_drone* settings"] = "" +ctld.i18n["es"]["specificParams is ignored on crates (%1) — drone orbit altitude, radii and speed now come from the JTAC_drone* settings"] = "specificParams se ignora en las cajas (%1) — la altitud de órbita, los radios y la velocidad del dron ahora provienen de los ajustes JTAC_drone*" --- Keys added by generate_i18n_dicts.ps1 on 2026-08-01 -ctld.i18n["es"]["dropOffZones is not read by CTLD 2 — declare each AI drop-off point as an aiZones entry with isDropoff: true"] = "" +ctld.i18n["es"]["dropOffZones is not read by CTLD 2 — declare each AI drop-off point as an aiZones entry with isDropoff: true"] = "dropOffZones no es leído por CTLD 2 — declara cada punto de descarga de IA como una entrada aiZones con isDropoff: true" --- Keys added by generate_i18n_dicts.ps1 on 2026-08-01 -ctld.i18n["es"][" AIZ[%1] ERROR '%2': name already taken by zone '%3' — entry ignored"] = "" +ctld.i18n["es"][" AIZ[%1] ERROR '%2': name already taken by zone '%3' — entry ignored"] = " AIZ[%1] ERROR '%2': nombre ya utilizado por la zona '%3' — entrada ignorada" --- Keys added by generate_i18n_dicts.ps1 on 2026-08-09 ctld.i18n["es"]["%1 (%2 troops, %3m)"] = "%1 (%2 tropas, %3 m)" diff --git a/src/CTLD_i18n_ko.lua b/src/CTLD_i18n_ko.lua index 1fade20..68462fe 100644 --- a/src/CTLD_i18n_ko.lua +++ b/src/CTLD_i18n_ko.lua @@ -322,102 +322,104 @@ ctld.i18n["ko"][" AIZ[%1] WARN '%2': all vehicleTypes entries are unknown in lo ctld.i18n["ko"][" AIZ WARN: '%1' (P) overlaps '%2' (D) same coalition — risk of instant pickup+dropoff loop"] = " AIZ 경고: '%1' (P)이(가) '%2' (D)와 동일 연합에서 겹침 — 즉시 pickup+dropoff 루프 위험" ctld.i18n["ko"]["[CTLD] Zone validation — %1 error(s), %2 warning(s):"] = "[CTLD] 구역 유효성 검사 — 오류 %1개, 경고 %2개:" ctld.i18n["ko"]["CTLDZoneManager: zone config valid"] = "CTLDZoneManager: 구역 구성이 유효합니다" -ctld.i18n["ko"]["INIT-E: extractableGroup '%1' not found in mission — skipped"] = "" -ctld.i18n["ko"]["[CTLD] Config errors detected — search CTLD_STARTUP_REPORT in DCS.log"] = "" +ctld.i18n["ko"]["INIT-E: extractableGroup '%1' not found in mission — skipped"] = "INIT-E: extractableGroup '%1'을(를) 임무에서 찾을 수 없음 — 건너뜀" +ctld.i18n["ko"]["[CTLD] Config errors detected — search CTLD_STARTUP_REPORT in DCS.log"] = "[CTLD] 설정 오류 발견 — DCS.log에서 CTLD_STARTUP_REPORT 검색" --- Keys added by generate_i18n_dicts.ps1 on 2026-07-22 -ctld.i18n["ko"]["--- Countryside FARP Deployment by %1 : Complete! ---"] = "" -ctld.i18n["ko"]["--- FARP Dynamic Deployment by %1 : Complete! ---"] = "" -ctld.i18n["ko"]["--- mineField Deployed by %1 ---"] = "" -ctld.i18n["ko"]["%1 [%2] %3."] = "" -ctld.i18n["ko"]["%1 crate dropped below you."] = "" -ctld.i18n["ko"]["%1 crate safely released."] = "" -ctld.i18n["ko"]["%1 packed into %2 crate(s)."] = "" -ctld.i18n["ko"]["[activate]"] = "" -ctld.i18n["ko"]["[deactivate]"] = "" +ctld.i18n["ko"]["--- Countryside FARP Deployment by %1 : Complete! ---"] = "--- %1의 야외 FARP 배치 : 완료! ---" +ctld.i18n["ko"]["--- FARP Dynamic Deployment by %1 : Complete! ---"] = "--- %1의 동적 FARP 배치 : 완료! ---" +ctld.i18n["ko"]["--- mineField Deployed by %1 ---"] = "--- %1이(가) 배치한 지뢰밭 ---" +ctld.i18n["ko"]["%1 [%2] %3."] = "%1 [%2] %3." +ctld.i18n["ko"]["%1 crate dropped below you."] = "%1 화물이 당신 아래로 투하되었습니다." +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"]["Cargo: [%1] — %2 troops, %3 kg"] = "" -ctld.i18n["ko"]["Clear Mine Field (%1 mines, ~%2 m)"] = "" -ctld.i18n["ko"]["Crate Commands"] = "" -ctld.i18n["ko"]["Cut Slingload"] = "" +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"]["Extracted [%1] (%2 troops)."] = "" -ctld.i18n["ko"]["FOB 90% complete - final installation in progress..."] = "" -ctld.i18n["ko"]["FOB construction started by %1."] = "" -ctld.i18n["ko"]["FOB established by %1 - logistics hub now active."] = "" -ctld.i18n["ko"]["Group too large for this aircraft (%1/%2 troops)."] = "" -ctld.i18n["ko"]["Hovering above %s crate.\n\nHold hover for %d seconds!\n\nIf the countdown stops you're too far away!"] = "" -ctld.i18n["ko"]["Infantry coalition limit reached, cannot load more troops."] = "" -ctld.i18n["ko"]["into %1"] = "" -ctld.i18n["ko"]["into combat"] = "" -ctld.i18n["ko"]["JTAC"] = "" -ctld.i18n["ko"]["JTAC Group 2"] = "" +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 건설을 시작했습니다." +ctld.i18n["ko"]["FOB established by %1 - logistics hub now active."] = "%1이(가) FOB를 설립했습니다 - 군수 거점 활성화됨." +ctld.i18n["ko"]["Group too large for this aircraft (%1/%2 troops)."] = "그룹이 이 항공기에 비해 너무 큽니다 (%1/%2명)." +ctld.i18n["ko"]["Hovering above %s crate.\n\nHold hover for %d seconds!\n\nIf the countdown stops you're too far away!"] = "%s 화물 위에서 정지 비행 중.\n\n%d초 동안 정지 비행을 유지하세요!\n\n카운트다운이 멈추면 너무 멀리 떨어진 것입니다!" +ctld.i18n["ko"]["Infantry coalition limit reached, cannot load more troops."] = "보병 연합 한도에 도달하여 더 이상 병력을 태울 수 없습니다." +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"]["Land near troops to extract them (%1m away)."] = "" +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 from %1"] = "" -ctld.i18n["ko"]["Loaded: %1 (%2 troops)."] = "" -ctld.i18n["ko"]["Mine Field"] = "" -ctld.i18n["ko"]["Mine Field cleared by %1."] = "" +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"]["No active JTACs."] = "" -ctld.i18n["ko"]["No crates loaded."] = "" +ctld.i18n["ko"]["No active JTACs."] = "활성화된 JTAC 없음." +ctld.i18n["ko"]["No crates loaded."] = "적재된 화물 없음." 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"]["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"]["Parachute Crates"] = "" -ctld.i18n["ko"]["Parachute Troops"] = "" -ctld.i18n["ko"]["Parachute Vehicle"] = "" -ctld.i18n["ko"]["RECON is disabled (set reconF10Menu=true in config)."] = "" -ctld.i18n["ko"]["Release Slingload"] = "" -ctld.i18n["ko"]["Request 9-Line"] = "" -ctld.i18n["ko"]["Request Equipment"] = "" -ctld.i18n["ko"]["Request Smoke on Target"] = "" -ctld.i18n["ko"]["Slingloaded %1 crate!"] = "" -ctld.i18n["ko"]["Smoke"] = "" -ctld.i18n["ko"]["This pickup zone is empty."] = "" -ctld.i18n["ko"]["This pickup zone is not active."] = "" -ctld.i18n["ko"]["This pickup zone is not available to your coalition."] = "" -ctld.i18n["ko"]["Too fast! Slingloaded crate lost: %1"] = "" +ctld.i18n["ko"]["Parachute Crates"] = "화물 낙하산 투하" +ctld.i18n["ko"]["Parachute Troops"] = "병력 낙하산 투하" +ctld.i18n["ko"]["Parachute Vehicle"] = "차량 낙하산 투하" +ctld.i18n["ko"]["RECON is disabled (set reconF10Menu=true in config)."] = "정찰이 비활성화되어 있습니다 (설정에서 reconF10Menu=true로 지정하세요)." +ctld.i18n["ko"]["Release Slingload"] = "슬링로드 해제" +ctld.i18n["ko"]["Request 9-Line"] = "9-Line 요청" +ctld.i18n["ko"]["Request Equipment"] = "장비 요청" +ctld.i18n["ko"]["Request Smoke on Target"] = "목표에 연막 요청" +ctld.i18n["ko"]["Slingloaded %1 crate!"] = "%1 화물을 슬링로드했습니다!" +ctld.i18n["ko"]["Smoke"] = "연막" +ctld.i18n["ko"]["This pickup zone is empty."] = "이 픽업 구역은 비어 있습니다." +ctld.i18n["ko"]["This pickup zone is not active."] = "이 픽업 구역은 활성화되어 있지 않습니다." +ctld.i18n["ko"]["This pickup zone is not available to your coalition."] = "이 픽업 구역은 당신의 연합이 사용할 수 없습니다." +ctld.i18n["ko"]["Too fast! Slingloaded crate lost: %1"] = "너무 빠릅니다! 슬링로드 화물 손실: %1" -- STALE: ctld.i18n["ko"]["Too high or too fast to drop troops! Hover below %1 ft or land."] = "" -ctld.i18n["ko"]["Too high to fast-rope! Descend below %1 ft."] = "" -ctld.i18n["ko"]["Too fast to fast-rope! Slow down and hover."] = "" -ctld.i18n["ko"]["Too high to hook crate.\n\nHold hover for %1 seconds"] = "" -ctld.i18n["ko"]["Too high to release slingload. Descend below %1m AGL (current: %2m AGL)."] = "" -ctld.i18n["ko"]["Too high! %1 crate destroyed on impact."] = "" -ctld.i18n["ko"]["Too low to hook crate.\n\nHold hover for %1 seconds"] = "" -ctld.i18n["ko"]["Troop Commands"] = "" -ctld.i18n["ko"]["Troops returned to base."] = "" +ctld.i18n["ko"]["Too high to fast-rope! Descend below %1 ft."] = "너무 높아서 패스트로프를 할 수 없습니다! %1피트 이하로 하강하세요." +ctld.i18n["ko"]["Too fast to fast-rope! Slow down and hover."] = "너무 빨라서 패스트로프를 할 수 없습니다! 속도를 줄이고 정지비행하세요." +ctld.i18n["ko"]["Too high to hook crate.\n\nHold hover for %1 seconds"] = "너무 높아서 화물을 걸 수 없습니다.\n\n%1초 동안 정지비행을 유지하세요" +ctld.i18n["ko"]["Too high to release slingload. Descend below %1m AGL (current: %2m AGL)."] = "너무 높아서 슬링로드를 해제할 수 없습니다. %1m AGL 이하로 하강하세요 (현재: %2m AGL)." +ctld.i18n["ko"]["Too high! %1 crate destroyed on impact."] = "너무 높습니다! %1 화물이 충돌로 파괴되었습니다." +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"]["Vehicle Commands"] = "" -ctld.i18n["ko"]["You must land inside the pickup zone to load troops."] = "" -ctld.i18n["ko"]["You must land to extract troops."] = "" -ctld.i18n["ko"]["Zone not found."] = "" +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."] = "병력을 추출하려면 착륙해야 합니다." +ctld.i18n["ko"]["Zone not found."] = "구역을 찾을 수 없습니다." -- Keys intentionally kept in English: DCS equipment designations, military acronyms, proper nouns. ctld.i18n["ko"].__keep_en = { + ["%1 [%2] %3."] = true, ["BRDM-2"] = true, ["BTR-D"] = true, ["CTLD"] = true, + ["JTAC"] = true, ["LPWS C-RAM"] = true, ["MLRS"] = true, ["SKP-11 - JTAC"] = true, } --- 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"] = "" +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"]["AI deployed a full %1.\n\nAA Active System limit: %2\nActive: %3"] = "" -ctld.i18n["ko"]["Cannot deploy %1: AA system limit reached (%2/%3)"] = "" -ctld.i18n["ko"]["Cannot repair %1. No damaged %1 within %2m"] = "" +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)"] = "" @@ -429,18 +431,18 @@ 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"] = "" +ctld.i18n["ko"]["%1 setting(s) absent from the mission config — CTLD default used: %2"] = "미션 설정에 %1 항목이 없음 — CTLD 기본값 사용: %2" --- Keys added by generate_i18n_dicts.ps1 on 2026-07-30 --- Keys added by generate_i18n_dicts.ps1 on 2026-07-30 -ctld.i18n["ko"]["specificParams is ignored on crates (%1) — drone orbit altitude, radii and speed now come from the JTAC_drone* settings"] = "" +ctld.i18n["ko"]["specificParams is ignored on crates (%1) — drone orbit altitude, radii and speed now come from the JTAC_drone* settings"] = "specificParams는 화물(%1)에서 무시됩니다 — 드론 궤도 고도, 반경, 속도는 이제 JTAC_drone* 설정에서 가져옵니다" --- Keys added by generate_i18n_dicts.ps1 on 2026-08-01 -ctld.i18n["ko"]["dropOffZones is not read by CTLD 2 — declare each AI drop-off point as an aiZones entry with isDropoff: true"] = "" +ctld.i18n["ko"]["dropOffZones is not read by CTLD 2 — declare each AI drop-off point as an aiZones entry with isDropoff: true"] = "dropOffZones는 CTLD 2에서 읽지 않습니다 — 각 AI 하차 지점을 isDropoff: true로 aiZones 항목으로 선언하세요" --- Keys added by generate_i18n_dicts.ps1 on 2026-08-01 -ctld.i18n["ko"][" AIZ[%1] ERROR '%2': name already taken by zone '%3' — entry ignored"] = "" +ctld.i18n["ko"][" AIZ[%1] ERROR '%2': name already taken by zone '%3' — entry ignored"] = " AIZ[%1] 오류 '%2': 이름이 이미 구역 '%3'에서 사용 중 — 항목 무시됨" --- Keys added by generate_i18n_dicts.ps1 on 2026-08-09 ctld.i18n["ko"]["%1 (%2 troops, %3m)"] = "%1 (%2명, %3 m)" From c6fd94e5912684899073408f93178dc6c1abf53a Mon Sep 17 00:00:00 2001 From: FullGas1 <51051389+FullGas1@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:53:32 +0200 Subject: [PATCH 2/2] chore(backlog): mark FIX-I18N-DEBT-REPAYMENT as merged (PR #116) --- .backlog/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.backlog/README.md b/.backlog/README.md index 12bcfae..dfbd31a 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`](FIX-I18N-DEBT-REPAYMENT/PRD.md) | ready | 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` | +| [`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` | | [`FIX-I18N-DICT-GUARD`](FIX-I18N-DICT-GUARD/PRD.md) | merged (PR #115) | Reported by **FullGas**: some F10 menu entries stay untranslated in Korean. Root cause: `translate_i18n.py`'s stub detection never matches the `""` empty-stub convention `generate_i18n_dicts.ps1 -Apply` actually writes, so freshly-added keys are never picked up for translation even with `ANTHROPIC_API_KEY` set — and the only existing guard (`.githooks/pre-push`, opt-in, `MISSING`-only) doesn't check translation content. New CI job `i18n-guard`, diff-scoped like `changelog-guard`: blocks unconditionally on `MISSING`, blocks by default (bypassable via `skip-i18n` label) on a newly-introduced empty non-EN entry. Fixes `translate_i18n.py`'s stub detection in the same lot. Pre-existing debt (91 KO + 76 ES empty entries) explicitly out of scope — a follow-up repayment lot must follow immediately. See **ADR 0013**. | `fix/i18n-dict-guard` | | [`FIX-FIELD-EXTRACT-CASUALTIES`](FIX-FIELD-EXTRACT-CASUALTIES/PRD.md) | merged (PR #111) | Field extraction (`embarkFromField`) returns the troop count frozen at deploy time instead of the survivor count — an undeclared legacy-parity deviation. Fix counts live DCS units (excluding `SVNT_*` servants) at extraction time; adds troop counts to the "Extract from field" menu labels; auto-despawns an orphaned mortar servant when its operator dies leaving zero real troops. Also fixed a pre-existing bug where `onUnitDead` never fired in-game. | `fix/field-extract-casualties` | | [`FIX-MENU-DOUBLE-MULTICREW`](FIX-MENU-DOUBLE-MULTICREW/PRD.md) | merged (PR #106) | F10 menu duplication on multi-crew aircraft (CH-47 pilot + copilot); menu loss when one crew member leaves a shared group. | — |