gettext — Multilingual internationalization services

Вихідний код: 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, LC_ALL, LC_MESSAGES та LANG відповідно.

Якщо localedir пропущено або None, повертається поточне прив’язування для domain. [1]

gettext.textdomain(domain=None)

Змініть або запитайте поточний глобальний домен. Якщо domain має значення None, тоді повертається поточний глобальний домен, інакше глобальний домен встановлюється на domain, який повертається.

gettext.gettext(message)

Return the localized translation of message, based on the current global domain, language, and locale directory. This function is usually aliased as _() in the local namespace (see examples below).

gettext.dgettext(domain, message)

Як gettext(), але шукайте повідомлення у вказаному доміні.

gettext.ngettext(singular, plural, n)

Як gettext(), але враховуйте форми множини. Якщо переклад знайдено, застосуйте формулу множини до n та поверніть отримане повідомлення (деякі мови мають більше двох форм множини). Якщо переклад не знайдено, поверніть singular, якщо n дорівнює 1; повернути множину інакше.

Формула множини взята із заголовка каталогу. Це вираз 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()), але переклад обмежено даним контекстом повідомлення.

Нове в версії 3.8.

Note that GNU gettext also defines a dcgettext() method, but this was deemed not useful and so it is currently unimplemented.

Ось приклад типового використання цього API:

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print(_('This is a translatable string.'))

API на основі класів

The class-based API of the gettext module gives you more flexibility and greater convenience than the GNU gettext API. It is the recommended way of localizing your Python applications and modules. gettext defines a GNUTranslations class which implements the parsing of GNU .mo format files, and has methods for returning strings. Instances of this class can also install themselves in the built-in namespace as the function _().

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

Ця функція реалізує стандартний алгоритм пошуку файлів .mo. Для цього потрібно домен, ідентичний тому, що приймає textdomain(). Необов’язковий localedir такий, як у bindtextdomain(). Необов’язкові мови – це список рядків, де кожен рядок є кодом мови.

Якщо localedir не вказано, використовується каталог локалі системи за замовчуванням. [2] Якщо мови не вказано, шукаються такі змінні середовища: 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)

Return a *Translations instance based on the domain, localedir, and languages, which are first passed to find() to get a list of the associated .mo file paths. Instances with identical .mo file names are cached. The actual class instantiated is class_ if provided, otherwise GNUTranslations. The class’s constructor must take a single file object argument.

Якщо знайдено декілька файлів, пізніші файли використовуються як запасні для попередніх. Щоб дозволити встановлення запасного варіанта, copy.copy() використовується для клонування кожного об’єкта перекладу з кешу; фактичні дані екземпляра все ще використовуються в кеші.

Якщо файл .mo не знайдено, ця функція викликає OSError, якщо fallback має значення false (що є типовим), і повертає екземпляр NullTranslations, якщо fallback є правда.

Змінено в версії 3.3: IOError раніше викликався, тепер це псевдонім OSError.

Змінено в версії 3.11: codeset parameter is removed.

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

This installs the function _() in Python’s builtins namespace, based on domain and localedir which are passed to the function translation().

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

As seen below, you usually mark the strings in your application that are candidates for translation, by wrapping them in a call to the _() function, like this:

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

For convenience, you want the _() function to be installed in Python’s builtins namespace, so it is easily accessible in all modules of your application.

Змінено в версії 3.11: names is now a keyword-only parameter.

Клас 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)

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

gettext(message)

Якщо встановлено запасний варіант, перешліть gettext() на резервний варіант. В іншому випадку поверніть повідомлення. Перевизначено в похідних класах.

ngettext(singular, plural, n)

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

pgettext(context, message)

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

Нове в версії 3.8.

npgettext(context, singular, plural, n)

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

Нове в версії 3.8.

info()

Return a dictionary containing the metadata found in the message catalog file.

charset()

Повернути кодування файлу каталогу повідомлень.

install(names=None)

Цей метод встановлює gettext() у вбудований простір імен, прив’язуючи його до _.

If the names parameter is given, it must be a sequence containing the names of functions you want to install in the builtins namespace in addition to _(). Supported names are 'gettext', 'ngettext', 'pgettext', and 'npgettext'.

Note that this is only one way, albeit the most convenient way, to make the _() function available to your application. Because it affects the entire application globally, and specifically the built-in namespace, localized modules should never install _(). Instead, they should use this code to make _() available to their module:

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

This puts _() only in the module’s global namespace and so only affects calls within this module.

Змінено в версії 3.8: Додано 'pgettext' і 'npgettext'.

Клас GNUTranslations

The gettext module provides one additional class derived from NullTranslations: GNUTranslations. This class overrides _parse() to enable reading GNU gettext format .mo files in both big-endian and little-endian format.

GNUTranslations parses optional metadata out of the translation catalog. It is convention with GNU gettext to include metadata as the translation for the empty string. This metadata is in RFC 822-style key: value pairs, and should contain the Project-Id-Version key. If the key Content-Type is found, then the charset property is used to initialize the «protected» _charset instance variable, defaulting to None if not found. If the charset encoding is specified, then all message ids and message strings read from the catalog are converted to Unicode using this encoding, else ASCII is assumed.

Since message ids are read as Unicode strings too, all *gettext() methods will assume message ids as Unicode strings, not byte strings.

The entire set of key/value pairs are placed into a dictionary and set as the «protected» _info instance variable.

