Localização
O sistema de localização da Turian (ADR 0011) é compartilhado pelo engine, pelo jogo compilado e pelo próprio Studio — um único runtime, um único formato de autoria, uma única CLI. Esta página cobre o fluxo de trabalho: autoria → extração → tradução → compilação → publicação.
Três formas de chegar a uma string
| Ponto de entrada | Para | Chave | Escrito por |
|---|---|---|---|
tr("Open Scene…") |
Chrome de UI (menus, botões, labels de HUD) | a própria string de origem | programadores, no código |
Locale.keyFallback("edit.undo", "Undo") |
Texto dev-autoral extraído de uma lista em tempo de execução (um comando/título de painel registrado) | um id estável explícito, com o próprio valor em execução como fallback | programadores, no código |
Locale.key("dlg.act1.intro") |
Conteúdo do jogo (diálogo, nomes de itens, missões) | um id estável explícito | designers, em um asset .strings |
Todos os três são compilados na mesma tabela e servidos pelo mesmo serviço Locale
— a única diferença é de onde vem o id, e para o que uma falta degrada.
// Texto simples
frame.tr("Open Scene…")
// Desambiguado por contexto (duas strings em inglês idênticas, significados diferentes)
frame.trc("door", "Open")
// Plural (a regra cardinal CLDR escolhe a forma correta para o idioma ativo)
frame.trn("# file", "# files", count, &.{})
// Interpolado (placeholders nomeados de um subconjunto ICU, não especificadores std.fmt)
frame.trArgs("Save changes to \"{title}\" before closing?", &.{
.{ .name = "title", .value = .{ .text = doc_title } },
})
// Conteúdo do jogo escrito por designers, buscado por id
frame.trKey("dlg.act1.intro", &.{})
tr/trc/trn recebem sua mensagem como um parâmetro comptime — um
argumento não literal é um erro de compilação, não um comentário de
revisão. Uma tradução faltante degrada para a fonte em inglês, nunca para
uma chave crua. key/trKey degradam para ⟦the.key⟧ em builds de debug e
para a chave nua em release, então uma tradução de conteúdo de designer
faltante nunca fica silenciosamente invisível. keyFallback/trKeyFallback ficam entre os dois: como key, ids não literais são aceitáveis (o id é um valor em tempo de execução); como tr, uma falta degrada para texto real — o fallback fornecido pelo chamador — não um marcador entre colchetes, já que esse fallback é sempre um padrão inglês conhecido e seguro, não conteúdo de designer arbitrário.
A sintaxe de mensagem é um subconjunto documentado do ICU MessageFormat:
{name}, {count, plural, one {# file} other {# files}} (com um branch
opcional de valor exato =0 {...}), e {gender, select, male {He} other {They}}.
Registrando o serviço
var locale = engine.Locale.init("en"); // default_locale
locale.loadTable(embedded_pt_br_strtab) catch {};
frame.services.register(engine.Locale, &locale);
Troque de idioma em tempo de execução — sem necessidade de recarregar a cena, já que a UI é renderizada novamente a cada quadro:
locale.setLocale("pt-BR");
Tabelas carregadas são mantidas em cache pela sessão e nunca são liberadas ao trocar, então qualquer slice de string já entregue permanece válido (ele só mostra o locale antigo até ser buscado novamente).
Traduzindo listas em tempo de execução
Parte do texto de UI lista itens orientados a dados em tempo de execução com uma string de exibição — um comando registrado, um painel encaixável, um plugin descoberto — onde a string é em inglês (criada pelo desenvolvedor), mas só existe como um valor em tempo de execução (um campo de struct) quando chega à chamada de desenho. tr() não pode aceitá-lo: seu parâmetro msg precisa ser um literal comptime.
A solução é usar o id estável da própria entrada como chave, com a string em tempo de execução como fallback:
// Local de registro — um literal struct comum, nada extra para escrever:
.{ .id = "edit.undo", .title = "Undo", .defaults = &.{...} }
// Local de desenho — traduzido por id, recorrendo ao título inglês (já final):
StudioLocale.trKeyFallback(entry.desc.id, entry.desc.title, &.{})
Nada precisa ser mantido em sincronia manualmente: turian-cli i18n extract também percorre literais struct, colhendo qualquer par de campos irmãos .id/.title como uma entrada de catálogo com chave de id, e o texto literal de .title se torna a fonte inglesa para o tradutor. Isso funciona para qualquer struct que siga a convenção de nomenclatura .id/.title — editor.shortcuts.CommandDesc e o PanelDesc de studio/main-window/Panels.zig hoje — não apenas um tipo específico.
Escrevendo traduções
Um arquivo .strings é a fonte da verdade — JSON, um arquivo por locale:
{
"version": 1,
"locale": "pt-BR",
"units": [
{ "id": "Open Scene…", "source": "Open Scene…", "target": "Abrir Cena…", "state": "translated" }
]
}
Extrair → traduzir → compilar
# Percorre uma árvore de código-fonte em busca de call sites de
# tr/trc/trn/trKey, escrevendo (ou atualizando, preservando traduções
# existentes) um arquivo .strings:
turian-cli i18n extract <src-dir> pt-BR my-project/i18n/pt-BR.strings.json
# Preencha "target" para cada unidade, depois compile para o formato de runtime:
turian-cli i18n compile my-project/i18n/pt-BR.strings.json my-project/i18n/pt-BR.strtab
extract é seguro para reexecutar a qualquer momento: ids correspondentes
mantêm seu target existente; novos call sites são adicionados com state: "new". compile só emite unidades com um target não vazio — uma string
não traduzida simplesmente não está na tabela, e recai para a fonte em
inglês em tempo de execução, então uma cobertura de tradução parcial nunca é
um erro fatal.
Traduzindo o próprio Studio
A UI do próprio Studio usa o mecanismo idêntico, através de
studio/services/StudioLocale.zig (um wrapper fino que expõe
tr/trc/trn/trArgs/trKeyFallback apoiado por uma instância Locale). O Studio não é
um projeto, então seus catálogos são incorporados via @embedFile em vez de
passar pelo pipeline de assets: studio/i18n/<locale>.strings.json (fonte)
e studio/i18n/<locale>.strtab (compilado, incorporado).
Para atualizar o catálogo pt-BR do Studio após mexer no código de UI:
turian-cli i18n extract studio pt-BR studio/i18n/pt-BR.strings.json
# traduza quaisquer unidades novas (state: "new") no .strings.json
turian-cli i18n compile studio/i18n/pt-BR.strings.json studio/i18n/pt-BR.strtab
O seletor de idioma fica em Settings → UI → Language; a troca surte
efeito imediatamente (o Studio persiste a escolha em
editor.ui.language).
Plurais
A categoria de plural é resolvida a partir das regras cardinais CLDR para o
idioma ativo — one/few/many/other (e zero/two para idiomas como
o árabe) — não condicionais escritas manualmente por idioma. trn/{n, plural, ...} cobrem o caso comum; um branch explícito no estilo =0 {...}
tem prioridade sobre a categoria quando presente.
Fora de escopo
Espelhamento de layout RTL, shaping BiDi e tradução automática não fazem
parte deste sistema. Um campo direction está reservado nos metadados de
locale para suporte futuro a RTL.