Localização do motor
O texto do motor voltado às pessoas está em 17 idiomas além do inglês: nl, pl, pt-BR, pt-PT, fi, sv, da, nb, cs, uk, ru, de, fr, es, et, ja, zh-Hans. O inglês é a fonte; en-XA é uma pseudolocalidade que acentua, coloca entre colchetes e preenche cada mensagem, de modo que um teste pode ver se o texto foi para o catálogo. O framework é o Project Fluent (fluent-bundle, unic-langid); o código está em src/i18n/.
O que é localizado e o que nunca é#
| Localizado (uma pessoa lê) | Nunca localizado (um programa lê) |
|---|---|
| mensagens e erros da linha de comando | chaves e valores JSON de result.json e report.json |
| a linha de resumo de cada execução | cabeçalhos e linhas de CSV |
| o texto dos gráficos SVG (títulos, eixos, legendas, descrições) | nomes de arquivos |
report.html: títulos, tabelas, notas, legendas |
códigos de erro (E-CLOCK-VERSION) |
| palavras de veredito (VALIDATED, MODELLED, PARTIAL) | a matriz de verificação, definições de unidades, proveniência |
as descrições dos tipos de cenário (list_kinds(lang)) |
|
declarações consultivas, avisos legais e comunicados (engine-legal-*) |
os mesmos textos dentro de JSON, que permanecem em inglês |
report.html da campanha iq, a página de comparação de estudos, o placar do bench, o relatório de resiliência |
summary.html de um pacote de evidências e o PNG do observatório: ambos têm hash em um manifesto (o do pacote também é assinado), por isso são gerados em inglês qualquer que seja o idioma |
Os números em texto para pessoas usam o separador decimal do idioma (uma vírgula em 15 dos 18 idiomas; um ponto em en, ja, zh-Hans e en-XA). Os números em arquivos de máquina sempre usam o ponto. Uma contagem (uma variável chamada n, count, n_*, num_* ou *_count) chega ao Fluent como número, de modo que uma tradução pode escrever { $n -> [one] ... [few] ... *[other] ... }.
Escolha do idioma#
- Linha de comando:
--lang <tag>, depoisKSHANA_LANG,LC_ALL,LC_MESSAGES,LANG, depois o inglês.pté pt-BR; apenaspt-PTé o de Portugal.zhé o simplificado; o tradicional é recusado com a lista das etiquetas aceitas. - Biblioteca:
api::run_toml_lang(toml, lang),run_toml_bilingual,run_toml_human,run_scenario_lang,list_scenario_kinds_json_lang(lang). - Python:
lang=Noneemrun,run_full,run_typed,validate_toml,error_kind,list_kinds,scenario_kinds;run_human(toml, lang);error_info(toml, lang). - WebAssembly: um
langfinal opcional emrun_typed,run,chart_svg,summary,table_csv,run_all,error_kind,list_kinds,scenario_kinds;run_human(toml, lang);error_info(toml, lang);register_catalogue(tag, ftl_text). - MCP:
langemrun_scenarioelist_scenario_kinds.
Erros e seus códigos#
Toda mensagem de erro tem um código estável, o id da mensagem em maiúsculas (e-clock-version é E-CLOCK-VERSION), o mesmo em todos os idiomas. O texto da linha de comando em inglês não muda. Em outro idioma a linha é text [E-CODE]; com --verbose uma segunda linha code: E-CODE vem em seguida em todos os idiomas. A API fornece KshanaError::code() e error_info ({kind, code, message}); os erros do MCP trazem {kind, code, message} como dados do erro. Compare pelo código, nunca pelo texto.
Como uma execução funciona em outro idioma (a regra de executar duas vezes)#
Um arquivo de máquina não pode depender do idioma. Uma execução em outro idioma portanto executa o cenário duas vezes: uma em inglês, cujos result.json, report.json e CSV são os arquivos de máquina, e outra no idioma do usuário, para o resumo, o gráfico e report.html. O inglês é uma única execução, byte a byte o que era antes.
Medido, nativo, build de release, inglês e depois --lang de (segundos): leo-navmsg-fit-interval-trade 9,2 -> 17,9, cislunar-arc-recovery 7,8 -> 14,1, lunar-station-coverage 7,8 -> 15,0, clock-ensemble 5,8 -> 11,6, leo-navmsg-model-comparison 5,1 -> 10,5, leo-ppp-convergence 2,8 -> 6,2; todo outro cenário incluído leva menos de 4 s; os 173 cenários incluídos quando isso foi medido levaram 64 s em inglês no total. O custo é exatamente o dobro para outro idioma. O WebAssembly não foi medido (sem wasm-bindgen aqui); com o fator do navegador de cerca de 4, é um minuto para o tipo de cenário mais lento.
Uma página evita a espera mostrando o resultado em inglês de imediato e chamando run_human(toml, lang) depois: essa é apenas a segunda execução, retorna {summary, svg}, e o Studio pode dizer "traduzindo..." até que ela chegue.
Item da 0,41: uma única execução. Renderizar os dois a partir de um registro de (message id, arguments). Foi avaliado e não construído: os resultados de t! são inspecionados, medidos e têm hash calculado depois de produzidos (larguras do texto dos gráficos, hashes de proveniência sobre o texto de saída), e num() formata no ponto de chamada, de modo que um registro poria marcadores provisórios nos arquivos de máquina e faria seus bytes dependerem da repetição. Exigiria t!/num que devolvem marcadores, um finalizador em cada gravador e uma prova de identidade de bytes por gravador.
WebAssembly#
A build wasm incorpora apenas o catálogo em inglês. Os outros dezesseis acrescentariam cerca de 150 kB gzip cada um. Uma página busca i18n/<lang>/engine.ftl quando um idioma é escolhido e chama register_catalogue(tag, text); se a busca ou o registro falhar, a página permanece em inglês. Nativamente, a linha de comando e o wheel de Python incorporam todos os 18.
Tamanho, .wasm sem otimização a partir de cargo build --release --target wasm32-unknown-unknown --features wasm --lib (antes do wasm-bindgen e do wasm-opt), base bc04e98f contra esta ramificação: 18,71 MB -> 19,88 MB bruto, 5,43 MB -> 5,79 MB gzip, +357 kB gzip. O catálogo em inglês tem 521 kB brutos, 153 kB deles em gzip; o resto é fluent-bundle e suas dependências. Isso é aceito como medido.
O catálogo#
i18n/<tag>/engine.ftl contém as mensagens, com ids prefixados por engine-. i18n/en/engine.ftl é a fonte; os outros arquivos são dos tradutores e podem não ter mensagens (cada uma recorre ao inglês por mensagem). Listas complementares em i18n/en/: engine-counts.txt (mensagens com variáveis de contagem, para as formas plurais), engine-numbers.txt (números passados como placeables), engine-fragments.txt (mensagens que são partes de uma frase montada em tempo de execução: devem ser reunidas em mensagens de frase completa, nunca aumentar).
Testes#
tests/i18n_catalogue.rs prova o inglês: toda mensagem tem um modelo original registrado e é renderizada exatamente nesse texto; todo id é usado; as variáveis coincidem; a pseudolocalidade renderiza todas as mensagens. i18n_api.rs, i18n_kinds.rs, i18n_legal.rs cobrem os pontos de entrada, as descrições dos tipos de cenário e os textos legais. i18n_ratchet.rs falha se um novo literal de texto que uma pessoa leria contorna o catálogo: tolerância por arquivo em tests/fixtures/i18n/allow.json, que pode diminuir e nunca aumentar. scripts/i18n_compare.py executa cenários inteiros com um binário de referência e com o atual e compara todos os arquivos de saída.
Como mover um texto para o catálogo: docs/design/i18n-migration.md.
Dependências e licenças#
O framework acrescenta fluent-bundle 0,16 (Apache-2.0 OR MIT) e unic-langid 0,9 (MIT OR Apache-2.0), com seus crates transitivos: fluent-syntax e fluent-langneg (Apache-2.0 OR MIT), intl-memoizer (Apache-2.0 OR MIT), intl_pluralrules (Apache-2.0/MIT), type-map (MIT/Apache-2.0), self_cell (Apache-2.0 OR GPL-2.0-only: a alternativa Apache-2.0 é a usada, que deny.toml permite), tinystr e zerovec (Unicode-3.0), rustc-hash, smallvec, thiserror, displaydoc (MIT OR Apache-2.0). Todas são permissivas segundo a lista allow de deny.toml; thiserror aparece em duas versões (1 e 2), o que multiple-versions = "warn" apenas informa. A lista de licenças foi lida de cargo metadata; cargo deny check não está instalado no ambiente de elaboração e é executado na CI de integração.