Spec-Zone.ru › Python 3.13

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)

Ищет в каталоге контекст context и идентификатор сообщения message, а затем возвращает соответствующую строку сообщения в виде строки 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, которые компилируются в машиночитаемые двоичные файлы каталогов .mo с помощью программы msgfmt. Файлы .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–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/gettext.html

Spec-Zone.ru

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