Spec-Zone.ru › Python 3.5

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

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

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

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

1. 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, то возвращается текущая привязка для домена. [1]

gettext.bind_textdomain_codeset(domain, codeset=None)

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

gettext.textdomain(domain=None)

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

gettext.gettext(message)

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

gettext.dgettext(domain, message)

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

gettext.ngettext(singular, plural, n)

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

Формула множественного числа взята из заголовка каталога. Это выражение 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.'))

2. API на основе классов

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

gettext.find(domain, localedir=None, languages=None, all=False)

Эта функция реализует стандартный алгоритм поиска файла .mo . Она принимает домен, идентичный тому, что принимает textdomain(). Необязательный localedir такой же, как в bindtextdomain() Необязательный языки – это список строк, где каждая строка – это код языка.

Если localedir не указан, используется стандартный системный каталог локали. [2] Если языки не указаны, то ищутся следующие переменные среды: LANGUAGE, LC_ALL, LC_MESSAGES, и LANG. Первая переменная, возвращающая ненулевое значение, используется для переменной языки. Переменные среды должны содержать список языков, разделенных двоеточием, который будет разделен на ожидаемый список строк кодов языка.

find() затем расширяет и нормализует языки, а затем итерируется по ним, ища существующий файл, составленный из этих компонентов:

localedir/language/LC_MESSAGES/domain.mo

Имя первого такого файла, который существует, возвращается функцией find(). Если такой файл не найден, то возвращается None . Если задан all, возвращает список всех имён файлов в порядке их появления в списке языков или переменных среды.

gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False, codeset=None)

Возвращает экземпляр Translations на основе домена, пути_локалей и языков, которые сначала передаются в find() для получения списка связанных путей к файлам .mo. Экземпляры с одинаковыми именами файлов .mo кешируются. Фактический созданный класс — это class_, если он предоставлен, в противном случае GNUTranslations. Конструктор класса должен принимать один аргумент — объект файла объект файла. Если предоставлен codeset, он изменит кодировку, используемую для кодирования переведённых строк в методах lgettext() и lngettext().

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

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

Изменено в версии 3.3: IOError ранее вызывался вместо OSError.

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

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

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

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

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

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

2.1. Класс NullTranslations

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

class gettext.NullTranslations(fp=None)

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

_parse(fp)

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

add_fallback(fallback)

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

gettext(message)

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

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

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

2.2. Класс GNUTranslations

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

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

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

Весь набор пар ключ-значение помещается в словарь и устанавливается как «защищённая» переменная экземпляра *gettext().

Если магическое число файла .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().

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

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

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

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

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

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

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

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

Интернационализация (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' не помечены.

Существует несколько инструментов для извлечения строк, предназначенных для перевода. Исходный 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 в вашем коде зависит от того, интернационализируете ли вы отдельный модуль или всё ваше приложение. В следующих двух разделах будут рассмотрены каждый из этих случаев.

3.1. Локализация вашего модуля

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

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

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

3.2. Локализация вашего приложения

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

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

import gettext
gettext.install('myapplication')

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

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

3.3. Изменение языка на лету

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

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

3.4. Отложенные переводы

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

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 определение _() просто возвращает строку без изменений. И это временное определение будет временно переопределять любое определение _() в встроенном пространстве имён (до тех пор, пока не будет выполнено команда 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().

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

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

  • 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() выше.

© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.5/library/gettext.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API