Internationalization and localization of the python applications with gettext by Alexander Belchenko

Post on 22-Jun-2015

2.381 views 2 download

Tags:

description

Presentation from the Alexander Belchenko (@bialix) on the PyCamp Kyiv, 2010.01.30

Transcript of Internationalization and localization of the python applications with gettext by Alexander Belchenko

Интернационализация и локализация 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