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