Локализация движка
Текст движка, предназначенный человеку, представлен на 17 языках наряду с английским: nl, pl, pt-BR, pt-PT, fi, sv, da, nb, cs, uk, ru, de, fr, es, et, ja, zh-Hans. Источником является английский; en-XA — псевдолокаль, которая добавляет акценты, скобки и заполнение к каждому сообщению, чтобы тест мог увидеть, переместился ли текст в каталог. Используемая платформа — Project Fluent (fluent-bundle, unic-langid); код находится в src/i18n/.
Что локализуется, а что никогда#
| Локализуется (читает человек) | Никогда не локализуется (читает программа) |
|---|---|
| сообщения и ошибки командной строки | ключи и значения JSON в result.json и report.json |
| итоговая строка каждого прогона | заголовки и строки CSV |
| текст графиков SVG (заголовки, оси, легенды, подписи) | имена файлов |
report.html: заголовки, таблицы, примечания, подписи |
коды ошибок (E-CLOCK-VERSION) |
| слова вердиктов (VALIDATED, MODELLED, PARTIAL) | матрица верификации, определения единиц, происхождение |
описания видов сценариев (list_kinds(lang)) |
|
консультативные заявления, оговорки и уведомления (engine-legal-*) |
те же тексты внутри JSON, которые остаются на английском |
report.html кампании iq, страница сравнения исследований, таблица лидеров bench, отчёт об устойчивости |
summary.html пакета доказательств и PNG обсерватории: оба хешируются в манифест (у пакета он ещё и подписан), поэтому создаются на английском независимо от языка |
Числа в тексте для человека используют десятичный знак языка (запятая в 15 из 18 языков; точка в en, ja, zh-Hans и en-XA). Числа в машинных файлах всегда используют точку. Счётчик (переменная с именем n, count, n_*, num_* или *_count) попадает в Fluent как число, поэтому перевод может записать { $n -> [one] ... [few] ... *[other] ... }.
Выбор языка#
- Командная строка:
--lang <tag>, затемKSHANA_LANG,LC_ALL,LC_MESSAGES,LANG, затем английский.ptозначает pt-BR; толькоpt-PTотносится к Португалии.zhозначает упрощённый вариант; традиционный отклоняется со списком поддерживаемых тегов. - Библиотека:
api::run_toml_lang(toml, lang),run_toml_bilingual,run_toml_human,run_scenario_lang,list_scenario_kinds_json_lang(lang). - Python:
lang=Noneвrun,run_full,run_typed,validate_toml,error_kind,list_kinds,scenario_kinds;run_human(toml, lang);error_info(toml, lang). - WebAssembly: необязательный завершающий
langвrun_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:
langвrun_scenarioиlist_scenario_kinds.
Ошибки и их коды#
У каждого сообщения об ошибке есть стабильный код, идентификатор сообщения заглавными буквами (e-clock-version — это E-CLOCK-VERSION), одинаковый на каждом языке. Английский текст командной строки не изменён. На другом языке строка имеет вид text [E-CODE]; с --verbose за ней следует вторая строка code: E-CODE на каждом языке. API выдаёт KshanaError::code() и error_info ({kind, code, message}); ошибки MCP несут {kind, code, message} как данные ошибки. Ориентируйтесь на код, а не на текст.
Как работает прогон на другом языке (правило двойного прогона)#
Машинный файл не должен зависеть от языка. Поэтому прогон на другом языке выполняет сценарий дважды: один раз на английском, и его result.json, report.json и CSV являются машинными файлами, и один раз на языке пользователя, для сводки, графика и report.html. Английский — это один прогон, байт в байт такой же, как прежде.
Измерено, нативная сборка, релизная сборка, английский, затем --lang de (секунды): 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; каждый другой поставляемый сценарий занимает менее 4 с; 173 сценария, поставлявшихся на момент измерения, вместе заняли 64 с на английском. Для другого языка затраты ровно вдвое больше. WebAssembly не измерялся (здесь нет wasm-bindgen); при браузерном множителе около 4 для самого медленного вида это минута.
Страница избегает ожидания, показывая английский результат сразу и вызывая run_human(toml, lang) позже: это только второй прогон, он возвращает {summary, svg}, и Studio может показывать «перевод...», пока он не придёт.
Пункт 0,41: один прогон. Построить оба результата из записи (message id, arguments). Это было рассмотрено и не построено: результаты t! проверяются, измеряются и хешируются после их создания (ширина текста графиков, хеши происхождения по выходному тексту), а num() форматирует в месте вызова, поэтому запись внесла бы в машинные файлы токены-заполнители и сделала бы их байты зависящими от воспроизведения. Для этого нужны t!/num, возвращающие токены, завершающий этап в каждом писателе и доказательство побайтной идентичности для каждого писателя.
WebAssembly#
Сборка wasm встраивает только английский каталог. Остальные шестнадцать добавили бы по 150 кБ gzip каждый. Страница при выборе языка загружает i18n/<lang>/engine.ftl и вызывает register_catalogue(tag, text); если загрузка или регистрация не удаются, страница остаётся на английском. Нативная сборка, командная строка и колесо Python встраивают все 18.
Размер, неоптимизированный .wasm из cargo build --release --target wasm32-unknown-unknown --features wasm --lib (до wasm-bindgen и wasm-opt), база bc04e98f в сравнении с этой веткой: 18,71 МБ -> 19,88 МБ в исходном виде, 5,43 МБ -> 5,79 МБ в gzip, +357 кБ gzip. Английский каталог занимает 521 кБ в исходном виде, 153 кБ из них в gzip; остальное — fluent-bundle и его зависимости. Это принято как измеренное.
Каталог#
i18n/<tag>/engine.ftl содержит сообщения, идентификаторы с префиксом engine-. i18n/en/engine.ftl является источником; остальные файлы принадлежат переводчикам и могут не содержать каких-то сообщений (каждое сообщение при этом берётся из английского). Сопутствующие списки в i18n/en/: engine-counts.txt (сообщения с переменными-счётчиками, для форм множественного числа), engine-numbers.txt (числа, передаваемые как заполнители), engine-fragments.txt (сообщения, являющиеся частями предложения, собираемого во время выполнения: их следует объединить в сообщения из целых предложений, и они не должны разрастаться).
Тесты#
tests/i18n_catalogue.rs доказывает английский: у каждого сообщения есть записанный исходный шаблон, и оно отображается ровно в этот текст; каждый идентификатор используется; переменные совпадают; псевдолокаль отображает каждое сообщение. i18n_api.rs, i18n_kinds.rs, i18n_legal.rs охватывают точки входа, описания видов и юридические тексты. i18n_ratchet.rs завершается ошибкой, если новый строковый литерал, который прочитал бы человек, обходит каталог: допуск по файлам в tests/fixtures/i18n/allow.json, который может уменьшаться и никогда не растёт. scripts/i18n_compare.py выполняет целые сценарии базовым и текущим бинарным файлом и сравнивает каждый выходной файл.
Как перенести строку в каталог: docs/design/i18n-migration.md.
Зависимости и лицензии#
Платформа добавляет fluent-bundle 0,16 (Apache-2.0 OR MIT) и unic-langid 0,9 (MIT OR Apache-2.0) вместе с их транзитивными крейтами: fluent-syntax и 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: используется вариант Apache-2.0, который deny.toml допускает), tinystr и zerovec (Unicode-3.0), rustc-hash, smallvec, thiserror, displaydoc (MIT OR Apache-2.0). Каждая из них является разрешительной по списку allow файла deny.toml; thiserror встречается в двух версиях (1 и 2), о чём multiple-versions = "warn" только сообщает. Список лицензий был считан из cargo metadata; cargo deny check не установлен в среде разработки и запускается в интеграционном CI.