Spec-Zone.ru › Python 3.14

gettext — службы интернационализации для многоязычных приложений

Исходный код: Lib/gettext.py

Модуль gettext предоставляет службы интернационализации (I18N) и локализации (L10N) для модулей и приложений на Python. Он поддерживает как API каталогов сообщений GNU gettext, так и более высокоуровневый API на основе классов, который может лучше подходить для файлов Python. Описанный ниже интерфейс позволяет писать сообщения модулей и приложений на одном естественном языке и предоставлять каталог переведённых сообщений для работы на разных естественных языках.

Также приводятся некоторые советы по локализации модулей и приложений на Python.

API GNU gettext

Модуль gettext определяет следующий API, очень похожий на API GNU gettext. Использование этого API повлияет на перевод всего приложения в целом. Часто это именно то, что нужно для одноязычного приложения, в котором выбор языка зависит от локали пользователя. Если вы локализуете модуль Python или если приложению необходимо переключать языки на лету, вероятно, вам следует использовать API на основе классов.

gettext.bindtextdomain(domain, localedir=None)

Связывает domain с каталогом локалей localedir. Точнее, gettext будет искать двоичные файлы .mo для указанного домена по пути (в Unix): localedir/language/LC_MESSAGES/domain.mo, где language ищется соответственно в переменных окружения LANGUAGE, LC_ALL, LC_MESSAGES и LANG.

Если localedir не указан или равен None, возвращается текущая привязка для domain. [1]

gettext.textdomain(domain=None)

Изменяет текущий глобальный домен или возвращает его значение. Если domain равен None, возвращается текущий глобальный домен; в противном случае глобальный домен устанавливается в значение domain, которое и возвращается.

gettext.gettext(message, /)

Возвращает локализованный перевод message с учётом текущего глобального домена, языка и каталога локалей. Обычно эту функцию связывают с именем _() в локальном пространстве имён (см. примеры ниже).

gettext.dgettext(domain, message, /)

Подобно gettext(), но выполняет поиск сообщения в указанном domain.

gettext.ngettext(singular, plural, n, /)

Подобно gettext(), но учитывает формы множественного числа. Если перевод найден, к n применяется формула множественного числа, и возвращается соответствующее сообщение (в некоторых языках больше двух форм множественного числа). Если перевод не найден, возвращается singular, когда n равно 1, и plural в остальных случаях.

Формула множественного числа берётся из заголовка каталога. Это выражение на C или Python со свободной переменной n; результат вычисления выражения задаёт индекс формы множественного числа в каталоге. Точный синтаксис для файлов .po и формулы для разных языков см. в документации GNU gettext.

gettext.dngettext(domain, singular, plural, n, /)

Подобно ngettext(), но выполняет поиск сообщения в указанном domain.

gettext.pgettext(context, message, /)
gettext.dpgettext(domain, context, message, /)
gettext.npgettext(context, singular, plural, n, /)
gettext.dnpgettext(domain, context, singular, plural, n, /)

Подобны соответствующим функциям без p в префиксе (то есть gettext(), dgettext(), ngettext(), dngettext()), но перевод ограничивается заданным контекстом сообщения context.

Добавлено в версии 3.8.

Обратите внимание, что GNU gettext также определяет метод dcgettext(), но он был признан бесполезным и поэтому пока не реализован.

Пример типичного использования этого API:

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print(_('This is a translatable string.'))

API на основе классов

API на основе классов модуля gettext обеспечивает большую гибкость и удобство по сравнению с API GNU gettext. Это рекомендуемый способ локализации приложений и модулей Python. gettext определяет класс GNUTranslations, который реализует разбор файлов в формате GNU .mo и предоставляет методы для возврата строк. Экземпляры этого класса также могут устанавливать себя во встроенном пространстве имён в качестве функции _().

gettext.find(domain, localedir=None, languages=None, all=False)

Эта функция реализует стандартный алгоритм поиска файлов .mo. Она принимает domain, такой же, как и функция textdomain(). Необязательный параметр localedir используется так же, как в bindtextdomain(). Необязательный параметр languages — это список строк, каждая из которых представляет собой код языка.

