Aller au contenu

Internationalisation (i18n)

Chaque chaîne présentée à l'utilisateur dans CTLD est traduisible. Le code source n'émet jamais de texte brut vers les joueurs — il appelle ctld.tr("English text"), et la couche i18n résout cet appel vers la langue active à l'exécution. CTLD fournit quatre dictionnaires : anglais (référence), français, espagnol et coréen.

Le sous-système i18n est délibérément scindé entre logique et données. src/CTLD_i18n.lua contient le singleton CTLDi18n, la fonction ctld.tr() et les fonctions utilitaires d'audit — aucune chaîne. Chaque langue vit dans son propre fichier dictionnaire CTLD_i18n_XX.lua, maintenu indépendamment (souvent par différents traducteurs). Un outil PowerShell maintient ces dictionnaires synchronisés avec les clés réellement utilisées dans le source.

Les quatre dictionnaires

Fichier Langue Rôle
CTLD_i18n.lua Logique de classe uniquement : singleton CTLDi18n, ctld.tr(), utilitaires d'audit. Aucune donnée de dictionnaire.
CTLD_i18n_en.lua Anglais Dictionnaire de référence. Chaque valeur est égale à sa clé. Jamais vide.
CTLD_i18n_fr.lua Français Valeur vide "" = non traduit (repli sur l'EN).
CTLD_i18n_es.lua Espagnol Même règle.
CTLD_i18n_ko.lua Coréen Même règle.

Les cinq fichiers sont listés — dans cet ordre — dans tools/build/listToMerge.txt, de sorte que CTLD_i18n.lua se charge avant tout dictionnaire et que le dictionnaire de référence en se charge en premier.

La langue active est sélectionnée en haut de src/CTLD_i18n.lua :

ctld.i18n_lang = "en"
--ctld.i18n_lang = "fr"
--ctld.i18n_lang = "es"
--ctld.i18n_lang = "ko"

Le contrat de ctld.tr()

ctld.tr() traduit une clé vers la langue active et substitue les éventuels paramètres positionnels.

ctld.tr("Troops loaded")
ctld.tr("Sorry you must wait %1 seconds before you can get another crate", waitTime)
ctld.tr("%1 loaded %2 vehicles into %3", pilotName, count, zoneName)

Règle : la clé passée à ctld.tr() est TOUJOURS un littéral de chaîne complet. Les parties variables sont exprimées avec des marqueurs %1, %2, … à l'intérieur du littéral ; les valeurs dynamiques sont passées en arguments séparés. Ne jamais construire une clé par concaténation :

-- FORBIDDEN — breaks the static scan, keys go silently missing from dicts
ctld.tr("prefix_" .. suffix)
ctld.tr(someVariable)

Cela a son importance parce que generate_i18n_dicts.ps1 extrait les clés avec une expression régulière. Si chaque clé est un littéral, le scan est fiable à 100 % ; la concaténation crée des angles morts.

ctld.i18n_translate est un alias de compatibilité ascendante pour ctld.tr — le nouveau code utilise ctld.tr.

Chaîne de repli

ctld.tr() ne retourne jamais nil ni une chaîne vide. Elle résout dans cet ordre :

  1. Le dictionnaire de la langue active (ctld.i18n[ctld.i18n_lang]).
  2. Le dictionnaire anglais.
  3. La clé elle-même (qui est le texte anglais).

Une langue active inconnue journalise un WARN et se replie sur en.

Versionnage des dictionnaires

Chaque dictionnaire porte sa propre translation_version, une chaîne "MAJOR.MINOR" :

ctld.i18n["en"].translation_version = "1.8"

Les versions sont indépendantes par dictionnaire — les dictionnaires sont maintenus séparément, donc chacun n'est incrémenté que lorsque lui-même change, pas lorsqu'un dictionnaire frère change. ctld.i18n_check() et ctld.i18n_audit() signalent un écart entre l'EN et une autre langue comme un indice que l'autre dictionnaire est peut-être en retard sur l'EN et nécessite une revue. Un écart est un avertissement, pas une erreur : la chaîne de repli résout toujours chaque clé.

Maintenir les dictionnaires synchronisés — generate_i18n_dicts.ps1

tools/build/generate_i18n_dicts.ps1 scanne chaque fichier src/*.lua (à l'exclusion des dictionnaires eux-mêmes) à la recherche des clés ctld.tr(), puis réconcilie chaque dictionnaire avec cet ensemble. Exécutez-le après tout ajout ou suppression de ctld.tr() dans le source.

# Audit only — reports what would change, writes nothing (default)
powershell -ExecutionPolicy Bypass -File tools\build\generate_i18n_dicts.ps1

# Apply — add missing keys, mark stale ones, bump versions
powershell -ExecutionPolicy Bypass -File tools\build\generate_i18n_dicts.ps1 -Apply

Par dictionnaire, -Apply effectue :

Situation Action
Clé utilisée dans le source, absente du dictionnaire Ajoutée en fin de fichier. L'EN reçoit value = key ; les autres langues reçoivent value = "" (non traduit).
Clé dans le dictionnaire, plus utilisée dans le source Sa ligne est préfixée par -- STALE: — jamais supprimée, afin qu'un humain confirme avant de l'enlever.
Toute modification apportée au dictionnaire La translation_version propre à ce dictionnaire est incrémentée (composante patch +1).

L'outil réécrit uniquement les fichiers dictionnaires — il ne régénère aucun loader et ne touche pas à listToMerge.txt. L'ordre de fusion qui assemble CTLD.lua est entièrement piloté par listToMerge.txt (voir Architecture).

Ajouter une clé de traduction

  1. Appelez ctld.tr("My new text") dans le module source (clé littérale, marqueurs %1… pour les variables).
  2. Exécutez generate_i18n_dicts.ps1 -Apply. La nouvelle clé est ajoutée à l'ensemble des quatre dictionnaires — avec le texte anglais comme valeur EN et "" dans les autres — et la version de chaque dictionnaire modifié est incrémentée.
  3. Les traducteurs remplissent les valeurs vides dans CTLD_i18n_fr/es/ko.lua.
  4. Reconstruisez CTLD.lua (tools\build\merge_CTLD.ps1) et ajoutez une couverture busted pour la nouvelle logique.

Ajouter une langue

Pour ajouter une cinquième langue XX :

  1. Copiez src/CTLD_i18n_en.lua vers src/CTLD_i18n_XX.lua, remplacez chaque ctld.i18n["en"] par ctld.i18n["XX"], et traduisez les valeurs (gardez les clés identiques à l'EN).
  2. Enregistrez le fichier dans tools/build/listToMerge.txt, après les dictionnaires existants.
  3. Enregistrez la langue dans la table ordonnée $DictFiles de tools/build/generate_i18n_dicts.ps1 afin que l'outil de synchronisation la suive :
$DictFiles = [ordered]@{
    "en" = (Join-Path $DictDir "CTLD_i18n_en.lua")
    "fr" = (Join-Path $DictDir "CTLD_i18n_fr.lua")
    "es" = (Join-Path $DictDir "CTLD_i18n_es.lua")
    "ko" = (Join-Path $DictDir "CTLD_i18n_ko.lua")
    "XX" = (Join-Path $DictDir "CTLD_i18n_XX.lua")
}
  1. Ajoutez la langue comme option dans src/CTLD_i18n.lua (--ctld.i18n_lang = "XX"), et sélectionnez-la à cet endroit lorsque vous voulez l'activer.
  2. Chargez le dictionnaire dans les tests qui exercent toutes les langues — tests/ci/unit/i18n_spec.lua effectue un dofile explicite des dictionnaires non-EN (le tests/ci/helpers/loader.lua partagé ne charge que CTLD_i18n_en.lua).
  3. Exécutez generate_i18n_dicts.ps1 pour confirmer que le nouveau dictionnaire est complet, puis reconstruisez.

Surcharges pour les créateurs de mission (ctld.i18n_overrides)

Les créateurs de mission peuvent surcharger n'importe quelle chaîne sans toucher à un fichier dictionnaire, depuis CTLD_userConfig.lua (la config côté .mizctld.i18n_overrides n'est jamais défini à l'intérieur de src/) :

-- In CTLD_userConfig.lua, not in src/
ctld.i18n_overrides = {
    fr = { ["Troops loaded"] = "Soldats embarqués" },
    en = { ["CTLD Commands"] = "Helicopter Commands" },
}

Les surcharges sont appliquées une seule fois au démarrage, en mémoire, par CTLDi18n:_init() — le premier appel à CTLDi18n.getInstance() parcourt ctld.i18n_overrides et écrase les entrées de dictionnaire correspondantes. La séquence à l'exécution :

1. CTLD.lua loads      → dictionaries populated in ctld.i18n[lang]
2. CTLD_userConfig.lua → sets ctld.i18n_overrides
3. getInstance()       → _init() copies overrides into the in-memory dicts
4. ctld.tr(key)        → returns the overridden value

Il s'agit d'un mécanisme d'exécution (runtime), orthogonal à generate_i18n_dicts.ps1, qui est un outil de construction (build-time) opérant sur les fichiers source des dictionnaires. Les surcharges ne touchent jamais à src/.

API d'audit pour traducteurs

Trois fonctions détectent les écarts entre l'EN et une langue cible. La paire audit retourne des données structurées (adaptées aux assertions busted et aux déclencheurs DO SCRIPT) ; i18n_check est la variante héritée qui journalise directement vers env.*.

ctld.i18n_audit(language)

Compare une langue à l'EN et retourne une table de résultat (plus une chaîne d'erreur lorsque la langue est inconnue) :

local result, err = ctld.i18n_audit("fr")
if err then
    -- language not loaded
else
    if not result.version_match then
        -- EN was bumped; FR may need review
    end
    -- result.en_version    : EN's translation_version
    -- result.lang_version  : target's translation_version
    -- result.missing       : keys present in EN but absent in FR
    -- result.untranslated  : keys where the FR value equals the EN value
end

ctld.i18n_auditAll()

Exécute ctld.i18n_audit() sur chaque langue non-EN chargée et retourne une table indexée par code de langue :

local results = ctld.i18n_auditAll()  -- results["fr"], results["es"], results["ko"]
for lang, r in pairs(results) do
    print(lang, "#missing=" .. #r.missing, "#untranslated=" .. #r.untranslated)
end

ctld.i18n_check(language, verbose) (hérité — DCS uniquement)

Journalise les clés manquantes en tant que env.error et les clés non traduites en tant que env.warning, en écrivant directement dans le log DCS. Comme elle ne retourne pas de données, elle ne convient pas aux assertions — utilisez ctld.i18n_audit() dans les tests et les scripts. Un rapport d'écarts par langue prêt à l'emploi, que vous pouvez coller dans un déclencheur DO SCRIPT, se trouve dans le bloc de commentaire au bas de src/CTLD_i18n.lua.

Voir Building & testing pour la façon dont les specs i18n s'exécutent sous busted, et Architecture pour l'ordre de fusion qui assemble les dictionnaires dans CTLD.lua.