Якщо магічне число файлу .mo недійсне, основний номер версії неочікуваний або якщо під час читання файлу виникають інші проблеми, створення екземпляра класу GNUTranslations може викликати OSError.

class gettext.GNUTranslations

Наступні методи перевизначені з реалізації базового класу:

gettext(message)

Знайдіть ідентифікатор повідомлення в каталозі та поверніть відповідний рядок повідомлення як рядок Unicode. Якщо в каталозі немає запису для ідентифікатора message і встановлено резервний варіант, пошук пересилається до резервного методу gettext(). В іншому випадку повертається ідентифікатор повідомлення.

ngettext(singular, plural, n)

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

Якщо ідентифікатор повідомлення не знайдено в каталозі та вказано запасний варіант, запит пересилається до резервного методу 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)

Знайдіть контекст і ідентифікатор повідомлення в каталозі та поверніть відповідний рядок повідомлення як рядок Unicode. Якщо в каталозі немає запису для ідентифікатора повідомлення та контексту, і було встановлено резервний варіант, пошук пересилається до резервного методу pgettext(). В іншому випадку повертається ідентифікатор повідомлення.

Нове в версії 3.8.

npgettext(context, singular, plural, n)

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

Якщо ідентифікатор повідомлення для контексту не знайдено в каталозі, і вказано резервний варіант, запит пересилається на резервний метод npgettext(). В іншому випадку, коли n дорівнює 1, повертається однина, а множина повертається в усіх інших випадках.

Нове в версії 3.8.

Підтримка каталогу повідомлень Solaris

Операційна система Solaris визначає власний двійковий формат файлу .mo, але оскільки документацію щодо цього формату немає, він наразі не підтримується.

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

GNOME використовує версію модуля gettext від Джеймса Хенстріджа, але ця версія має дещо інший API. Його задокументоване використання:

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

For compatibility with this older module, the function Catalog() is an alias for the translation() function described above.

Одна відмінність між цим модулем і модулем Хенстріджа: його об’єкти каталогу підтримували доступ через API відображення, але він, здається, не використовується, тому наразі не підтримується.

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

Інтернаціоналізація (I18N) відноситься до операції, за допомогою якої програма дізнається про декілька мов. Локалізація (L10N) означає адаптацію вашої програми після її інтернаціоналізації до місцевої мови та культурних звичок. Щоб надати багатомовні повідомлення для своїх програм на Python, потрібно виконати наступні кроки:

  1. підготуйте свою програму або модуль, спеціально позначивши рядки для перекладу

  2. запустіть набір інструментів над позначеними файлами, щоб створити каталоги необроблених повідомлень

  3. створювати переклади каталогів повідомлень на певну мову

  4. використовуйте модуль gettext, щоб рядки повідомлень були правильно перекладені

In order to prepare your code for I18N, you need to look at all the strings in your files. Any string that needs to be translated should be marked by wrapping it in _('...') — that is, a call to the function _. For example:

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

У цьому прикладі рядок «writing a log message» позначено як кандидат на переклад, тоді як рядки «mylog.txt» і «w» — ні.

There are a few tools to extract the strings meant for translation. The original GNU gettext only supported C or C++ source code but its extended version xgettext scans code written in a number of languages, including Python, to find strings marked as translatable. Babel is a Python internationalization library that includes a pybabel script to extract and compile message catalogs. François Pinard’s program called xpot does a similar job and is available as part of his po-utils package.

(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 у своєму коді, залежить від того, чи інтернаціоналізуєте ви один модуль чи всю програму. У наступних двох розділах буде розглянуто кожен випадок.

Локалізація вашого модуля

Якщо ви локалізуєте свій модуль, ви повинні подбати про те, щоб не вносити глобальних змін, напр. до вбудованого простору імен. Вам слід використовувати не GNU gettext API, а замість нього API на основі класів.

Припустімо, що ваш модуль називається «спамом», і різні файли перекладу природної мови модуля .mo містяться в /usr/share/locale у форматі GNU gettext. Ось що ви б розмістили у верхній частині свого модуля:

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

Локалізація вашої програми

If you are localizing your application, you can install the _() function globally into the built-in namespace, usually in the main driver file of your application. This will let all your application-specific files just use _('...') without having to explicitly install it in each file.

У простому випадку вам потрібно лише додати наступний біт коду до основного файлу драйвера вашої програми:

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

This works because the dummy definition of _() simply returns the string unchanged. And this dummy definition will temporarily override any definition of _() in the built-in namespace (until the del command). Take care, though if you have a previous definition of _() in the local namespace.

Note that the second use of _() will not identify «a» as being translatable to the gettext program, because the parameter is not a string literal.

Ще один спосіб вирішити це за допомогою наступного прикладу:

def N_(message): return message

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

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

In this case, you are marking translatable strings with the function N_(), which won’t conflict with any definition of _(). However, you will need to teach your message extraction program to look for translatable strings marked with N_(). xgettext, pygettext, pybabel extract, and xpot all support this through the use of the -k command-line switch. The choice of N_() here is totally arbitrary; it could have just as easily been MarkThisStringForTranslation().

Подяки

Наступні люди надали код, відгуки, пропозиції щодо дизайну, попередні реалізації та цінний досвід для створення цього модуля:

  • Пітер Функ

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

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

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

  • Мартін фон Льовіс

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

  • Баррі Варшава

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

Виноски