Если localedir не указан, используется системный каталог локалей по умолчанию. [2] Если languages не указан, проверяются следующие переменные окружения: LANGUAGE, LC_ALL, LC_MESSAGES и LANG. Для переменной languages используется первая переменная, значение которой не пусто. Переменные окружения должны содержать разделённый двоеточиями список языков; он разбивается по двоеточиям, чтобы получить ожидаемый список строк с кодами языков.

Затем find() разворачивает и нормализует список языков, а после этого перебирает их в поисках существующего файла, составленного из следующих компонентов:

localedir/language/LC_MESSAGES/domain.mo

Первое найденное имя существующего файла возвращается функцией find(). Если подходящий файл не найден, возвращается None. Если задан параметр all, функция возвращает список всех имён файлов в том порядке, в котором они указаны в списке языков или переменных окружения.

gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False)

Возвращает экземпляр *Translations на основе параметров domain, localedir и languages, которые сначала передаются в find() для получения списка соответствующих путей к файлам .mo. Экземпляры с одинаковыми именами файлов .mo кэшируются. Создаваемый класс — это class_, если он указан, или GNUTranslations в противном случае. Конструктор класса должен принимать один аргумент — файловый объект.

Если найдено несколько файлов, более поздние файлы используются как резервные для более ранних. Чтобы можно было задать резервный вариант, для клонирования каждого объекта перевода из кэша используется copy.copy(); фактические данные экземпляра при этом по-прежнему используются совместно с кэшем.

Если файл .mo не найден, эта функция вызывает исключение OSError, если fallback имеет значение false (по умолчанию), и возвращает экземпляр NullTranslations, если fallback имеет значение true.

Изменено в версии 3.3: Ранее вызывалось исключение IOError, теперь это псевдоним OSError.

Изменено в версии 3.11: Параметр codeset удалён.

gettext.install(domain, localedir=None, *, names=None)

Эта функция устанавливает функцию _() во встроенное пространство имён Python на основе параметров domain и localedir, передаваемых функции translation().

Описание параметра names см. в описании метода объекта перевода install().

Как показано ниже, обычно строки приложения, которые следует перевести, отмечают, заключая их в вызов функции _(), например:

print(_('This string will be translated.'))

Для удобства функцию _() следует установить во встроенное пространство имён Python, чтобы она была легко доступна во всех модулях приложения.

Изменено в версии 3.11: Теперь names — параметр, доступный только по имени.

Класс NullTranslations

Классы перевода реализуют собственно преобразование строк сообщений в исходных файлах в переведённые строки сообщений. Базовым классом для всех классов перевода является NullTranslations; он предоставляет базовый интерфейс, который можно использовать для создания собственных специализированных классов перевода. Ниже перечислены методы NullTranslations:

class gettext.NullTranslations(fp=None)

Принимает необязательный файловый объект fp, который базовый класс игнорирует. Инициализирует «защищённые» переменные экземпляра _info и _charset, значения которым присваиваются в производных классах, а также _fallback, значение которой задаётся через add_fallback(). Затем вызывает self._parse(fp), если fp не равен None.

_parse(fp)

В базовом классе этот метод ничего не делает. Он принимает файловый объект fp, считывает данные из файла и инициализирует каталог сообщений. Если формат файла каталога сообщений не поддерживается, следует переопределить этот метод, чтобы реализовать разбор своего формата.

add_fallback(fallback)

Добавляет fallback в качестве резервного объекта для текущего объекта перевода. Объект перевода должен обращаться к резервному объекту, если не может предоставить перевод заданного сообщения.

gettext(message, /)

Если задан резервный объект, перенаправляет gettext() ему. В противном случае возвращает message. Переопределяется в производных классах.

ngettext(singular, plural, n, /)

Если задан резервный объект, перенаправляет ngettext() ему. В противном случае возвращает singular, когда n равно 1, и plural в остальных случаях. Переопределяется в производных классах.

pgettext(context, message, /)

Если задан резервный объект, перенаправляет pgettext() ему. В противном случае возвращает переведённое сообщение. Переопределяется в производных классах.

