gettext
— Услуги по многоязычной интернационализации¶
Источник: Lib/gettext.py
Модуль gettext
предоставляет услуги интернационализации (I18N) и локализации (L10N) для ваших модулей и приложений Python. Он поддерживает как API каталога сообщений GNU gettext, так и API более высокого уровня, основанный на классах, который может быть более подходящим для файлов Python. Описанный ниже интерфейс позволяет писать сообщения модулей и приложений на одном естественном языке и предоставлять каталог переведенных сообщений для работы на разных естественных языках.
Также даны некоторые советы по локализации модулей и приложений Python.
GNU gettext API¶
Модуль gettext
определяет следующий API, который очень похож на GNU gettext. API. Если вы используете этот 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.textdomain(domain=None)¶
Изменение или запрос текущего глобального домена. Если domain равен
None
, то возвращается текущий глобальный домен, в противном случае глобальный домен устанавливается в domain, который и возвращается.
- gettext.gettext(message)¶
Возвращает локализованный перевод message, основанный на текущем глобальном домене, языке и каталоге локалей. В локальном пространстве имен эта функция обычно имеет псевдоним
_()
(см. примеры ниже).
- gettext.ngettext(singular, plural, n)¶
Аналогично
gettext()
, но учитывает формы множественного числа. Если перевод найден, примените формулу множественного числа к n и верните полученное сообщение (в некоторых языках есть более двух форм множественного числа). Если перевод не найден, верните singular, если n равно 1; верните plural в противном случае.Формула множественного числа берется из заголовка каталога. Она представляет собой выражение на языке C или Python со свободной переменной n; выражение оценивается как индекс множественного числа в каталоге. Точный синтаксис для использования в файлах
.po
и формулы для различных языков см. в the GNU gettext documentation.
- 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()
), но перевод ограничивается заданным контекстом сообщения.Added in version 3.8.
Обратите внимание, что в GNU gettext также определен метод dcgettext()
, но он был признан бесполезным и в настоящее время не реализован.
Вот пример типичного использования этого API:
import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print(_('This is a translatable string.'))
API на основе классов¶
API модуля gettext
, основанный на классах, обеспечивает большую гибкость и удобство, чем API GNU gettext. API. Это рекомендуемый способ локализации приложений и модулей 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)¶
Возвращает экземпляр
*Translations
, основанный на domain, localedir и languages, которые сначала передаются вfind()
, чтобы получить список связанных.mo
путей к файлам. Экземпляры с одинаковыми.mo
именами файлов кэшируются. Фактический инстанцируемый класс - class_, если он указан, иначеGNUTranslations
. Конструктор класса должен принимать один file object аргумент.Если найдено несколько файлов, более поздние файлы используются в качестве резервных для более ранних. Чтобы можно было установить обратный ход,
copy.copy()
используется для клонирования каждого объекта перевода из кэша; фактические данные экземпляра по-прежнему передаются в кэш.Если файл
.mo
не найден, эта функция поднимает значениеOSError
, если значение fallback равно false (по умолчанию), и возвращает экземплярNullTranslations
, если значение fallback равно true.Изменено в версии 3.11: Параметр codeset удален.
- gettext.install(domain, localedir=None, *, names=None)¶
Это устанавливает функцию
_()
в пространство имен Python builtins, основанное на domain и localedir, которые передаются функцииtranslation()
.Для параметра names см. описание метода
install()
объекта перевода.Как показано ниже, вы обычно помечаете строки в вашем приложении, которые являются кандидатами на перевод, обернув их в вызов функции
_()
, например, так:print(_('This string will be translated.'))
Для удобства вы хотите установить функцию
_()
в пространство имен Python builtins, чтобы она была легко доступна во всех модулях вашего приложения.Изменено в версии 3.11: names теперь является параметром только для ключевых слов.
Класс NullTranslations
¶
Классы трансляции - это то, что фактически реализует трансляцию строк сообщений исходного файла в транслированные строки сообщений. Базовым классом, используемым всеми классами трансляции, является NullTranslations
; он предоставляет базовый интерфейс, который можно использовать для написания собственных специализированных классов трансляции. Вот методы NullTranslations
:
- class gettext.NullTranslations(fp=None)¶
Принимает необязательный file object fp, который игнорируется базовым классом. Инициализирует «защищенные» переменные экземпляра _info и _charset, которые устанавливаются производными классами, а также _fallback, которая устанавливается через
add_fallback()
. Затем вызываетself._parse(fp)
, если fp не являетсяNone
.- _parse(fp)¶
Этот метод, не имеющий аналогов в базовом классе, принимает файловый объект fp и считывает данные из файла, инициализируя свой каталог сообщений. Если у вас неподдерживаемый формат файла каталога сообщений, вам следует переопределить этот метод для разбора вашего формата.
- add_fallback(fallback)¶
Добавьте fallback в качестве объекта резервного копирования для текущего объекта перевода. Объект перевода должен обратиться к резервному объекту, если он не может предоставить перевод для данного сообщения.
- gettext(message)¶
Если был задан обратный ход, перешлите
gettext()
обратному ходу. В противном случае возвращается сообщение. Переопределено в производных классах.
- ngettext(singular, plural, n)¶
Если задан обратный ход, перешлите
ngettext()
в обратный ход. В противном случае, если n равно 1, возвращается singular; в противном случае возвращается plural. Переопределено в производных классах.
- pgettext(context, message)¶
Если задан обратный ход, перешлите
pgettext()
обратному ходу. В противном случае возвращается переведенное сообщение. Переопределено в производных классах.Added in version 3.8.
- npgettext(context, singular, plural, n)¶
Если задан обратный ход, перешлите
npgettext()
обратному ходу. В противном случае возвращается переведенное сообщение. Переопределено в производных классах.Added in version 3.8.
- info()¶
Возвращает словарь, содержащий метаданные, найденные в файле каталога сообщений.
- charset()¶
Возвращает кодировку файла каталога сообщений.
- install(names=None)¶
Этот метод устанавливает
gettext()
во встроенное пространство имен, привязывая его к_
.Если указан параметр names, он должен быть последовательностью, содержащей имена функций, которые вы хотите установить в пространство имен builtins в дополнение к
_()
. Поддерживаются следующие имена:'gettext'
,'ngettext'
,'pgettext'
и'npgettext'
.Обратите внимание, что это лишь один из способов, хотя и самый удобный, сделать функцию
_()
доступной для вашего приложения. Поскольку она глобально влияет на все приложение, и в частности на встроенное пространство имен, локализованные модули никогда не должны устанавливать_()
. Вместо этого они должны использовать этот код, чтобы сделать_()
доступной для своего модуля:import gettext t = gettext.translation('mymodule', ...) _ = t.gettext
Это помещает
_()
только в глобальное пространство имен модуля и, таким образом, влияет только на вызовы внутри этого модуля.Изменено в версии 3.8: Добавлены
'pgettext'
и'npgettext'
.
Класс GNUTranslations
¶
Модуль gettext
предоставляет один дополнительный класс, производный от NullTranslations
: GNUTranslations
. Этот класс переопределяет _parse()
, чтобы обеспечить чтение файлов формата GNU gettext в формате .mo
как с большим, так и с меньшим порядком.
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 используется для определения формы множественного числа. Возвращаемая строка сообщения представляет собой строку Юникода.
Если идентификатор сообщения не найден в каталоге и указан 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. Если в каталоге нет записей для идентификатора сообщения и контекста, и был установлен fallback, поиск передается методу fallback
pgettext()
. В противном случае возвращается идентификатор сообщения.Added in version 3.8.
- npgettext(context, singular, plural, n)¶
Выполните поиск множественных форм для идентификатора сообщения. singular используется в качестве идентификатора сообщения для целей поиска в каталоге, а n используется для определения формы множественного числа.
Если идентификатор сообщения для context не найден в каталоге, и указан fallback, запрос передается методу fallback
npgettext()
. В противном случае, если n равно 1, возвращается сингулярный, а во всех остальных случаях возвращается плюральный.Added in version 3.8.
Поддержка каталога сообщений Solaris¶
Операционная система Solaris определяет свой собственный формат бинарных файлов .mo
, но поскольку документации по этому формату найти не удалось, в настоящее время он не поддерживается.
Конструктор каталога¶
В GNOME используется версия модуля gettext
, созданная Джеймсом Хенстриджем, но эта версия имеет немного другой API. Его использование документировано следующим образом:
import gettext
cat = gettext.Catalog(domain, localedir)
_ = cat.gettext
print(_('hello world'))
Для совместимости с этим старым модулем функция Catalog()
является псевдонимом для функции translation()
, описанной выше.
Одно отличие между этим модулем и модулем Хенстриджа: его объекты каталога поддерживали доступ через API отображения, но этот модуль, похоже, не используется и в настоящее время не поддерживается.
Интернационализация ваших программ и модулей¶
Интернационализация (I18N) - это операция, с помощью которой программа воспринимает несколько языков. Локализация (L10N) - это адаптация вашей программы, после интернационализации, к местному языку и культурным традициям. Чтобы обеспечить многоязычные сообщения для ваших программ на Python, необходимо выполнить следующие действия:
подготовить программу или модуль, специально пометив переводимые строки
запустите набор инструментов для работы с размеченными файлами, чтобы создать каталоги необработанных сообщений
создавать переводы каталогов сообщений на конкретные языки
используйте модуль
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
в своем коде, зависит от того, интернационализируете ли вы один модуль или все приложение. В следующих двух разделах мы рассмотрим каждый из этих случаев.
Локализация вашего модуля¶
Если вы локализуете свой модуль, вы должны следить за тем, чтобы не вносить глобальных изменений, например, во встроенное пространство имен. Вы должны использовать не GNU gettext API, а использовать 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()
.
Благодарности¶
Следующие люди предоставили код, отзывы, предложения по дизайну, предыдущие реализации и ценный опыт для создания этого модуля:
Питер Функ
Джеймс Хенстридж
Хуан Давид Ибаньес Паломар
Марк-Андре Лембург
Мартин фон Лёвис
Франсуа Пинар
Барри Варшава
Густаво Нимейер
Сноски