internationalization and localization of the python applications with gettext by alexander belchenko
DESCRIPTION
Presentation from the Alexander Belchenko (@bialix) on the PyCamp Kyiv, 2010.01.30TRANSCRIPT
Интернационализация и локализация python-приложений
с использованием gettext
Александр Бельченко (bialix)
http://bialix.com/pycamp/gettext.pdf
v.1.2
Вступительное слово
Это будет еще один доклад про ДжангоЭто доклад про GUI desktop приложения. Частично может быть применимо для webЛокализация нужна пользователямСеребряной пули нет (и ложки тоже нет)Пример одного из возможных вариантов использования gettext. Мы используем его в Bazaar GUI
Стандартный модуль gettext в Python
Предоставляет два вида API:Функции GNU gettext APIКласс GNUTranslations
Функциональное API лишь обертка вокруг классов; в общем случае работает несколько медленнее
Функции gettext.py
Пара функций-переводчиков:gettext — перевод простых строкngettext — перевод с выбором между единственным и множественным числом
Варианты функций с различными префиксами:
u — возвращает перевод как unicode строкуl — перевод в нужной кодировкеd — перевод ищется в указанном домене
Файлы с переводами
Где gettext ищет файлы переводов (sys.prefix/share/locale):
Linux: /usr/local/share/localeWindows: C:\PythonXY\share\locale
Путь поиска можно указать вручнуюСтандартная локация:
Linux: /usr/share/localeWindows: ???
{app}/locale
Язык пользователя
Язык для перевода:Можно указать вручнуюПо умолчанию берется из переменных окружения:LANGUAGE, LC_ALL, LC_MESSAGES, LANG
Эти переменные окружения отсутствуют на Windows (сюрприз-сюрприз!)
Кратко о GNU gettext, PO и MO
Библиотека общего назначения: годится для консоли, GUI и webСтандарт де-факто
Существует развитая инфраструктура инструментов: специальные редакторы, поддержка в web-сервисах для перевода
Файлы:POT — шаблон для переводаPO — файлы перевода на конкретные языкиMO — бинарные файлы перевода (runtime)
Формат PO-файлов
1) Заголовок файла (информация о переводчике, кодировка, выражение для множественного числа)
2) Тело файла состоит из записей вида: #: foo.py:3 msgid "Hello" msgstr ""
Работа с GNU gettext утилитамиСбор строк для перевода (py → pot):
xgettext myapp.py myapplib/*.py -o myapp.potСоздание файла перевода для конкретного языка (pot → po):
msginit -l ru -i myapp.pot -o myapp-ru.po
Трансляция в бинарный формат (po → mo):msgfmt -o locale/ru/LC_MESSAGES/myapp.mo myapp-ru.po
Обновление файлов с переводами (pot→po):msgmerge myapp-ru.po myapp.pot -o new.po
Python-утилитыВ стандартной поставке Python:
pygettext.pymsgfmt.py
В production использовать НЕ рекомендуюЕдинственное видимое достоинство pygettext: умение извекать docstringsНедостатки: не знает про ngettext, dgettext
Для Windows: http://gnuwin32.sf.net
Кавалерийская атака на танки
Документация на модуль gettext рекомендует очень простой способ включения:
import gettextgettext.install('myapp', unicode=True)
В коде приложения не надо ничего импортировать и можно делать:
s = _('Hello, world!')
В чем подвох?
В чем подвох gettext.install
Такой короткий код нормально работает на Linux и в большинстве случаев НЕ работает на WindowsНарушается принцип: явное лучше неявногоПеревод строк сразу «включается» для языка пользователя (LANG)
Что в свою очередь влияет на юнит-тесты, если вы проверяете строки
В чем подвох _()
?
Мы пойдём другим путём
Будем использовать функции и методы модуля gettext напрямую и импортировать имена явноВключать перевод когда это нам нужноСледовать уставу в чужом монастыре
Использование gettext в среде Windows
Кто украл $LANG? (Известно, кто)locale.getdefaultlocale()[0]Получение идентификатора локали LCID (через pywin32 или ctypes):
GetUserDefaultLCID()GetSystemDefaultLCID()
Преобразование LCID в строку при помощи стандартного модуля locale:
locale.windows_locale[lcid]
Пример готового кода
launchpad.net/gettext-py-windowsКратко:
import ctypes, localelcid = ctypes.windll.kernel32.\ GetUserDefaultLCID()lang = locale.windows_locale[lcid]
Использование методов gettext
Работаем с API класса(-ов) GNUTranslations:
import gettext as _gettext_t = _gettext.NullTranslations()_t = _gettext.translation('myapp', localedir=xxx, fallback=True)
Функции-переводчики
bzr branch lp:qbzr (lib/i18n.py)
def gettext(s): return _t.ugettext(s)def N_(s): return s
def ngettext(s, p, n): return _t.ungettext(s, p, n)
Использование в основном коде
import i18n...print i18n.gettext('Hello, world!')Либо:
from i18n import gettext...print gettext('Hello, world!')
Выбор формы множественного числа
«Найдено %d документов»
Английский: Found 1 document Found 2 documents
Русский: Найден 1 документ Найдено 2 документа Найдено 5 документов
Функция ngettextСигнатура: ngettext(singular, plural, number)
print ngettext('Found %d document', 'Found %d documents', n) % nPOT-файл:
#: foo.py:7#, python-formatmsgid "Found %d document"msgid_plural "Found %d documents"msgstr[0] ""msgstr[1] ""
gettext и unit-тесты
Всегда включать перевод явно для основного режима и не включать для режима тестов
_t = _gettext.NullTranslations()def install(): global _t if sys.platform == 'win32': _check_win32_locale() _t = _gettext.translation('myapp', localedir=_get_locale_dir(), fallback=True)
Тестирование интернационализации
Используем специальный класс ZzzTranslations(), который декорирует строкиВключаем явно через командную строку:
python myapp.py --zzz
Класс ZzzTranslations
class _ZzzTranslations(object): def zzz(self, s): return 'zz{{%s}}' % s def ugettext(self, s): return self.zzz( _null_t.ugettext(s)) def ungettext(self, s, p, n): return self.zzz( _null_t.ungettext(s, p, n))
Инфраструктура проекта
Структура каталогов:
myapplib/locale/ ← mo файлыpo/ ← pot, po файлыmyapp.pysetup.pysetup.py: build_pot, build_mo
● Это не шутка, он реально нужен (LANG=en:ja)
● Генерируется автоматически из POT-шаблона утилитой msginit либо msgen
Нужен ли перевод с дефолтного языка на английский?
Строки форматированияНеправильно:s = gettext('Page ' + number + ' of ' + count + ' pages')s = gettext('Page %d of %d pages' % (number, count))Плохо:s = gettext('Page %d of %d pages') % (number, count)Хорошо:s = gettext('Page %(number)d of ' '%(count)d pages') % dict(number=number, count=count)
Web-сервисы для совместной работы над переводами
Один из сервисов: переводы на https://translations.launchpad.net/●Удобно для разработчиков: все языки в одном месте
●Удобно для переводчиков: подсказки о переводах таких же фраз из других проектов
Применение gettext в PyQt4?
В собственном коде использовать gettext() вместо tr() Формы/диалоги создаваемые в QtDesigner: трансляция *.ui → ui_*.py
используется скрипт для автоматической замены вызовов QtGui.QApplication.translate() на gettext()
Ссылки
GNU gettext: http://www.gnu.org/software/gettext
Утилиты для Windows: http://gnuwin32.sf.net/packages/gettext.htm
Код поддержки gettext для Windows:https://launchpad.net/gettext-py-windows
Примеры основаны на коде проектов:https://launchpad.net/qbzrhttps://launchpad.net/bzr-explorer