gettext — Услуги по многоязычной интернационализации

Исходный код: 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)

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

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

gettext.bind_textdomain_codeset(domain, codeset=None)

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

Deprecated since version 3.8, removed in version 3.10.

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

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

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

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()), но перевод ограничивается заданным контекстом сообщения.

Добавлено в версии 3.8.

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, если возникнут проблемы с кодировкой переведенных строк.

Deprecated since version 3.8, removed in version 3.10.

Обратите внимание, что в 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. Она принимает домен, идентичный тому, что принимает 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 не найден, эта функция поднимает OSError, если fallback равен false (что является значением по умолчанию), и возвращает экземпляр NullTranslations, если fallback равен true.

Изменено в версии 3.3: Раньше вместо IOError поднималось OSError.

Deprecated since version 3.8, removed in version 3.10: Параметр codeset.

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

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

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

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

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

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

Deprecated since version 3.8, removed in version 3.10: Параметр codeset.

Класс NullTranslations

Классы трансляции - это то, что фактически реализует трансляцию строк сообщений исходного файла в переведенные строки сообщений. Базовым классом, используемым всеми классами трансляции, является NullTranslations; он предоставляет базовый интерфейс, который можно использовать для написания собственных специализированных классов трансляции. Вот методы класса NullTranslations:

class gettext.NullTranslations(fp=None)

Принимает необязательный file object fp, который игнорируется базовым классом. Инициализирует «защищенные» переменные экземпляра _info и _charset, которые устанавливаются производными классами, а также _fallback, которая устанавливается через add_fallback(). Затем вызывает self._parse(fp), если fp не является None.

_parse(fp)

No-op в базовом классе, этот метод принимает объект файла fp и считывает данные из файла, инициализируя свой каталог сообщений. Если у вас неподдерживаемый формат файла каталога сообщений, вам следует переопределить этот метод для разбора вашего формата.

add_fallback(fallback)

Добавьте fallback в качестве объекта fallback для текущего объекта перевода. Если объект перевода не может предоставить перевод для данного сообщения, он должен обратиться к резервному объекту.

gettext(message)

Если был установлен fallback, перешлите gettext() в fallback. В противном случае возвращается сообщение. Переопределяется в производных классах.

ngettext(singular, plural, n)

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

pgettext(context, message)

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

Добавлено в версии 3.8.

npgettext(context, singular, plural, n)

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

Добавлено в версии 3.8.

lgettext(message)
lngettext(singular, plural, n)

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

Предупреждение

Этих методов следует избегать в Python 3. См. предупреждение для функции lgettext().

Deprecated since version 3.8, removed in version 3.10.

info()

Возвращает переменную «protected» _info, словарь, содержащий метаданные, найденные в файле каталога сообщений.

charset()

Возвращает кодировку файла каталога сообщений.

output_charset()

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

Deprecated since version 3.8, removed in version 3.10.

set_output_charset(charset)

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

Deprecated since version 3.8, removed in version 3.10.

install(names=None)

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

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

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

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

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

Изменено в версии 3.8: Добавлены 'pgettext' и 'npgettext'.

Класс GNUTranslations

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

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

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

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

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

class gettext.GNUTranslations

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

gettext(message)

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

ngettext(singular, plural, n)

Выполните поиск множественной формы для идентификатора сообщения. singular используется в качестве идентификатора сообщения для поиска в каталоге, а n используется для определения формы множественного числа. Возвращаемая строка сообщения является строкой Unicode.

Если идентификатор сообщения не найден в каталоге, и указан fallback, запрос пересылается методу fallback’а ngettext(). В противном случае, если n равно 1, возвращается сингулярное, а во всех остальных случаях возвращается плюральное.

Вот пример:

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, и установлен fallback, поиск передается методу fallback’а pgettext(). В противном случае возвращается идентификатор сообщения.

Добавлено в версии 3.8.

npgettext(context, singular, plural, n)

Выполните поиск множественной формы для идентификатора сообщения. singular используется в качестве идентификатора сообщения для поиска в каталоге, а n используется для определения формы множественного числа.

Если идентификатор сообщения для context не найден в каталоге, и указан fallback, запрос передается методу fallback’а npgettext(). В противном случае, если n равно 1, возвращается сингулярное, а во всех остальных случаях возвращается плюральное.

Добавлено в версии 3.8.

lgettext(message)
lngettext(singular, plural, n)

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

Предупреждение

Этих методов следует избегать в Python 3. См. предупреждение для функции lgettext().

Deprecated since version 3.8, removed in version 3.10.

Поддержка каталога сообщений Solaris

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

Конструктор каталога

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

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

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

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

Интернационализация ваших программ и модулей

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

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

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

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

  4. использовать модуль 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 package.

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

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

Обратите внимание, что второе использование _() не идентифицирует «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().

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

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

  • Питер Функ

  • Джеймс Хенстридж

  • Хуан Давид Ибаньес Паломар

  • Марк-Андре Лембург

  • Мартин фон Лёвис

  • Франсуа Пинар

  • Барри Варшава

  • Густаво Нимейер

Сноски

1

Каталог локали по умолчанию зависит от системы; например, в RedHat Linux это /usr/share/locale, а в Solaris это /usr/lib/locale. Модуль gettext не пытается поддерживать эти системно-зависимые значения по умолчанию; вместо этого по умолчанию используется sys.base_prefix/share/locale (см. sys.base_prefix). По этой причине всегда лучше вызывать bindtextdomain() с явным абсолютным путем в начале вашего приложения.

2

См. сноску для bindtextdomain() выше.

Вернуться на верх