Pipeline de Localização para Chinês (Unity, com Auto-Auditoria)
Transforma o processo de "extrair → avaliar risco → traduzir → aplicar → auditar" em um pipeline reutilizável de quatro fases. O núcleo não é a tradução em si, mas sim determinar quais strings não podem ser traduzidas — este é o único ponto que pode quebrar o jogo.
Quando usar
Antes de usar, verifique três condições; se qualquer uma não for atendida, não prossiga:
- Onde está o texto: Jogos Unity
Mono/IL2CPPcom texto codificado diretamente no assembly gerenciado (*_Data/Managed/Assembly-CSharp.dll) ou emresources.assets, sem tabela de localização,.po,.csv, etc. Se houver uma tabela de localização existente, use-a; não modifique o assembly. - Arquivo original disponível: A modificação é em um arquivo executável. Você deve ter uma cópia não modificada original como baseline (um backup, ou o arquivo baixado após verificação Steam). Sem baseline, não faça edições in-place.
- Chinês é exibível: O projeto usa fontes dinâmicas incorporadas (TMP ou Legacy
Font). Fontes estáticas/atlas de caracteres chineses exigem troca de textura; este pipeline lida apenas com fontes dinâmicas.
IL2CPP (sem Assembly-CSharp.dll, apenas GameAssembly.dll) não é suportado; requer Il2CppDumper + recompilação, fora do escopo.
Início Rápido
# 0) Criar esqueleto do projeto (gera l10n.json)
python <skill>/scripts/zhl10n.py init -C /caminho/do/seu/projeto
# 1) Listar todos os locais de ldstr (~10s para 5000 entradas)
python <skill>/scripts/zhl10n.py extract
# 2) Ver a classificação de risco: quais podem ser traduzidos, quais só com direcionamento, quais absolutamente não
python <skill>/scripts/zhl10n.py risk
# 3) Preencher trans_map.json / site_map.json e aplicar a tradução (não modifica o original; saída em out/build/)
python <skill>/scripts/zhl10n.py apply
# 4) Auditoria automática (lê os artefatos em out/build por padrão)
python <skill>/scripts/zhl10n.py audit --fail-on P1
Comando único: zhl10n.py all (extract → apply → audit).
Instalar dependências: pip install dnfile dncil UnityPy fonttools. Para executar apenas audit, UnityPy não é necessário.
Responsabilidades de cada fase
| Fase | Entrada | Saída | Escopo |
|---|---|---|---|
| init | Caminho do jogo alvo | l10n.json, tabelas vazias | Apenas gera configuração, não toca nos arquivos do jogo |
| extract | DLL original (+ assets opcionais) | out/extract/sites.jsonCada item em sites.json contém str/type/method/token/offset | Apenas leitura. Deve ler o original sem patch, caso contrário extrairá strings já traduzidas |
| risk | sites.json | Estatísticas de classificação no console | Apenas avaliação, não modifica nenhuma tabela |
| apply | DLL original + tabela de tradução + subconjunto de fonte | out/build/Assembly-CSharp.dllout/build/resources.assets | Escreve novos arquivos; o original não é tocado (--inplace modifica no local, mas primeiro cria um backup .orig). Qualquer tradução que atinja uma regra de bloqueio é rejeitada e um erro é gerado |
| audit | Artefato + tabela de tradução + glossário + logs | out/audit/audit.{json,md,html}fixlist.csv | Apenas leitura. Pode ser executado em qualquer versão do arquivo para comparação entre versões |
Três tipos de tabelas de tradução, com responsabilidades distintas:
trans_map.json— Tabela global, substitui a string em todas as ocorrências. Coloque apenas strings que "traduzir para chinês não quebra a lógica".site_map.json— Tabela direcionada, a chave éTipo::Método, modifica apenas oldstrnaquele local. Usada para casos onde "a mesma palavra deve ser traduzida no menu, mas não no sistema de entrada".enum_map.json— Tabela de nomes de enumeração, modifica os nomes dos campos no heap#Strings. Usada para nomes de exibição de enumerações vindas deToString().
Ordem de avaliação: Direcionada → Global. Se a direcionada for encontrada, a global não é usada.
Auditoria Automática
A auditoria automática é o ponto central deste pipeline. Ela lê o artefato após a aplicação de forma independente, sem confiar na própria tabela de tradução.
Saídas
| Arquivo | Conteúdo |
|---|---|
audit.json | Resultado estruturado completo: cada achado contém check/severity/count/items[]/hint |
audit.md | Resumo legível, ordenado por P0→P2 |
audit.html | Relatório em arquivo único, com seções recolhíveis |
fixlist.csv | Lista de itens a corrigir, uma linha por item, pronta para uso como lista de tarefas |
Verificações (31 itens, 6 famílias)
CV Integridade — se algo foi esquecido
| ID | Severidade | Verificação | Critério |
|---|---|---|---|
| CV1 | P1 | Texto não traduzido | Site exibível + nenhuma tradução em ambas as tabelas + não liberado por regra de risco |
| CV1b | P2 | Texto com aparência de UI mas bloqueado | Indica restrição do mecanismo, não esquecimento; requer tradução direcionada e teste real |
| CV2 | P1 | Tradução vazia / placeholder | Tradução vazia, contendo TODO/待译 |
| CV3 | P1 | Texto longo idêntico ao original | Suspeito de não ter sido traduzido |
| CV4 | P2 | Palavras curtas/símbolos idênticos ao original | Manter original para caracteres de ícone, nomes de teclas geralmente correto |
| CV5 | P2 | Entradas órfãs na tabela direcionada | Declaradas mas não encontradas no IL original; método renomeado ou sobrescrito pela tabela global |
CS Consistência — se a terminologia corresponde
| ID | Severidade | Verificação | |---|---|---|---| | CS1 | P2 | Mesmo texto original com traduções diferentes na tabela global e direcionada | | CS2 | P1 | Tradução obrigatória do glossário não respeitada | | CS3 | P1 | Tradução atinge palavra proibida do glossário | | CS4 | P2 | Chaves duplicadas (diferindo apenas por maiúsculas/minúsculas/espaços) | | CS5 | P2 | Pontuação chinesa com estilo parcialmente half-width |
FM Formato — se a concatenação quebrará
| ID | Severidade | Verificação | Consequência |
|---|---|---|---|---|
| FM1 | P1 | Placeholders não preservados | String.Format lança exceção ou números desaparecem |
| FM1b | P2 | Placeholder de plural em inglês removido | Prática correta, apenas confirma que não removeu um parâmetro real acidentalmente |
| FM2 | P2 | Número de quebras de linha diferente | Layout desalinhado |
| FM3 | P1 | Espaços iniciais/finais de unidade/fragmento perdidos | 1000moedas colado |
| FM4 | P2 | Outras alterações de espaços iniciais/finais | Geralmente irrelevante entre caracteres chineses |
| FM5/FM6 | P2 | Pontuação half-width / espaços consecutivos | Estética |
| FM7 | P2 | Expansão excessiva do comprimento da tradução | Geralmente ocorre quando uma descrição é colocada dentro de um rótulo |
RK Risco — se o jogo pode quebrar
| ID | Severidade | Verificação | |---|---|---|---| | RK1 | P0 | String de alto risco incluída na tabela de tradução (caminhos, chaves do mecanismo, regex, nomes de ação de entrada…) | | RK2 | P2 | Tabela global contém identificador único; sugere usar direcionada | | RK3 | P1 | Chave da tabela de tradução contém chinês; segunda aplicação resultará em confusão | | RK4 | P2 | Tradução contém fragmento em inglês residual |
IG Estrutura — se o arquivo em si ainda é válido
| ID | Severidade | Verificação |
|---|---|---|---|
| IG0/IG1/IG2/IG4 | P0 | Arquivo alvo ausente, anomalia de PE/metadados, #US não analisável, token ldstr inválido, número de linhas de metadados diferente do original |
| IG3 | P2 | Site ainda em inglês após patch (whitelist esperada, para verificação) |
| IG5/IG6 | P1 | Fonte com falta de glifos / arquivo de fonte não analisável |
RT Runtime — se o jogo reclamou
| ID | Severidade | Verificação |
|---|---|---|---|
| RT1 | P1 | Stack trace de exceção em Player.log (requer configuração source.logs) |
Formas de Acionamento
Três modos, escolha conforme o cenário:
# 1. Auto-verificação imediatamente após a escrita (mais comum no ciclo de desenvolvimento)
zhl10n.py audit
# 2. Como gate: no CI ou antes do lançamento, sai com código de erro se não atingir o padrão
zhl10n.py audit --fail-on P0 # Falha se houver qualquer bloqueio
zhl10n.py audit --fail-on P1 # Rigor padrão, recomendado para lançamento
zhl10n.py audit --fail-on P2 # Até os avisos precisam ser resolvidos
# 3. Comparação de regressão: quantos problemas a mais que na última versão
zhl10n.py audit --baseline
Ao final de apply, uma auditoria é executada automaticamente e os resultados são exibidos — a fase de aplicação não produzirá um pacote defeituoso sem conhecimento.
Como usar os resultados da auditoria
- Olhe primeiro os
P0. Nenhum P0 pode ser lançado, ele significa "o jogo pode não iniciar". - Depois, os
P1.CV1é a lista de textos não traduzidos,FM1é a lista de bugs garantidos,RT1é a evidência de que já quebrou. P2como lista de verificação:CV1binforma quais são restrições do mecanismo e não esquecimento;CV5/CS4são entradas lixo que podem ser removidas.fixlist.csvpode ser usado diretamente, linha por linha. Corrija e execute novamente atéP0=0 e P1=0.
Configuração (l10n.json)
{
"project": "NomeDoJogo", "version": "1.0",
"source": {
"dll": "…/Managed/Assembly-CSharp.dll", // Apenas para localização; extract lê o backup
"backup": "…/_Backup/Assembly-CSharp.dll", // Deve ser o original não modificado
"assets": ["…/resources.assets"],
"logs": ["…/Player.log"] // Opcional, para 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": [] }
}
Duas saídas de emergência, pense nelas ao encontrar um erro:
risk.extra_patterns— Strings conhecidas que não podem ser traduzidas no projeto (regex). Ex.:^Levels$risk.extra_allow— Strings específicas já confirmadas que não precisam de tradução. Adicioná-las aqui as remove da lista de não traduzidas
Glossário glossary.csv: três colunas en,zh,forbidden, forbidden separa sinônimos com |. CS2/CS3 dependem dele; se ausente, essas verificações são ignoradas.
Regras de Decisão: Quais Strings Absolutamente Não Podem Ser Traduzidas
Versão resumida: Strings usadas como "chave" no código, se traduzidas, não serão encontradas.
| Categoria | Exemplo | O que acontece se traduzir |
|---|---|---|
| Caminho de recurso | "Levels", "UI/Fonts/X" | Tabela de fases vazia → NRE ao restaurar save → tela de carregamento trava |
| Nome de ação de entrada/campo | Move/Up/Button | Atalhos de teclado quebram |
| Campo de save | legacySkillPointCredit | Save não carrega |
| Nome de enumeração | Deposit (usado em Enum.Parse além de ToString()) | Falha na análise |
| Padrão regex | ([0-9.]+)% more coins | Substituição de texto de dica falha, retorna ao inglês |
| Nome de objeto/recurso | new GameObject("Display glass") | Geralmente não quebra, mas polui o nome do recurso |
| String de formato | {0:N0} / {1:N0} | Exceção String.Format |
Mesma forma, significado diferente: Pause no menu de configurações é texto de botão (deve ser traduzido), em GameInput é nome de ação (não pode ser traduzido).
É impossível distinguir apenas pela forma do texto; só é possível pelo "tipo e método em que aparece". É o que _code_shape() no risk.py faz:
- Métodos que criam objetos (
.ctor/Build/Initialize/Create/Ensure…) → literais são nomes, não traduzir - Métodos que desenham UI (
Draw/OnGUI/Format/get_…) → são textos, traduzir - Métodos de comparação (
Is/Has/Matches/Find…) → são chaves para correspondência, não traduzir
Após adicionar uma nova regra, deve-se validar com teste negativo: insira deliberadamente na tabela de tradução "Levels": "Níveis" e confirme que a auditoria reporta P0.
Problemas com Fontes
Fontes dinâmicas incorporadas só permitem substituição in-place de mesmo tamanho. Reescrever resources.assets completamente altera o offset m_StreamData do Texture2D, corrompendo todas as texturas.
Processo: a fase font cria um subconjunto com base no "conjunto real de caracteres chineses usados", converte para TTF (Unity tem suporte instável para CFF/OTF) e insere em m_FontData. Se o número de glifos for maior que o suportado, reduza o tamanho da fonte ou comprima o peso; não pode exceder o tamanho original.
Fontes como Bangers-Regular, com apenas dezenas de KB, não comportam chinês — redirecione o caminho da fonte para outra fonte chinesa (configurado em font.targets no l10n.json), é mais confiável do que forçar a substituição.
Lista de Verificação Pré-Lançamento
-
audit --fail-on P1passa - Iniciar o jogo e percorrer cada tela: menu principal / configurações / lista de saves / HUD do jogo / pop-ups / erros
-
Player.logsem novas exceções (RT1 = 0) - Localização do arquivo original de backup incluída nas notas de lançamento (é a única forma de restaurar)
- Registrar MD5 do artefato nas notas de versão
Método de localização de defeitos: Primeiro troque apenas o DLL original, mantenha o resto; se funcionar, o problema está no patch do assembly. Depois troque apenas o DLL, mantenha o resto; se reproduzir, confirme. Bisseção é mais rápido que ler código.
Documentos de Referência
references/flow.md— Implementação detalhada e estruturas de dados de cada fasereferences/audit-spec.md— Definição completa das 31 verificações, níveis, guia de correçãoreferences/risk-rules.md— Tabela de regras de risco e os acidentes reais por trás delasassets/l10n.example.json/assets/glossary.example.csv— Modelos de configuração