Машинный перевод, не проверенный носителем языка. Эталоном служит английская версия.

Начало работыПока нет на GitHubИсточник: docs/i18n.md

Локализация движка

Текст движка, предназначенный человеку, представлен на 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.