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

22.1. gettext – Многоязычные службы интернационализацииgettext – Multilingual internationalization services

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


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

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

22.1.1. GNU gettext API

Модуль gettext определяет следующий API, который очень похож на API GNU gettext. При использовании этого API перевод затрагивает всё приложение глобально. Часто это именно то, что нужно, если приложение является одноязычным, а выбор языка зависит от локали пользователя. Если же вы локализуете модуль Python или вашему приложению требуется переключать языки на лету, вероятно, лучше использовать API на основе классов.

gettext.bindtextdomain(domain, localedir=None)

Привязывает domain к каталогу локали localedir. Если точнее, gettext будет искать двоичные файлы .mo для указанного domain по пути (в Unix): localedir/language/LC_MESSAGES/domain.mo, где languages ищется в переменных окружения LANGUAGE, LC_ALL, LC_MESSAGES и LANG соответственно.

Если localedir опущен или равен None, возвращается текущая привязка для domain. [1]

gettext.bind_textdomain_codeset(domain, codeset=None)

Привязывает domain к codeset, изменяя кодировку строк, возвращаемых семейством функций gettext(). Если codeset опущен, возвращается текущая привязка.

gettext.textdomain(domain=None)

Изменяет или запрашивает текущий глобальный domain. Если domain равен None, возвращается текущий глобальный domain; в противном случае глобальный domain устанавливается в domain, который и возвращается.

gettext.gettext(message)

Возвращает локализованный перевод message на основе текущего глобального домена, языка и каталога локали. Эта функция обычно имеет псевдоним _() в локальном пространстве имён (см. примеры ниже).

gettext.lgettext(message)

Эквивалентно gettext(), но перевод возвращается в предпочтительной системной кодировке, если другая кодировка не была явно задана с помощью bind_textdomain_codeset().

gettext.dgettext(domain, message)

Как и gettext(), но ищет сообщение в указанном domain.

gettext.ldgettext(domain, message)

Эквивалентно dgettext(), но перевод возвращается в предпочтительной системной кодировке, если другая кодировка не была явно задана с помощью bind_textdomain_codeset().

gettext.ngettext(singular, plural, n)

Как и gettext(), но учитывает формы множественного числа. Если перевод найден, применяет формулу множественного числа к n и возвращает результирующее сообщение (в некоторых языках более двух форм множественного числа). Если перевод не найден, возвращает singular, если n равно 1; в противном случае возвращает plural.

Формула множественного числа берётся из заголовка каталога. Это выражение на C или Python с переменной n; выражение вычисляет индекс множественного числа в каталоге. Обратитесь к документации GNU gettext за точным синтаксисом, используемым в файлах .po, а также за формулами для разных языков.

gettext.lngettext(singular, plural, n)

Эквивалентно ngettext(), но перевод возвращается в предпочтительной системной кодировке, если другая кодировка не была явно задана с помощью bind_textdomain_codeset().

gettext.dngettext(domain, singular, plural, n)

Как и ngettext(), но ищет сообщение в указанном domain.

gettext.ldngettext(domain, singular, plural, n)

Эквивалентно dngettext(), но перевод возвращается в предпочтительной системной кодировке, если другая кодировка не была явно задана с помощью bind_textdomain_codeset().

Обратите внимание, что 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.'))

22.1.2. API на основе классовClass-based API

API на основе классов модуля gettext даёт больше гибкости и удобства, чем API GNU gettext. Это рекомендуемый способ локализации приложений и модулей Python. gettext определяет класс «translations», который реализует разбор файлов формата GNU .mo и имеет методы для возврата строк. Экземпляры этого класса «translations» также могут устанавливать себя во встроенное пространство имён в качестве функции _().

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. Конструктор класса должен принимать единственный аргумент file object. Если указан codeset, он изменит кодировку, используемую для кодирования переведённых строк в методах lgettext() и lngettext().

Если найдено несколько файлов, более поздние используются как запасные для более ранних. Чтобы разрешить установку запасного варианта, для клонирования каждого объекта перевода из кэша используется copy.copy(); фактические данные экземпляра по-прежнему являются общими с кэшем.

Если файл .mo не найден, эта функция возбуждает IOError, если fallback имеет значение false (по умолчанию), и возвращает экземпляр NullTranslations, если fallback равно true.

gettext.install(domain, localedir=None, codeset=None, names=None)

Эта функция устанавливает функцию _() во встроенное пространство имён Python на основе параметров domain, localedir и codeset, которые передаются функции translation().

Для параметра names обратитесь к описанию метода install() объекта перевода.

Как показано ниже, строки в приложении, предназначенные для перевода, обычно помечаются обёртыванием их в вызов функции _(), например:

print(_('This string will be translated.'))

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

22.1.2.1. Класс 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() запасному объекту. В противном случае вернуть переведённое сообщение. Переопределяется в производных классах.

lgettext(message)

Если установлен запасной вариант, перенаправить вызов lgettext() запасному объекту. В противном случае вернуть переведённое сообщение. Переопределяется в производных классах.

ngettext(singular, plural, n)

Если установлен запасной вариант, перенаправить вызов ngettext() запасному объекту. В противном случае вернуть переведённое сообщение. Переопределяется в производных классах.

lngettext(singular, plural, n)

Если установлен запасной вариант, перенаправить вызов lngettext() запасному объекту. В противном случае вернуть переведённое сообщение. Переопределяется в производных классах.

info()

Возвращает «защищённую» переменную _info.

charset()

Возвращает «защищённую» переменную _charset, которая является кодировкой файла каталога сообщений.

output_charset()

