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) -
Привязывает область к каталогу локали localedir. Более конкретно,
gettextбудет искать двоичные.moфайлы для данной области, используя путь (в Unix):localedir/language/LC_MESSAGES/domain.mo, где языки ищутся в переменных окруженияLANGUAGE,LC_ALL,LC_MESSAGES, иLANGсоответственно.Если localedir опущен или
None, то возвращается текущая привязка для domain. 1
-
gettext.bind_textdomain_codeset(domain, codeset=None) -
Привязывает область к codeset, изменяя кодировку байтовых строк, возвращаемых функциями
lgettext(),ldgettext(),lngettext()иldngettext(). Если codeset опущен, то возвращается текущая привязка.
-
gettext.textdomain(domain=None) -
Изменяет или запрашивает текущую глобальную область. Если domain
None, то возвращается текущая глобальная область, в противном случае глобальная область устанавливается на domain, которая и возвращается.
-
gettext.gettext(message) -
Возвращает локализованный перевод message, основанный на текущей глобальной области, языке и каталоге локали. Эта функция обычно алиасится как
_()в локальном пространстве имен (см. примеры ниже).
-
gettext.dgettext(domain, message) -
Как
gettext(), но ищет сообщение в указанной области.
-
gettext.ngettext(singular, plural, n) -
Как
gettext(), но учитывает множественные формы. Если перевод найден, применяет формулу множественного числа к n и возвращает полученное сообщение (некоторые языки имеют более двух множественных форм). Если перевод не найден, возвращает singular, если n равно 1; в противном случае возвращает plural.Формула множественного числа взята из заголовка каталога. Это выражение C или Python, которое имеет свободную переменную n; выражение вычисляется в индекс множественного числа в каталоге. См. документацию GNU gettext для точного синтаксиса, используемого в файлах
.poи формулах для различных языков.
-
gettext.dngettext(domain, singular, plural, n) -
Как
ngettext(), но ищет сообщение в указанной области.
-
gettext.lgettext(message)
-
gettext.ldgettext(domain, message)
-
gettext.lngettext(singular, plural, n)
-
gettext.ldngettext(domain, singular, plural, n) -
Эквивалентно соответствующим функциям без префикса
l(gettext(),dgettext(),ngettext()иdngettext()), но перевод возвращается в виде байтовой строки, закодированной в предпочтительной системной кодировке, если другая кодировка не была явно задана с помощьюbind_textdomain_codeset().Предупреждение
От этих функций следует избегать в Python 3, потому что они возвращают закодированные байты. Гораздо лучше использовать альтернативы, которые возвращают строки Unicode вместо этого, так как большинство приложений Python захотят манипулировать читаемым человеком текстом в виде строк, а не байтов. Кроме того, возможны неожиданные исключения, связанные с Unicode, если есть проблемы с кодировкой в переведённых строках. Возможно, функции
l*()будут устаревшими в будущих версиях Python из-за их собственных проблем и ограничений.
Обратите внимание, что 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, codeset=None) -
Возвращает экземпляр
*Translationsна основе domain, localedir и languages, которые сначала передаются вfind()для получения списка соответствующих путей к файлам.mo. Экземпляры с одинаковыми именами файлов.moкэшируются. Фактически создаваемый класс — class_, если он предоставлен, в противном случаеGNUTranslations. Конструктор класса должен принимать один аргумент — объект файла. Если задан codeset, он изменит кодировку, используемую для кодирования переведённых строк в методахlgettext()иlngettext().Если найдено несколько файлов, более поздние файлы используются как резервные варианты для более ранних. Для настройки резервного копирования используется
copy.copy()для клонирования каждого объекта перевода из кэша; фактические данные экземпляра по-прежнему совместно используются с кэшем.Если файл
.moне найден, эта функция поднимаетOSError, если fallback ложно (по умолчанию), и возвращает экземплярNullTranslations, если fallback истинно.
-
gettext.install(domain, localedir=None, codeset=None, names=None) -
Эта функция устанавливает функцию
_()в пространство имён встроенных функций Python, основываясь на domain, localedir и codeset, которые передаются функцииtranslation().Для параметра names см. описание метода объекта перевода
install().Как показано ниже, вы обычно помечаете строки в своём приложении, которые являются кандидатами для перевода, заключив их в вызов функции
_()следующим образом:print(_('This string will be translated.'))Для удобства вы хотите, чтобы функция
_()была установлена в пространстве имен встроенных функций Python, чтобы она была легко доступна во всех модулях вашего приложения.
Класс 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. Переопределяется в производных классах.
-
lgettext(message)
-
lngettext(singular, plural, n) -
Эквивалентно
gettext()иngettext(), но перевод возвращается в виде байтовой строки, закодированной в предпочтительной кодировке системы, если кодировка не была явно задана с помощьюset_output_charset(). Переопределяется в производных классах.Предупреждение
От этих методов следует избегать в Python 3. См. предупреждение для функции
lgettext().
-
info() -
Возвращает «защищённую» переменную
_info— словарь, содержащий метаданные, найденные в файле каталога сообщений.
-
charset() -
Возвращает кодировку файла каталога сообщений.
-
output_charset() -
Возвращает кодировку, используемую для возврата переведённых сообщений в методах
lgettext()иlngettext().
-
set_output_charset(charset) -
Изменить кодировку, используемую для возврата переведённых сообщений.
-
install(names=None) -
Этот метод устанавливает
gettext()в пространство имён встроенных функций, связывая его с_.Если задан параметр names, он должен быть последовательностью, содержащей имена функций, которые вы хотите установить в пространстве имён встроенных функций, помимо
_(). Поддерживаемые имена —'gettext','ngettext','lgettext'и'lngettext'.Обратите внимание, что это лишь один способ, хотя и самый удобный, сделать функцию
_()доступной для вашего приложения. Поскольку это влияет на всё приложение в целом, а именно на пространство имён встроенных функций, локализованные модули никогда не должны устанавливать_(). Вместо этого они должны использовать этот код, чтобы сделать_()доступным для своего модуля:import gettext t = gettext.translation('mymodule', ...) _ = t.gettextЭто помещает
_()только в глобальное пространство имён модуля, и таким образом влияет только на вызовы внутри этого модуля.
-
Класс GNUTranslations
Модуль gettext предоставляет ещё один класс, производный от NullTranslations: GNUTranslations. Этот класс переопределяет _parse() для поддержки чтения файлов в формате GNU gettext .mo в формате как big-endian, так и little-endian.
GNUTranslations парсит необязательные метаданные из каталога перевода. По соглашению в GNU gettext метаданные включаются как перевод для пустой строки. Эти метаданные находятся в парах вида «ключ-значение» в формате RFC 822, и должны содержать ключ Project-Id-Version . Если найден ключ Content-Type , то свойство charset используется для инициализации «защищённой» переменной экземпляра _charset , по умолчанию принимая значение None если не найдено. Если кодировка символов указана, то все идентификаторы сообщений и строки сообщений, считанные из каталога, преобразуются в Unicode с использованием этой кодировки, в противном случае предполагается ASCII.
Поскольку идентификаторы сообщений также читаются как строки Unicode, все методы *gettext() будут предполагать идентификаторы сообщений как строки Unicode, а не байтовые строки.
Весь набор пар «ключ-значение» помещается в словарь и устанавливается как «защищённая» переменная экземпляра _info.
Если магическое число файла .mo недействительно, номер основной версии ожидается неправильно или при чтении файла возникли другие проблемы, создание экземпляра класса GNUTranslations может вызвать OSError.
-
class gettext.GNUTranslations -
Следующие методы переопределены из реализации базового класса:
-
gettext(message) -
Ищет идентификатор сообщения в каталоге и возвращает соответствующую строку сообщения в виде строки Unicode. Если в каталоге нет записи для идентификатора сообщения, и задан резервный вариант, поиск переадресуется методу
gettext()резервного варианта. В противном случае возвращается идентификатор сообщения.
-
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}
-
lgettext(message)
-
lngettext(singular, plural, n) -
Эквивалентно
gettext()иngettext(), но перевод возвращается как строка байтов, закодированная в предпочтительной кодировке системы, если кодировка не была явно установлена с помощьюset_output_charset().Предупреждение
Эти методы следует избегать в Python 3. См. предупреждение для функции
lgettext().
-
Поддержка каталогов сообщений Solaris
Операционная система Solaris определяет свой собственный двоичный формат файла .mo, но поскольку документация по этому формату отсутствует, он в настоящее время не поддерживается.
Конструктор каталога
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, который компилируется в читаемый машиной двоичный каталог .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')
Если вам нужно задать каталог locale, вы можете передать его в функцию 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))
Это работает, потому что dummy-определение _() просто возвращает строку без изменений. И это dummy-определение временно переопределит любое определение _() в встроенном пространстве имён (до тех пор, пока не будет выполнена команда del). Однако будьте осторожны, если у вас есть предыдущее определение _() в локальном пространстве имён.
Обратите внимание, что второе использование _() не определит «a» как переводимую для программы gettext, потому что параметр не является строковым литералом.
Другой способ решения этой проблемы — использовать следующий пример:
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 -
Директория по умолчанию для локализации зависит от системы; например, в RedHat Linux это
/usr/share/locale, а в Solaris это/usr/lib/locale. Модульgettextне пытается поддерживать эти зависящие от системы значения по умолчанию; вместо этого его значение по умолчаниюsys.base_prefix/share/locale(см.sys.base_prefix). По этой причине всегда лучше вызыватьbindtextdomain()с явным абсолютным путем в начале вашей программы. -
2 -
См. примечание для
bindtextdomain()выше.
© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/gettext.html