Добавлено в версии 3.8.

npgettext(context, singular, plural, n, /)

Если задан резервный объект, перенаправляет npgettext() ему. В противном случае возвращает переведённое сообщение. Переопределяется в производных классах.

Добавлено в версии 3.8.

info()

Возвращает словарь с метаданными из файла каталога сообщений.

charset()

Возвращает кодировку файла каталога сообщений.

install(names=None)

Этот метод устанавливает gettext() во встроенное пространство имён, связывая её с _.

Если указан параметр names, он должен быть последовательностью, содержащей имена функций, которые нужно установить во встроенном пространстве имён помимо _(). Поддерживаются имена 'gettext', 'ngettext', 'pgettext' и 'npgettext'.

Обратите внимание, что это лишь один из способов (хотя и самый удобный) сделать функцию _() доступной в приложении. Поскольку она влияет на всё приложение в целом, а точнее, на встроенное пространство имён, локализуемым модулям не следует устанавливать _(). Вместо этого для доступа к _() в модуле следует использовать следующий код:

import gettext
t = gettext.translation('mymodule', ...)
_ = t.gettext

Так _() помещается только в глобальное пространство имён модуля и влияет лишь на вызовы внутри него.

Изменено в версии 3.8: Добавлены 'pgettext' и 'npgettext'.

Класс GNUTranslations

Модуль gettext предоставляет ещё один класс, производный от NullTranslations: GNUTranslations. Этот класс переопределяет _parse(), чтобы поддерживать чтение файлов .mo в формате GNU gettext как с прямым, так и с обратным порядком байтов.

GNUTranslations извлекает необязательные метаданные из каталога перевода. В GNU gettext принято включать метаданные в перевод пустой строки. Эти метаданные представлены парами key: value в стиле RFC 822 и должны содержать ключ Project-Id-Version. Если найден ключ Content-Type, свойство charset используется для инициализации «защищённой» переменной экземпляра _charset; если ключ не найден, используется значение по умолчанию None. Если кодировка указана, все идентификаторы и строки сообщений, считанные из каталога, преобразуются в Unicode с использованием этой кодировки; в противном случае предполагается ASCII.

Поскольку идентификаторы сообщений также считываются как строки Unicode, все методы *gettext() будут считать идентификаторы сообщений строками Unicode, а не байтовыми строками.

Все пары ключ/значение помещаются в словарь, который задаётся в качестве «защищённой» переменной экземпляра _info.

Если неверно магическое число файла .mo, обнаружен неожиданный номер основной версии или возникли другие проблемы при чтении файла, при создании экземпляра класса GNUTranslations может быть вызвано исключение OSError.

class gettext.GNUTranslations

Переопределены следующие методы базового класса:

gettext(message, /)

Ищет в каталоге идентификатор message и возвращает соответствующую строку сообщения в формате Unicode. Если в каталоге нет записи для идентификатора message и задан резервный объект, поиск перенаправляется методу gettext() этого объекта. В противном случае возвращается идентификатор message.

ngettext(singular, plural, n, /)

Выполняет поиск формы множественного числа для идентификатора сообщения. Для поиска в каталоге в качестве идентификатора сообщения используется singular, а параметр n определяет, какую форму множественного числа использовать. Возвращённая строка сообщения имеет формат Unicode.

Если идентификатор сообщения не найден в каталоге и задан резервный объект, запрос перенаправляется методу ngettext() этого объекта. В противном случае, когда n равно 1, возвращается singular, а во всех остальных случаях — plural.

Пример:

n = len(os.listdir('.'))
cat = GNUTranslations(somefile)
message = cat.ngettext(
    'There is %(num)d file in this directory',
    'There are %(num)d files in this directory',
    n) % {'num': n}
pgettext(context, message, /)

Ищет в каталоге идентификатор message и context и возвращает соответствующую строку сообщения в формате Unicode. Если в каталоге нет записи для идентификатора message и контекста context, а резервный объект задан, поиск перенаправляется методу pgettext() этого объекта. В противном случае возвращается идентификатор message.

Добавлено в версии 3.8.

npgettext(context, singular, plural, n, /)

