Интернационализация и локализация
Лекции прошлых лет:
Разработка на Python 2026 (по Sphinx)
Термины
- Интернационализация (i18n) — приведение исходного кода в готовое для локализации состояние
- Локализация (l10n) — адаптация культурных и языковых предпочтений ПО
Объекты ПО
- Сообщения и тексты в составе ПО
- Словоформы (в первую очередь — множественные)
- Размер и оформление интерфейсных элементов (RTL, например)
- Изображения: текст на них и культурная атрибуция
Управляющие клавиши (иероглифы, ага
) Параметры, чувствительные к локали (команда locale)
- Ссылки на внешние ресурсы (поддержка, издатель и пр.; телефоны, url и пр.)
- (в сложных случаях) Шрифты
Другие объекты продукта
- Электронная документация
- Непрограммные данные (шаблоны документов и форм и т. п.)
- Бумажная документация и др. сопутствующие предметы
Локализация: инструменты
- Общие словари (?)
- Память перевода (локальные общие словари)
- Нечёткий перевод (при лёгком изменении ресурса или при добавлении похожего)
- … что ещё?
GNU Gettext
Создание перевода
I18n: Обмажем функцией gettext() все строки, нуждающиеся в переводе
Есть ещё ngettext(), см. ниже
xgettext: Создадим на основе i18n-ванного исходного кода шаблон перевода, файл domain.pot (domain — это довольно произвольное название того, что мы переводим)
Не удаляйте сгенерированные комментарии из файла, это не комментарии☺!
msginit: Создадим прототип перевода (для русского — ru.po)
(текстовый редактор или специализированный инструмент редактирования .po) Переведём все строки в ru.po
msgfmt: Скомпилируем перевод в файл ru.mo
Обновление перевода
Вышла новая версия программы (или сами поправили). Там «поехали» строки с сообщениями (появились новые, пропали/изменились/переместились старые).
xgettext: Сгенерируем новый шаблон
msgmerge: Обновим содержимое ru.po на основании шаблона и старого ru.po. У msgmerge много искусственного мозга:
- Не теряет старые переводы (только комментирует)
Размечает новые сообщения возможными (fuzzy) переводами из старых/закомментированных
Умеет в примитивную память переводов
Допереводим ru.po
msgfmt: Компилируем новый перевод
Есть ещё вспомогательные инструменты:
$ rpm -ql gettext-tools | grep /bin/ | cut -d/ -f4 | xargs -n1 whatis autopoint (1) - copies standard gettext infrastructure gettextize (1) - install or upgrade gettext infrastructure msgattrib (1) - attribute matching and manipulation on message catalog msgcat (1) - combines several message catalogs msgcat (n) - Tcl message catalog msgcmp (1) - compare message catalog and template msgcomm (1) - match two message catalogs msgconv (1) - character set conversion for message catalog msgen (1) - create English message catalog msgexec (1) - process translations of message catalog msgfilter (1) - edit translations of message catalog msgfmt (1) - compile message catalog to binary format msggrep (1) - pattern matching on message catalog msginit (1) - initialize a message catalog msgmerge (1) - merge message catalog and template msgunfmt (1) - uncompile message catalog from binary format msguniq (1) - unify duplicate translations in message catalog recode-sr-latin (1) - convert Serbian text from Cyrillic to Latin script xgettext (1) - extract gettext strings from source
Ход работы
Преобразовать текст на Си (собственно i18n)
Традиционный макрос _() (не обязателен)
#include <libintl.h> #include <locale.h> #define _(STRING) gettext(STRING)
В main()-е
setlocale (LC_ALL, "") bindtextdomain ("домен", "путь к локализации"); textdomain ("домен");Во всей программе заменить строки "Something..." на _("Something...") (вызов gettext())
- Создание шаблона (шаблон — это генерат)
$ xgettext --keyword=_ исходник -o шаблон.pot
Документация xgettext
Если не использовать «_», то и --keyword не нужен
- Создание первоначального пустого перевода
$ msginit -i шаблон.pot -l локаль -o перевод.po
Документация msginit
Большинство комментариев — значащие (например, "#, c-format"), лучше их не трогать
- Некоторые поля следует заполнить заранее (vim выделяет их жёлтым)
- Перевод
Строки msgstr — это переводы (поначалу пустые)
- Компиляция перевода (нужна для интерпретации формулы множественного числа)
$ msgfmt -o скомпилированный_перевод.mo перевод.po
- Обновление перевода
Перегенерация шаблона (см. выше, точно той же командой xgettext)
- Обновление старого перевода на основании нового шаблона
$ msgmerge -U старый_перевод.po новый_шаблон.pot
- При этом;
- Совсем удалённые сообщения в переводе комментируются (но не удаляются — это translation memory!)
Некоторые новые сообщения не переведены, для других подобраны похожие переводы, и помечены #, fuzzy. Fuzzy-строки считаются непереведёнными, для того, чтобы они вступили в силу, эту часть комментария (и только ее!) надо удалить
Варианты «пути к локализации»
Локальный — положить рядом с бинарником или в подкаталог po/
Так в Linux не принято!
Системный — положить в /usr/share/locale и/или в /usr/local/share/locale
Требует прав администратора и средств деинсталляции (⇒ вариант для пакетов)
Микс — предусмотреть два варианта сборки, определить переменную в configure и т. д.
С относительным путём: сформировать путь относительно местоположения бинарника
если бинарник ставится в /usr/bin, а переводы — в /usr/share/locale
то путь получится ../share/locale
выполнить тестовую установку куда-нибудь в /tmp или /home/пользователь/что-то-там
при этом бинарник должен ставиться в /home/пользователь/что-то-там/bin/программа
- оттуда и запускать
а путь до локали /home/пользователь/что-то-там/share/locale/… остаётся тот же самый: ../share/locale
Использование множественных форм
В английском их две, в русском — три: 1¹ / 2,3,4² / 5-20³ / 21¹ и т. д.
функция ngettext("Строка", "Строка для множественной формы", сколько)
После обновления в русском переводе — три msgstr[] — для каждой из трёх форм
Страшная формула в строке Plural-Forms:
Общий вид: "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2\n"
nplurals=3 три словоформы, далее идёт C-style выражение:
plural=n%10==1 && n%100!=11 ? 0 — единственное число
: n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 — малое число
: 2 — иначе большое число
Если увеличить nplurals до 4 и добавить в начало формулы n==1? 3 :, получим четвёртую словоформу — для ровно одного объекта (в таких случаях можно число 1 не выводить)
Translation memory
Закомментированные переводы тоже используются (при полном совпадении или как fuzzy при неполном)
При составлении перевода msgmerge можно подключать файлы с другими переводами (ключ -C)
Gettext и Autotools
В Autotools есть поддержка GNU Gettext
Большую часть работы делает autopoint
- (это не «auto point», а «auto PO intl» ☺)
он приносит m4-макросы в каталог 'm4', после него можно запускать autoconf
…точнее, autoconf -Im4
После первого запуска образуется файл po/Makevars.template — его надо переименовать в po/Makevars; из этого файла генерится po/Makefile
autoconf: добавить AM_GNU_GETTEXT([external])
autoconf: добавить po/Makefile.in в AC_CONFIG_FILES() (этот файл — генерат)
Список локалей, для которых требуется перевод — файл po/LINGUAS
Суффикс .in в AC_CONFIG_FILES сам по себе ничего не значит, кроме того, что исходник имеет два сфуфикса /.in — это po/Makefile.in.in, но и он тоже генерат ☺…
… а уже макросы из подключённого AM_GNU_GETTEXT превратят po/Makefile.in в po/Makefile
Добавить po/POTFILES.in со списком файлов, из которых извлекаются строки для перевода
Исходники для autotools:
po/LINGUAS
po/Makevars
po/POTFILES.in
сами .po-переводы.
Остальные генераты не нужны (их приедет довольно много)
Создание и обновление перевода
.pot создаётся автоматически при сборке
Создание .po с переводом — так же, как и раньше, msginit + добавить локаль в ALL_LINGUAS + перевести
Обновление: make -C po update-po
Загвоздка с POT-Creation-Date: — время от времени перевод будет меняться просто потому, что перегенерился pot
Используется относительная схема размещения локализации (см. выше)
Чтобы заработал перевод, проект надо собрать и установить
Достаточно запустить configure --prefix=/куда-то-там, а посте сборки запустить make install
Программа будет установлена в /куда-то-там/bin, а переводы — в /куда-то-там/share/locale
Если увидите методичку с упоминанием явного запуска утилиты gettextize или intltool — они устарели
Что хранить и что не хранить в GIT-е
В частности, не хранятся .mo / .gmo и обычно не хранятся .pot
Пример
Для ALT надо ставить gettext и gettext-tools
В этом примере изначально autotools использовались для всего, кроме переводов.
Так проще объяснить, что происходит, все команды видны в Makefile.am
- Так легче читать (и писать тоже, пока у вас один файл на си и один перевод!)
Формально говоря, это неправильный пример: общим каталогом для данных всего проекта объявляется каталог для переводов ☺
Под конец пример переключается на autotools, и каталоги с исходниками и с переводами разделяются
- Пример стал универсальным, можно добавлять переводы
- В качестве бонуса реализовано единственное число (по методу выше)
После каждого коммита я делал
git clean -fd
autoreconf -fisv && ./configure && make && ./ahello (до перехода на autotools)
autopoint && autoreconf -fisv -Im4 && ./configure && make (после перехода на autotools)
После перехода на autotools перевод работает только для локальной установки в /куда-то-там
Сборка пакета
В примере я использовал вариант, подходящий для хранения исходников пакета в git: все специфичные для ALT файлы (в частности, spec-файл) лежат прямо там же в подкаталоге .gear.
О том, что собираются и устанавливаются переводы, напоминает только autopoint и добавочный ключ к %autoreconf в секции %conf.
Протестировать собранный пакет (без root, в хешере!) можно так:
$ hsh --init … $ hsh-install ~/REPO/x86_64/RPMS.hasher/ahello-0.01-alt1.x86_64.rpm … $ hsh-shell [builder@localhost .in]$ ahello Hail thou the only Universe! Hello, 0 Worlds! Hello, 1 World! Hello, 2 Worlds! Hello, 3 Worlds! Hello, 4 Worlds! Hello, 5 Worlds! [builder@localhost .in]$ LC_ALL=ru_RU.UTF-8 ahello Привет тебе, о единая Вселенная! Привет, 0 миров! Привет, мир! Привет, 2 мира! Привет, 3 мира! Привет, 4 мира! Привет, 5 миров! [builder@localhost .in]$
Поддержка Gettext в других системах сборки
Д/З
За неимением лучшего почитать пример
Написать довольно тупую программу на Си, которая угадывает число 1…100 методом половинного деления
- Первым делом просит загадать число от 1 до 100
- Затем спрашивает, не больше ли число середины интервала (сначала это 50)
Для простоты ответ возможен только в единственной форме (например, "Yes" / "No")
- Любой другой ответ — это ошибка, о чём выводится соответствующая диагностика; ввод повторяется
Ошибкой является также закрытие стандартного ввода (^D)
- На основании ответа выбирает пол-интервала
- Далее по циклу по циклу
- Когда догадается — отвечает
- Оформить автосборку вокруг неё с помощью autotools
- Добавить перевод
- Добавить поддержку перевода с помощью autotools как в конце репозитория с примерами
Обратите внимание: .po-файл — не генерат; .mo и временный .pot — генераты
- Собрать пакет
Проверить, что после установки перевод работает, а лишних файлов (например, .po) в пакете нет.
Положить .src.rpm-пакет в каталог (извините! ☺) 08_I18n отчётного репозитория
