Интернационализация и локализация
          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/locale
  Windows: C:PythonXYsharelocale
Путь поиска можно указать вручную
Стандартная локация:
  Linux: /usr/share/locale
  Windows: ???
    {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.py
  msgfmt.py
В production использовать НЕ рекомендую
Единственное видимое достоинство
pygettext: умение извекать docstrings
Недостатки: не знает про ngettext, dgettext


Для Windows: http://gnuwin32.sf.net
Кавалерийская атака на танки
Документация на модуль gettext рекомендует
 очень простой способ включения:

import gettext
gettext.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, locale

lcid = 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) % n

POT-файл:

#: foo.py:7
#, python-format
msgid "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.py
setup.py

setup.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/qbzr
https://launchpad.net/bzr-explorer

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

  • 1.
    Интернационализация и локализация python-приложений с использованием gettext Александр Бельченко (bialix) http://bialix.com/pycamp/gettext.pdf v.1.2
  • 2.
    Вступительное слово Это будетеще один доклад про Джанго Это доклад про GUI desktop приложения. Частично может быть применимо для web Локализация нужна пользователям Серебряной пули нет (и ложки тоже нет) Пример одного из возможных вариантов использования gettext. Мы используем его в Bazaar GUI
  • 3.
    Стандартный модуль gettext в Python Предоставляет два вида API: Функции GNU gettext API Класс GNUTranslations Функциональное API лишь обертка вокруг классов; в общем случае работает несколько медленнее
  • 4.
    Функции gettext.py Пара функций-переводчиков: gettext — перевод простых строк ngettext — перевод с выбором между единственным и множественным числом Варианты функций с различными префиксами: u — возвращает перевод как unicode строку l — перевод в нужной кодировке d — перевод ищется в указанном домене
  • 5.
    Файлы с переводами Гдеgettext ищет файлы переводов (sys.prefix/share/locale): Linux: /usr/local/share/locale Windows: C:PythonXYsharelocale Путь поиска можно указать вручную Стандартная локация: Linux: /usr/share/locale Windows: ??? {app}/locale
  • 6.
    Язык пользователя Язык дляперевода: Можно указать вручную По умолчанию берется из переменных окружения: LANGUAGE, LC_ALL, LC_MESSAGES, LANG Эти переменные окружения отсутствуют на Windows (сюрприз-сюрприз!)
  • 7.
    Кратко о GNUgettext, PO и MO Библиотека общего назначения: годится для консоли, GUI и web Стандарт де-факто Существует развитая инфраструктура инструментов: специальные редакторы, поддержка в web-сервисах для перевода Файлы: POT — шаблон для перевода PO — файлы перевода на конкретные языки MO — бинарные файлы перевода (runtime)
  • 8.
    Формат PO-файлов 1) Заголовокфайла (информация о переводчике, кодировка, выражение для множественного числа) 2) Тело файла состоит из записей вида: #: foo.py:3 msgid "Hello" msgstr ""
  • 9.
    Работа с GNUgettext утилитами Сбор строк для перевода (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
  • 10.
    Python-утилиты В стандартной поставкеPython: pygettext.py msgfmt.py В production использовать НЕ рекомендую Единственное видимое достоинство pygettext: умение извекать docstrings Недостатки: не знает про ngettext, dgettext Для Windows: http://gnuwin32.sf.net
  • 11.
    Кавалерийская атака натанки Документация на модуль gettext рекомендует очень простой способ включения: import gettext gettext.install('myapp', unicode=True) В коде приложения не надо ничего импортировать и можно делать: s = _('Hello, world!') В чем подвох?
  • 12.
    В чем подвохgettext.install Такой короткий код нормально работает на Linux и в большинстве случаев НЕ работает на Windows Нарушается принцип: явное лучше неявного Перевод строк сразу «включается» для языка пользователя (LANG) Что в свою очередь влияет на юнит-тесты, если вы проверяете строки
  • 13.
  • 14.
    Мы пойдём другимпутём Будем использовать функции и методы модуля gettext напрямую и импортировать имена явно Включать перевод когда это нам нужно Следовать уставу в чужом монастыре
  • 15.
    Использование gettext в среде Windows Кто украл $LANG? (Известно, кто) locale.getdefaultlocale()[0] Получение идентификатора локали LCID (через pywin32 или ctypes): GetUserDefaultLCID() GetSystemDefaultLCID() Преобразование LCID в строку при помощи стандартного модуля locale: locale.windows_locale[lcid]
  • 16.
    Пример готового кода launchpad.net/gettext-py-windows Кратко: importctypes, locale lcid = ctypes.windll.kernel32. GetUserDefaultLCID() lang = locale.windows_locale[lcid]
  • 17.
    Использование методов gettext Работаемс API класса(-ов) GNUTranslations: import gettext as _gettext _t = _gettext.NullTranslations() _t = _gettext.translation('myapp', localedir=xxx, fallback=True)
  • 18.
    Функции-переводчики 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)
  • 19.
    Использование в основномкоде import i18n ... print i18n.gettext('Hello, world!') Либо: from i18n import gettext ... print gettext('Hello, world!')
  • 20.
    Выбор формы множественного числа «Найдено %d документов» Английский: Found 1 document Found 2 documents Русский: Найден 1 документ Найдено 2 документа Найдено 5 документов
  • 21.
    Функция ngettext Сигнатура: ngettext(singular,plural, number) print ngettext('Found %d document', 'Found %d documents', n) % n POT-файл: #: foo.py:7 #, python-format msgid "Found %d document" msgid_plural "Found %d documents" msgstr[0] "" msgstr[1] ""
  • 22.
    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)
  • 23.
    Тестирование интернационализации Используем специальный класс ZzzTranslations(), который декорирует строки Включаем явно через командную строку: python myapp.py --zzz
  • 24.
    Класс 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))
  • 26.
    Инфраструктура проекта Структура каталогов: myapplib/ locale/ ← mo файлы po/ ← pot, po файлы myapp.py setup.py setup.py: build_pot, build_mo
  • 27.
    Нужен ли переводс дефолтного языка на английский? ● Это не шутка, он реально нужен (LANG=en:ja) ● Генерируется автоматически из POT-шаблона утилитой msginit либо msgen
  • 28.
    Строки форматирования Неправильно: 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)
  • 29.
    Web-сервисы для совместной работы над переводами Один из сервисов: переводы на https://translations.launchpad.net/ ●Удобно для разработчиков: все языки в одном месте ●Удобно для переводчиков: подсказки о переводах таких же фраз из других проектов
  • 30.
    Применение gettext вPyQt4? В собственном коде использовать gettext() вместо tr() Формы/диалоги создаваемые в QtDesigner: трансляция *.ui → ui_*.py используется скрипт для автоматической замены вызовов QtGui.QApplication.translate() на gettext()
  • 31.
    Ссылки 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/qbzr https://launchpad.net/bzr-explorer