Документация Python неофициальный перевод
Содержание страницы

gettext – Многоязычные сервисы интернационализацииgettext – Multilingual internationalization services

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


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

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

GNU gettext API

Модуль 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; выражение вычисляет индекс формы множественного числа в каталоге. См. документацию GNU gettext для точного синтаксиса, используемого в файлах .po, и формулы для различных языков.

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 на основе классовClass-based 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 теперь является параметром только по ключевому слову.

Класс NullTranslationsThe NullTranslations class

Классы перевода – это то, что фактически реализует перевод строк сообщений исходного файла в переведённые строки сообщений. Базовый класс, используемый всеми классами перевода, – 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'.

Класс GNUTranslationsThe GNUTranslations class

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

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

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

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

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

class gettext.GNUTranslations

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

gettext(message, /)

Ищет идентификатор message в каталоге и возвращает соответствующую строку сообщения в виде строки Unicode. Если в каталоге нет записи для идентификатора message и задан резервный источник (fallback), поиск перенаправляется в метод 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.

Поддержка каталогов сообщений SolarisSolaris message catalog support

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

Конструктор каталогаThe Catalog constructor

GNOME использует версию модуля gettext от James Henstridge, но эта версия имеет несколько другой API. Её задокументированное использование было таким:

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

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

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

Интернационализация программ и модулейInternationalizing your programs and modules

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

  1. подготовить программу или модуль, специально помечая переводимые строки

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

  3. создать языковые переводы каталогов сообщений

  4. использовать модуль gettext для корректного перевода строк сообщений

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

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 также есть чисто питоновские версии этих программ: 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, который компилируется в машинно-читаемый двоичный каталог .mo с помощью программы msgfmt. Файлы .mo используются модулем gettext для непосредственного перевода во время выполнения.

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

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

Если вы локализуете свой модуль, нужно соблюдать осторожность, чтобы не вносить глобальные изменения, например во встроенное пространство имён. Следует использовать не API GNU gettext, а API на основе классов.

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

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

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

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

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

import gettext
gettext.install('myapplication')

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

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

Переключение языков на летуChanging languages on the fly

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

import gettext

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

# начните с использования language1
lang1.install()

# ... проходит время, пользователь выбирает language 2
lang2.install()

# ... проходит ещё больше времени, пользователь выбирает language 3
lang3.install()

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

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

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 как переводимое, поскольку параметр не является строковым литералом.

Другой способ – следующий пример:

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().

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

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

  • Peter Funk

  • James Henstridge

  • Juan David Ibáñez Palomar

  • Marc-André Lemburg

  • Martin von Löwis

  • François Pinard

  • Barry Warsaw

  • Gustavo Niemeyer

Сноски