Перейти к содержанию

Переводы

Исходный язык — английский, целиком: код, схемы тулов, промпты, строки интерфейса. Русский живёт рядом с кодом как каталог переводов. Плагин следует за языком интерфейса QGIS; неподдержанные локали откатываются на английский.

Как это устроено

Три файла, из которых руками правится только средний:

Файл Что это Правят руками?
*.py с tr("Apply") английский оригинал да — это и есть код
ai_agent/translations/ai_agent_ru.ts XML, английский → русский да, только колонку перевода
ai_agent/translations/ai_agent_ru.qm скомпилированный бинарник, его грузит QGIS никогда — генерируется

Отдельного ключа нет: ключом служит сама английская строка. Отсутствующий перевод безвреден — tr() вернёт английский оригинал.

Рабочий цикл

После добавления или правки строки в tr():

python3 tools/update_translations.py

Команде не нужны зависимости. Она вытаскивает литералы из tr() / tr_n() разбором AST, дописывает новые записи в .ts (сделанные переводы сохраняются) и компилирует .qm собственным компилятором из tools/qm.py.

Это осознанный отход от штатного рецепта QGIS (.propylupdate5 → Qt Linguist → lrelease.qm), и оба заменённых шага заменены по измеренным причинам:

  • Извлечение — разбором AST, а не pylupdate5. Тот вызовы tr() находит, но читает не-ASCII в исходнике как latin-1 ( и приезжают мусором, и поиск перевода в рантайме промахивается), вовсе не видит tr_n — множественное число теряется целиком — и пишет контекст @default вместо QgisAiAgent, по которому идёт поиск.
  • Компиляция — tools/qm.py, а не lrelease. Этот бинарник живёт в Qt-тулзах, которых нет ни в QGIS, ни в типичной системе: ставить 349 МБ PySide6 ради одного исполняемого файла абсурдно рядом со 144-КБ плагином. Формат .qm прост: магия, блок языка, таблица elfHash(source) → смещение, записи сообщений и правила множественного числа. Вывод побайтово совпадает с настоящим lrelease, что закреплено эталоном: tests/data/golden_ru.qm собран им один раз и лежит в репозитории, а tests/test_qm.py требует, чтобы наш компилятор его воспроизводил. Эталон намеренно покрывает то, на чём легко разойтись: не-ASCII, экранируемые в XML символы, перевод строки и все три формы множественного числа.

Дальше заполните пустые <translation> в .ts и прогоните команду ещё раз. tests/test_i18n.py падает, пока что-то осталось непереведённым, пока плейсхолдер ({0}, %n) потерян в переводе или каталог разошёлся с кодом.

Правила

  • tr() принимает только литерал: tr("Layer '{0}'").format(name) — не f-строку, из которой экстрактор ничего не достанет.
  • Пользовательский текст (summarize_call, всё в ui/) оборачивается в tr(). Модельный текст (описания тулов, схемы, ошибки) — чистый английский без tr(): пропустить его через переводчик значило бы слать модели русские схемы в русском QGIS.
  • Множественное число идёт через tr_n("%n step(s)", count); у русского в каталоге все три формы.

Добавление языка

  1. Добавьте локаль в SUPPORTED_LOCALES в ai_agent/i18n.py и в LANGUAGE_NAMES в core/agent/prompts.py.
  2. Добавьте её правила множественного числа в NUMERUS_RULES в tools/qm.py — неизвестный язык компилятор не угадывает, а отказывается собирать.
  3. Добавьте язык в LANGUAGES в tools/update_translations.py, запустите её, переведите созданный .ts.