Возвращает «защищённую» переменную _output_charset, которая определяет кодировку для возврата переведённых сообщений в методах lgettext() и lngettext().

set_output_charset(charset)

Изменяет «защищённую» переменную _output_charset, которая определяет кодировку, используемую для возврата переведённых сообщений.

install(names=None)

Этот метод устанавливает self.gettext() во встроенное пространство имён, привязывая его к _.

Если параметр names задан, это должна быть последовательность, содержащая имена функций, которые требуется установить в пространство имён builtins в дополнение к _(). Поддерживаемые имена: 'gettext' (привязывается к self.gettext()), 'ngettext' (привязывается к self.ngettext()), 'lgettext' и 'lngettext'.

Обратите внимание, что это лишь один из способов, хоть и самый удобный, сделать функцию _() доступной в приложении. Поскольку это влияет на всё приложение глобально, и в особенности на встроенное пространство имён, локализованные модули никогда не должны устанавливать _(). Вместо этого им следует использовать следующий код, чтобы сделать _() доступной в своём модуле:

import gettext
t = gettext.translation('mymodule', ...)
_ = t.gettext

Это помещает _() только в глобальное пространство имён модуля и поэтому влияет только на вызовы внутри этого модуля.

22.1.2.2. Класс GNUTranslationsThe GNUTranslations class

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

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

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

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

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

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

GNUTranslations.gettext(message)

Ищет идентификатор message в каталоге и возвращает соответствующую строку сообщения в виде строки Unicode. Если в каталоге нет записи для идентификатора message и установлен запасной вариант, поиск передаётся методу gettext() запасного объекта. В противном случае возвращается идентификатор message.

GNUTranslations.lgettext(message)

Эквивалентно gettext(), но перевод возвращается в виде байтовой строки, закодированной в выбранной выходной кодировке, или в предпочтительной системной кодировке, если кодировка не была явно задана с помощью set_output_charset().

GNUTranslations.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}
GNUTranslations.lngettext(singular, plural, n)

Эквивалентно gettext(), но перевод возвращается в виде байтовой строки, закодированной в выбранной выходной кодировке, или в предпочтительной системной кодировке, если кодировка не была явно задана с помощью set_output_charset().

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

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

22.1.2.4. Конструктор CatalogThe Catalog constructor

В GNOME используется версия модуля gettext от Джеймса Хенстриджа, но эта версия имеет несколько иной API. Её задокументированное использование было:

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

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

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

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

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

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

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

filename = 'mylog.txt'
message = _('writing a log message')
fp = open(filename, 'w')
fp.write(message)
fp.close()

В этом примере строка 'writing a log message' помечена как кандидат на перевод, а строки 'mylog.txt' и 'w' – нет.

Дистрибутив Python включает два инструмента, которые помогают создавать каталоги сообщений после подготовки исходного кода. Они могут быть доступны или не доступны в бинарном дистрибутиве, но их можно найти в дистрибутиве исходного кода, в каталоге Tools/i18n.

Программа pygettext [3] сканирует весь исходный код Python в поиске строк, которые были ранее помечены как переводимые. Она похожа на программу GNU gettext, за исключением того, что понимает все тонкости исходного кода Python, но ничего не знает об исходном коде C или C++. GNU gettext не требуется, если только не переводится код на C (например, модули расширения на C).

pygettext генерирует текстовые файлы каталогов сообщений в стиле Uniforum, удобочитаемые для человека – .pot, по сути структурированные файлы, которые содержат все помеченные строки в исходном коде вместе с заполнителем для строк перевода. pygettext – это скрипт командной строки, поддерживающий интерфейс, похожий на xgettext; подробнее о его использовании можно узнать, запустив:

pygettext.py --help

Копии этих .pot файлов затем передаются отдельным переводчикам, которые создают версии для каждого поддерживаемого естественного языка. Они возвращают заполненные языковые версии в виде файла .po. С помощью программы msgfmt.py [4] (из каталога Tools/i18n) файлы .po от переводчиков преобразуются в машиночитаемые бинарные файлы каталогов .mo. Файлы .mo используются модулем gettext для обработки перевода во время выполнения.

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

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

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

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

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

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

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

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

import gettext
gettext.install('myapplication')

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

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

22.1.3.3. Смена языка на лету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()

22.1.3.4. Отложенные переводы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). Следует соблюдать осторожность, если в локальном пространстве имён существует предыдущее определение _().

Обратите внимание, что второе использование _() не будет распознано программой pygettext как переводимое, поскольку это не строка.

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

def N_(message): return message

animals = [N_('mollusk'),
           N_('albatross'),
           N_('rat'),
           N_('penguin'),
           N_('python'), ]

# ...
for a in animals:
    print(_(a))

В этом случае переводимые строки помечаются функцией N_(), [5] которая не конфликтует ни с одним определением _(). Однако потребуется научить программу извлечения сообщений искать переводимые строки, помеченные N_(). pygettext и xpot поддерживают это с помощью параметров командной строки.

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

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

  • 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.prefix/share/locale. По этой причине в начале приложения всегда лучше вызывать bindtextdomain() с явным абсолютным путём.
[2]См. примечание к bindtextdomain() выше.
[3]Франсуа Пинар написал программу под названием xpot, которая выполняет схожую работу. Она входит в состав его пакета po-utils.
[4]msgfmt.py двоично совместим с GNU msgfmt, за исключением того, что предоставляет более простую реализацию на чистом Python. С помощью этого и pygettext.py обычно не требуется устанавливать пакет GNU gettext для интернационализации приложений Python.
[5]Выбор N_() здесь абсолютно произволен; с тем же успехом это могло бы быть MarkThisStringForTranslation().