Выполняет поиск формы множественного числа для идентификатора сообщения. Для поиска в каталоге в качестве идентификатора сообщения используется singular, а параметр n определяет, какую форму множественного числа использовать.

Если идентификатор сообщения для контекста context не найден в каталоге и задан резервный объект, запрос перенаправляется методу npgettext() этого объекта. В противном случае, когда n равно 1, возвращается singular, а во всех остальных случаях — plural.

Добавлено в версии 3.8.

Поддержка каталогов сообщений Solaris

Операционная система Solaris определяет собственный двоичный формат файлов .mo, однако документации по этому формату найти не удалось, поэтому сейчас он не поддерживается.

Конструктор Catalog

В GNOME используется версия модуля gettext, созданного Джеймсом Хенстриджем, но в этой версии немного отличается API. Документированное использование выглядело так:

import gettext
cat = gettext.Catalog(domain, localedir)
_ = cat.gettext
print(_('hello world'))

Для совместимости с этим более старым модулем функция Catalog() является псевдонимом функции translation(), описанной выше.

Одно из отличий этого модуля от версии Хенстриджа: его объекты каталогов поддерживали доступ через API отображений, однако, по-видимому, эта возможность не используется и поэтому сейчас не поддерживается.

Интернационализация программ и модулей

Интернационализация (I18N) — это процесс, в ходе которого программа адаптируется для работы с несколькими языками. Локализация (L10N) — это адаптация уже интернационализированной программы к местному языку и культурным особенностям. Чтобы обеспечить поддержку многоязычных сообщений в программах на Python, выполните следующие действия:

  1. подготовьте программу или модуль, специально отметив строки, подлежащие переводу
  2. запустите набор инструментов для обработки отмеченных файлов и создания исходных каталогов сообщений
  3. создайте переводы каталогов сообщений для каждого языка
  4. используйте модуль gettext для правильного перевода строк сообщений

Чтобы подготовить код к I18N, нужно проверить все строки в файлах. Любую строку, которую требуется перевести, следует отметить, обернув её в _('...') — то есть в вызов функции _. Например:

filename = 'mylog.txt'
message = _('writing a log message')
with open(filename, 'w') as fp:
    fp.write(message)

В этом примере строка 'writing a log message' отмечена как подлежащая переводу, а строки 'mylog.txt' и 'w' — нет.

Существует несколько инструментов для извлечения строк, предназначенных для перевода. Исходная программа GNU gettext поддерживала только исходный код на C или C++, но её расширенная версия xgettext сканирует код на нескольких языках, включая Python, и ищет строки, отмеченные как подлежащие переводу. Babel — это библиотека интернационализации для Python, включающая скрипт pybabel для извлечения и компиляции каталогов сообщений. Программа Франсуа Пинара xpot выполняет аналогичную задачу и входит в состав его пакета po-utils.

(В Python также включены написанные на чистом Python версии этих программ — pygettext.py и msgfmt.py; некоторые дистрибутивы Python устанавливают их автоматически. pygettext.py похожа на xgettext, но понимает только исходный код Python и не может обрабатывать другие языки программирования, например C или C++. pygettext.py поддерживает интерфейс командной строки, аналогичный интерфейсу xgettext; чтобы узнать подробности об использовании, выполните pygettext.py --help. msgfmt.py двоично совместима с GNU msgfmt. С этими двумя программами пакет GNU gettext может не понадобиться для интернационализации приложений на Python.)

xgettext, pygettext и подобные инструменты создают файлы .po — каталоги сообщений. Это структурированные файлы в удобочитаемом формате, содержащие все отмеченные строки исходного кода и заполнители для их переводов.

Затем копии этих файлов .po передаются переводчикам, которые создают переводы для каждого поддерживаемого естественного языка. Они возвращают готовые версии для отдельных языков в виде файла <language-name>.po, который компилируется программой msgfmt в двоичный каталог .mo, доступный для машинной обработки. Файлы .mo используются модулем gettext для непосредственной обработки переводов во время выполнения.

Способ использования модуля gettext в коде зависит от того, интернационализируете ли вы один модуль или всё приложение. В следующих двух разделах рассматривается каждый из этих случаев.

