Spec-Zone.ru › Python 3.9

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

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

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

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

API GNU gettext

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

gettext.bindtextdomain(domain, localedir=None)

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

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

gettext.bind_textdomain_codeset(domain, codeset=None)

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

Устарело начиная с версии 3.8, будет удалено в версии 3.10.

gettext.textdomain(domain=None)

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

gettext.gettext(message)

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

gettext.dgettext(domain, message)

Аналогично gettext(), но ищет сообщение в заданном домене.

gettext.ngettext(singular, plural, n)

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

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

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

Аналогично ngettext(), но ищет сообщение в заданном домене.

gettext.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.

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

Устарело начиная с версии 3.8, будет удалено в версии 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, возвращается список всех имён файлов в том порядке, в котором они появляются в списке languages или переменных среды.

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

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

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

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

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

Устарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр codeset.

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

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

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

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

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

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

Устарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр codeset.

Класс NullTranslations

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

class gettext.NullTranslations(fp=None)

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

_parse(fp)

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

add_fallback(fallback)

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

gettext(message)

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

ngettext(singular, plural, n)

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

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

Устарело начиная с версии 3.8, будет удалено в версии 3.10.

info()

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

charset()

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

output_charset()

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

Устарело начиная с версии 3.8, будет удалено в версии 3.10.

set_output_charset(charset)

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

Устарело начиная с версии 3.8, будет удалено в версии 3.10.

install(names=None)

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

Если параметр names задан, он должен быть последовательностью, содержащей имена функций, которые вы хотите установить в пространстве имён встроенных функций помимо _(). Поддерживаемые имена — '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 используется для инициализации переменной экземпляра «защищённого» _charset, по умолчанию None, если не найдено. Если кодировка набора символов указана, все идентификаторы сообщений и строки сообщений, считанные из каталога, преобразуются в Unicode с использованием этой кодировки; в противном случае предполагается ASCII.

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

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

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

class gettext.GNUTranslations

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

gettext(message)

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

ngettext(singular, plural, n)

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

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

Вот пример:

n = len(os.listdir('.'))
cat = GNUTranslations(somefile)
message = cat.ngettext(
    'There is %(num)d file in this directory',
    'There are %(num)d files in this directory',
    n) % {'num': n}
pgettext(context, message)

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

Введено в версии 3.8.

npgettext(context, singular, plural, n)

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

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

Введено в версии 3.8.

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

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

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

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

Устарело начиная с версии 3.8, будет удалено в версии 3.10.

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

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

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

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

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

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

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

Локализация ваших программ и модулей

Локализация (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 также включает чисто-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().

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

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

  • Peter Funk
  • James Henstridge
  • Juan David Ibáñez Palomar
  • Marc-André Lemburg
  • Martin von Löwis
  • François Pinard
  • Barry Warsaw
  • Gustavo Niemeyer

Примечания

1

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

2

См. примечание к bindtextdomain() выше.

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

Spec-Zone.ru

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