Pipeline de localisation en chinois pour Unity (avec auto-vérification)
Transforme le processus « extraction → analyse des risques → traduction → écriture → auto-vérification » en un pipeline réutilisable en quatre phases. Le cœur n'est pas la traduction elle-même, mais déterminer d'abord quelles chaînes ne doivent pas être traduites — c'est le seul endroit où l'on peut casser le jeu.
Quand l'utiliser
Avant de commencer, vérifiez ces trois conditions ; si l'une d'elles n'est pas remplie, ne poursuivez pas :
- Où se trouve le texte : Jeux Unity
Mono/IL2CPPdont le texte est codé en dur dans les assemblys managés (*_Data/Managed/Assembly-CSharp.dll) ou dansresources.assets, sans table de localisation, fichier.po,.csvou autre ressource de localisation. Si une table de localisation existe, utilisez-la directement, ne modifiez pas l'assembly. - Vous pouvez obtenir le fichier original : Comme vous modifiez un fichier exécutable, vous devez absolument disposer d'une copie originale non modifiée comme référence (répertoire de sauvegarde, fichier retéléchargé après vérification Steam, etc.). Sans référence, ne modifiez pas le fichier sur place.
- Le chinois peut s'afficher : Le projet utilise une police dynamique intégrée (TMP ou Legacy
Font). Les polices statiques / atlas nécessitent un changement de texture ; ce pipeline ne gère que les polices dynamiques.
L'IL2CPP (pas de Assembly-CSharp.dll, seulement GameAssembly.dll) n'est pas pris en charge ; il nécessite Il2CppDumper + recompilation, ce qui est une autre histoire.
Démarrage rapide
# 0) Créer la structure du projet (génère l10n.json)
python <skill>/scripts/zhl10n.py init -C votre_répertoire_projet
# 1) Lister tous les sites ldstr (environ 10 s pour 5000 entrées)
python <skill>/scripts/zhl10n.py extract
# 2) Voir la classification des risques : ce qui peut être traduit, ce qui ne peut l'être que de manière ciblée, ce qui ne doit absolument pas l'être
python <skill>/scripts/zhl10n.py risk
# 3) Remplir trans_map.json / site_map.json, puis appliquer (ne modifie pas l'original, sortie dans out/build/)
python <skill>/scripts/zhl10n.py apply
# 4) Auto-vérification (lit par défaut les artefacts dans out/build)
python <skill>/scripts/zhl10n.py audit --fail-on P1
Tout-en-un : zhl10n.py all (extract → apply → audit).
Installer les dépendances : pip install dnfile dncil UnityPy fonttools. Pour exécuter uniquement audit, pas besoin de UnityPy.
Responsabilités de chaque étape
| Étape | Point d'entrée | Entrée | Sortie | Périmètre |
|---|---|---|---|---|
| init | zhl10n.py init | Chemin du jeu cible | l10n.json, tables vides | Génère uniquement la configuration, ne touche pas aux fichiers du jeu |
| extract | extract.py | DLL originale (+ assets optionnels) | out/extract/sites.jsonChaque entrée de sites.json contient str/type/method/token/offset | Lecture seule. Doit lire l'original non patché, sinon des chaînes déjà traduites seront extraites |
| risk | risk.py | sites.json | Statistiques de classification dans la console | Uniquement analyse, ne modifie aucune table |
| apply | apply.py | DLL originale + tables de traduction + sous-ensemble de police | out/build/Assembly-CSharp.dllout/build/resources.assets | Écrit de nouveaux fichiers, l'original reste intact (--inplace modifie sur place, conserve d'abord un .orig). Toute traduction bloquée est refusée à l'écriture et génère une erreur |
| audit | audit.py | Artefacts + tables de traduction + glossaire + logs | out/audit/audit.{json,md,html}fixlist.csv | Lecture seule. Peut être exécuté sur n'importe quelle version de fichier, utile pour les comparaisons avant/après |
Trois tables de traduction, chacune avec un rôle différent :
trans_map.json— Table globale, remplace la chaîne partout où elle apparaît. Ne mettez que les chaînes dont la traduction en chinois ne cassera pas la logique.site_map.json— Table ciblée, la clé esttype::méthode, ne modifie leldstrqu'à cet endroit. Utile lorsque « le même mot doit être traduit dans le menu mais pas dans le système de saisie ».enum_map.json— Table des noms d'énumération, modifie les noms de champs dans le tas#Strings. Pour les noms d'affichage des énumérations obtenus parToString().
Ordre de décision : Ciblée → Globale. Si la ciblée correspond, la globale n'est pas utilisée.
Auto-vérification
L'auto-vérification est le point central de ce pipeline. Elle lit les artefacts après l'écriture, indépendamment des tables de traduction.
Sorties
| Fichier | Contenu |
|---|---|
audit.json | Résultat structuré complet : chaque constat contient check/severity/count/items[]/hint |
audit.md | Résumé lisible par un humain, trié par P0→P2 |
audit.html | Rapport pliable dans un seul fichier |
fixlist.csv | Liste des éléments à corriger, une ligne par élément, prête à être utilisée comme liste de tâches |
Points de contrôle (31 points, 6 familles)
CV Complétude — Y a-t-il des omissions ?
| N° | Niveau | Vérification | Critère |
|---|---|---|---|
| CV1 | P1 | Texte non traduit | Le site est affichable + aucune traduction dans les deux tables + non exempté par les règles de risque |
| CV1b | P2 | Texte ressemblant à une interface mais bloqué | Indique une contrainte du moteur, pas une omission de traduction ; nécessite une traduction ciblée et un test réel |
| CV2 | P1 | Traduction vide / placeholder | La traduction est vide, contient TODO/à traduire |
| CV3 | P1 | Traduction d'une phrase longue identique à l'original | Probablement pas traduite du tout |
| CV4 | P2 | Traduction d'un mot court/symbole identique à l'original | Conserver l'original pour les icônes, noms de touches est généralement correct |
| CV5 | P2 | Entrée morte dans la table ciblée | Déclarée mais introuvable dans l'IL original ; le nom de méthode a changé ou elle est couverte par la table globale |
CS Cohérence — La terminologie est-elle correcte ?
| N° | Niveau | Vérification |
|---|---|---|
| CS1 | P2 | Même texte original traduit différemment dans la table globale et la table ciblée |
| CS2 | P1 | Traduction obligatoire du glossaire non respectée |
| CS3 | P1 | La traduction correspond à un mot interdit du glossaire |
| CS4 | P2 | Clés en double (différentes uniquement par la casse/les espaces) |
| CS5 | P2 | Style de ponctuation chinois tendant vers les demi-largeurs |
FM Format — La concaténation casse-t-elle ?
| N° | Niveau | Vérification | Conséquence |
|---|---|---|---|
| FM1 | P1 | Non-conservation des placeholders | String.Format lève une exception ou des nombres disparaissent |
| FM1b | P2 | Suppression des placeholders de pluriel anglais | Bonne pratique, juste une confirmation que l'on n'a pas supprimé un vrai paramètre |
| FM2 | P2 | Nombre de sauts de ligne différent | Problème de mise en page |
| FM3 | P1 | Perte des espaces de début/fin dans les unités/fragments concaténés | 1000coins collé |
| FM4 | P2 | Autre changement d'espacement de début/fin | Généralement sans conséquence entre caractères chinois |
| FM5/FM6 | P2 | Ponctuation demi-largeur / espaces consécutifs | Apparence |
| FM7 | P2 | Expansion excessive de la longueur de la traduction | Souvent le résultat d'avoir mis une description dans une étiquette |
RK Risque — Le jeu peut-il être cassé ?
| N° | Niveau | Vérification |
|---|---|---|
| RK1 | P0 | Chaîne à haut risque incluse dans la table de traduction (chemin, clé moteur, expression régulière, nom d'action de saisie…) |
| RK2 | P2 | La table globale contient un identifiant unique, recommander un passage en ciblé |
| RK3 | P1 | La clé de la table de traduction contient du chinois, deuxième application confuse |
| RK4 | P2 | La traduction contient des fragments anglais résiduels |
IG Structure — Le fichier lui-même est-il toujours valide ?
| N° | Niveau | Vérification |
|---|---|---|
| IG0/IG1/IG2/IG4 | P0 | Fichier cible manquant, anomalie PE/métadonnées, #US impossible à analyser, jeton ldstr invalide, nombre de lignes de métadonnées différent de l'original |
| IG3 | P2 | Site toujours en anglais après le patch (liste blanche attendue, pour vérification) |
| IG5/IG6 | P1 | Caractères de police manquants / fichier de police impossible à analyser |
RT Exécution — Le jeu a-t-il signalé quelque chose ?
| N° | Niveau | Vérification |
|---|---|---|
| RT1 | P1 | Trace d'exception dans Player.log (nécessite la configuration source.logs) |
Modes de déclenchement
Trois modes, selon le scénario :
# 1. Auto-vérification immédiate après écriture (le plus courant en boucle de développement)
zhl10n.py audit
# 2. Comme passerelle : en CI ou avant publication, sortie non nulle si le seuil n'est pas atteint
zhl10n.py audit --fail-on P0 # Échoue dès qu'un blocage est présent
zhl10n.py audit --fail-on P1 # Sévérité par défaut, recommandé pour les publications
zhl10n.py audit --fail-on P2 # Même les avertissements doivent être résolus
# 3. Comparaison de régression : quels problèmes supplémentaires par rapport à la dernière fois
zhl10n.py audit --baseline
La phase apply exécute également automatiquement une audit à la fin, et affiche les conclusions — l'étape d'écriture ne produit pas de paquet défectueux sans que l'on le sache.
Comment utiliser les résultats de l'audit
- Vérifiez d'abord les
P0. Aucun P0 ne doit être publié, cela signifie « le jeu pourrait ne pas démarrer ». - Vérifiez ensuite les
P1.CV1est la liste des omissions de traduction,FM1est la liste des crashes certains,RT1est la preuve que le jeu a déjà crashé. - Les
P2servent de liste de vérification :CV1bvous dit quels points sont des contraintes du moteur et non des omissions,CV5/CS4sont des entrées poubelles à supprimer directement. fixlist.csvse traite ligne par ligne ; corrigez, relancez, jusqu'àP0=0 et P1=0.
Configuration (l10n.json)
{
"project": "NomDuJeu", "version": "1.0",
"source": {
"dll": "…/Managed/Assembly-CSharp.dll", // Uniquement pour localiser, extract lit backup
"backup": "…/_Backup/Assembly-CSharp.dll", // Doit être l'original non modifié
"assets": ["…/resources.assets"],
"logs": ["…/Player.log"] // Optionnel, pour RT1
},
"output": { "dir": "out" },
"text": { "map": "trans_map.json", "site_map": "site_map.json", "enum_map": "enum_map.json" },
"glossary": "glossary.csv",
"font": { "subset": "font/zh-subset.ttf", "source": "font/NotoSansSC-Bold.otf", "targets": {} },
"audit": { "severity_fail": "P1", "max_expansion_ratio": 2.5, "max_expansion_abs": 40 },
"risk": { "extra_patterns": [], "extra_allow": [] }
}
Deux issues de secours, pensez-y en cas d'erreur :
risk.extra_patterns— Chaînes connues pour être intraduisibles dans le projet (expressions régulières). Exemple :^Levels$risk.extra_allow— Chaînes spécifiques déjà confirmées comme n'ayant pas besoin d'être traduites. Les ajouter ici ne les considère pas comme des omissions
Glossaire glossary.csv : trois colonnes en,zh,forbidden, forbidden contient des synonymes séparés par |. CS2/CS3 en dépendent ; s'il est absent, ces deux vérifications sont ignorées.
Règles de décision : quelles chaînes ne doivent absolument pas être traduites
Version courte : Les chaînes utilisées comme « clés » dans le code, si on les traduit, on ne trouve plus rien.
| Catégorie | Exemple | Que se passe-t-il si on les traduit ? |
|---|---|---|
| Chemin de ressource | "Levels", "UI/Fonts/X" | Table des niveaux vide → NRE lors du chargement de la sauvegarde → blocage sur l'écran de chargement |
| Action de saisie / nom de champ | Move/Up/Button | Les liaisons de touches ne fonctionnent plus |
| Champ de sauvegarde | legacySkillPointCredit | La sauvegarde ne se charge pas |
| Nom d'énumération | Deposit (utilisé ailleurs que dans ToString(), comme Enum.Parse) | L'analyse échoue |
| Motif d'expression régulière | ([0-9.]+)% more coins | La réécriture du texte d'indice échoue, retour à l'anglais |
| Nom d'objet / de ressource | new GameObject("Display glass") | Généralement ne casse pas, mais pollue les noms de ressources |
| Chaîne de format | {0:N0} / {1:N0} | String.Format lève une exception |
Même forme, destin différent : Pause dans le menu des paramètres est un texte de bouton (à traduire), dans GameInput c'est un nom d'action (à ne pas traduire).
Il est impossible de les distinguer par la forme du texte ; seul le fait de savoir « dans quel type, dans quelle méthode » ça se trouve permet de trancher. C'est ce que fait _code_shape() dans risk.py :
- Les littéraux dans les méthodes de construction (
.ctor/Build/Initialize/Create/Ensure…) → noms, ne pas traduire - Les littéraux dans les méthodes d'affichage (
Draw/OnGUI/Format/get_…) → textes, traduire - Les littéraux dans les méthodes de comparaison (
Is/Has/Matches/Find…) → clés de correspondance, ne pas traduire
Après avoir ajouté une nouvelle règle, vous devez la valider avec un test négatif : insérez délibérément "Levels": "Niveaux" dans la table de traduction, et vérifiez que l'audit remonte un P0.
Les pièges des polices
Les polices dynamiques intégrées ne peuvent être remplacées que par une substitution de même taille. Réécrire entièrement resources.assets modifiera les décalages m_StreamData des Texture2D, et toutes les textures seront décalées.
Processus : l'étape font crée un sous-ensemble basé sur « l'ensemble des caractères chinois réellement utilisés », le convertit en TTF (Unity est instable avec les CFF/OTF), puis le replace dans m_FontData. Si le nombre de glyphes est trop important, réduisez la taille de la police ou le poids, mais ne dépassez pas la taille originale.
Les polices comme Bangers-Regular, qui ne font que quelques dizaines de Ko, ne peuvent pas contenir le chinois — redirigez le chemin de la police vers une autre police chinoise (dans font.targets de l10n.json), c'est plus fiable que de forcer.
Liste de vérification avant livraison
[ ] audit --fail-on P1 passé
[ ] Lancement réel du jeu, parcourez chaque écran : menu principal / paramètres / liste des sauvegardes / HUD en jeu / fenêtre contextuelle / erreur
[ ] Aucune nouvelle exception dans Player.log (RT1 = 0)
[ ] L'emplacement de la sauvegarde originale est documenté dans les notes de version (c'est la seule façon de restaurer)
[ ] Enregistrez la somme MD5 de l'artefact dans les notes de version
Méthode de diagnostic en cas d'erreur : remplacez d'abord uniquement la DLL originale, conservez le reste ; si tout fonctionne, le problème vient du patch de l'assembly ; puis remplacez uniquement la DLL, conservez le reste ; si le problème se reproduit, c'est confirmé. La dichotomie est plus rapide que la lecture du code.
Documentation de référence
references/flow.md— Implémentation détaillée et structures de données de chaque phasereferences/audit-spec.md— Définition complète des 31 points de contrôle, niveaux, instructions de correctionreferences/risk-rules.md— Table des règles de risque et les accidents réels derrièreassets/l10n.example.json/assets/glossary.example.csv— Modèles de configuration