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.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, выполните следующие действия:
- подготовьте программу или модуль, специально отметив строки, подлежащие переводу
- запустите набор инструментов для обработки отмеченных файлов и создания исходных каталогов сообщений
- создайте переводы каталогов сообщений для каждого языка
- используйте модуль
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
Сноски
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/gettext.html