Локализация модуля

При локализации модуля необходимо избегать глобальных изменений, например в пространстве имён встроенных объектов. Не следует использовать API GNU gettext; вместо него используйте API на основе классов.

Предположим, ваш модуль называется «spam», а файлы переводов .mo на разные естественные языки находятся в /usr/share/locale в формате GNU gettext. В начало модуля нужно поместить следующее:

import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.gettext

Локализация приложения

При локализации приложения функцию _() можно установить глобально в пространство имён встроенных объектов, обычно в главный файл запуска приложения. Тогда во всех файлах приложения можно будет использовать _('...'), не устанавливая её явно в каждом файле.

В простом случае достаточно добавить следующий фрагмент кода в главный файл запуска приложения:

import gettext
gettext.install('myapplication')

Если нужно указать каталог локали, его можно передать функции install():

import gettext
gettext.install('myapplication', '/usr/share/locale')

Переключение языков на лету

Если программе необходимо одновременно поддерживать множество языков, можно создать несколько экземпляров переводчика, а затем явно переключаться между ними, например так:

import gettext

lang1 = gettext.translation('myapplication', languages=['en'])
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])

# start by using language1
lang1.install()

# ... time goes by, user selects language 2
lang2.install()

# ... more time goes by, user selects language 3
lang3.install()

Отложенные переводы

В большинстве случаев программирования строки переводятся непосредственно в месте их написания. Однако иногда требуется отметить строки для перевода, отложив сам перевод на более поздний срок. Классический пример:

animals = ['mollusk',
           'albatross',
           'rat',
           'penguin',
           'python', ]
# ...
for a in animals:
    print(a)

В этом случае нужно отметить строки в списке animals как подлежащие переводу, но перевести их только при выводе на печать.

Вот один из способов решения этой задачи:

def _(message): return message

animals = [_('mollusk'),
           _('albatross'),
           _('rat'),
           _('penguin'),
           _('python'), ]

del _

# ...
for a in animals:
    print(_(a))

Это работает, потому что фиктивное определение _() просто возвращает строку без изменений. Это фиктивное определение временно переопределяет любое определение _() в пространстве имён встроенных объектов (до команды del). Однако будьте осторожны, если в локальном пространстве имён уже есть предыдущее определение _().

Обратите внимание, что второе использование _() не позволит программе gettext распознать «a» как строку, подлежащую переводу, поскольку параметр не является строковым литералом.

Другой способ решения этой задачи показан в следующем примере:

def N_(message): return message

animals = [N_('mollusk'),
           N_('albatross'),
           N_('rat'),
           N_('penguin'),
           N_('python'), ]

# ...
for a in animals:
    print(_(a))

В этом случае строки, подлежащие переводу, отмечаются функцией N_(), которая не будет конфликтовать с каким-либо определением _(). Однако потребуется настроить программу извлечения сообщений так, чтобы она находила строки, отмеченные как подлежащие переводу с помощью N_(). xgettext, pygettext, pybabel extract и xpot поддерживают это с помощью параметра командной строки -k. Выбор N_() совершенно произволен; с тем же успехом можно было выбрать MarkThisStringForTranslation().

Благодарности

Следующие люди внесли вклад в создание этого модуля, предоставив код, отзывы, предложения по проектированию, предыдущие реализации и ценный опыт:

  • Peter Funk
  • James Henstridge
  • Juan David Ibáñez Palomar
  • Marc-André Lemburg
  • Martin von Löwis
  • François Pinard
  • Barry Warsaw
  • Gustavo Niemeyer

Сноски

[1]

Каталог локали по умолчанию зависит от системы; например, в Red Hat Linux это /usr/share/locale, а в Solaris — /usr/lib/locale. Модуль gettext не пытается поддерживать значения по умолчанию, зависящие от системы; вместо этого он использует sys.base_prefix/share/locale (см. sys.base_prefix). Поэтому в начале приложения всегда лучше вызывать bindtextdomain(), указав абсолютный путь явно.

[2]

См. приведённую выше сноску к bindtextdomain().

© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/gettext.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API