Как стать автором
Обновить

Международные ягнята

Время на прочтение5 мин
Количество просмотров19K
Несмотря на то, что мировая культура в лице Википедии и Пола Маккартни уверяет нас, что Mary had a little lamb, на территории одной восьмой части суши продолжают считать, что на самом деле «У Мэри был ягнёнок». Кто же на самом деле был у Мэри, и как записать это на разных языках мира? Попробуем выяснить это (а также понять, что думают по этому поводу японцы) вместе с нашим любимым Python-ом и встроенным в него модулем поддержки многоязычных переводов gettext.

Приступим


Начнём с того, что напомним, что библиотека gettext используется для перевода не только программ на Python, а на многих различных языках. Он позволяет использовать в нашей программе шаблоны фраз, которые можно переводить с помощью отдельных и независимых файлов перевода. В самой программе мы, как и прежде, выводим текст сразу на экран, на диск, в логи или ещё куда-нибудь, всего лишь пометив переводимые строки особым образом; библиотека gettext же позволяет взять эти переводимые строки, наборы файлов перевода, и, при наличии подходящего для текущего языка файла перевода, подставить нужную строку.

В Python доступ к механизмам библиотеки gettext осуществляется с помощью идущего в комплекте с Python-ом модуля gettext. Так что не будем путать систему gettext как таковую (внешнюю по отношению к Python-у и совершенно не требующуюся ему для работы сущность; тем не менее, в комплект которой входят удобные утилиты для работы с файлами gettext) и встроенный в Python модуль gettext.

Напишем для начала базовую программу (назовём её mary.py), которую мы и будем пытаться перевести на различные языки:
#!/usr/bin/python

name = _("Mary")
animal = _("lamb")

print _("%s had a little %s") % (name, animal)


При использовании модуля gettext принято помечать переводимые строки вызовом функции _(). Пока эта функция не определена (впрочем, никто не мешает нам временно определить что-нибудь наподобие_ = lambda x: x), поэтому программа даже наверняка не сможет запуститься… но нам пока и не надо.

Вы уже, наверное, подумали, что сейчас мы будем создавать новый текстовый файл с ассоциациями, в котором надо будет не забыть указать все переводимые строки из программы? В нашем случае таких строк всего 3, но в серьёзной программе их может быть намного больше…

Шаблон перевода: .pot


… вы почти угадали. Создавать файл мы будем. Но при этом воспользуемся приятной возможностью системы gettext — анализом файлов с исходниками на предмет переводимых строк. Поскольку мы их благоразумно пометили вызовом функции _() ещё до того, как этот вызов стал всерьёз использовать gettext, теперь синтаксический анализатор может их быстро собрать.

Поскольку система gettext ориентирована для использования в любых языках программирования, в её состав входит программа xgettext, способная сформировать шаблонный файл для перевода из исходников на достаточно большом количестве языков — C, C++, ObjectiveC, C#, Java, Perl, Python, PHP, Lisp… Но это в том случае, если вы не поленились поставить сам комплект программ gettext («aptitude install gettext», или как там в вашем дистрибутиве). Но мы пишем программу на Python-е, который для перевода программ самодостаточен; поэтому мы воспользуемся входящим в состав Python-а скриптом pygettext.py (или pygettext под юниксами).

Запускаем pygettext: pygettext mary.py. В одном каталоге с нашей программой появился файл messages.pot, содержащий следующее:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2009-10-28 01:12+MSK\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"

#: mary.py:6
msgid "Mary"
msgstr ""

#: mary.py:7
msgid "lamb"
msgstr ""

#: mary.py:10
msgid "%s had a little %s"
msgstr ""


Что это такое? Это шаблон для перевода всей нашей программы. Если у нас есть большая команда переводчиков, то мы можем дать этот шаблон каждому переводчику для каждого целевого языка, и он должен нам будет вернуть заполненный шаблон для его языка. Обычно шаблоны имеют расширение .pot, а заполненные файлы имеют расширение .po.

Синтаксис у файла достаточно прозрачный. Комментарии, пометки авторских прав на перевод, пары из оригинальных строк и переводов. Выкинем из файла всё лишнее, кроме строчки с «Content-Type:» и необходимых для перевода строк, укажем кодировку UTF-8 и напишем переводы:

Файл перевода: .po


msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"

msgid "Mary"
msgstr "Мэри"

msgid "lamb"
msgstr "ягнёнок"

msgid "%s had a little %s"
msgstr "У %s был маленький %s"


В нашем случае файл достаточно маленький и простой; будь он посложнее, было бы удобнее использовать специализированные редакторы .po-файлов, наподобие Poedit, или «специализированного редактора всего» Emacs.

Скомпилированный файл перевода: .mo


Итак, строки в нашей программе мы перевели. Зря, кстати. gettext направлен исключительно на перевод законченных готовых предложений, и перевод отдельных слов и шаблонов предложений в нём делать опасно… (например, gettext совершенно не поддерживает падежи и рода и кое-как поддерживает разве что различение единственного и множественного числа; так что, чтобы подставить вместо Мэри «Таню» или «Свету», придётся учитывать падеж для каждого возможного употребления исходного имени.) Ну да ладно — в нашем случае это не принципиально. Сейчас у нас задача в другом: подготовить файл перевода к использованию.

Использовать исходный текстовый файл было бы неудобно по соображениям производительности (для программ, в которых много переводимого текста), поэтому система gettext использует скомпилированные в специальный формат файлы. Для компиляции мы можем воспользоваться либо тулой msgfmt из комплекта gettext, либо msgfmt.py из комплекта Python (в дебианоподобных дистрибутивах она входит в состав пакета python2.5-examples). Воспользуемся второй:

msgfmt.py mary.po

Ага, видим файл mary.mo. В отличие от mary.po он уже явно не предназначен для ручного редактирования.

Структура каталогов и запуск программы


Если бы мы подготавливали программу к инсталляции в служебные директории, то мы бы создали примерно такую иерархию (в случае Debian linux): системный каталог /usr/share/locale, в нём подкаталоги для разных языков — ru, en и т.п.; в них — по каталогу LC_MESSAGES, а там уже — файл наподобие mary.mo (с максимально уникальным именем, чтобы не пересечься с другими программами). Но в нашем учебном случае мы просто сделаем подкаталог locale в нашем каталоге, создадим в нём подкаталоги ru/LC_MESSAGES, а в последний уже положим mary.mo.

Теперь наконец добавим в нашу программу поддержку gettext:

#!/usr/bin/python
import gettext

gettext.install('mary', './locale', unicode=True)

name = _("Mary")
animal = _("lamb")

print _("%s had a little %s") % (name, animal)


Что изменилось? Мы проимпортировали модуль gettext (ну, это очевидно). А ещё мы проинсталлировали в глобальное пространство программы функцию _(), которая для перевода строк в подкаталоге ./locale (второй аргумент) найдёт каталог с нашей текущей локалью (тот самый подкаталог ru), а в его подкаталоге LC_MESSAGES будет искать юникодный (третий аргумент) файл mary.mo перевода программы mary (первый аргумент).

Что имеется в виду под словом «проинсталлировали»? А то, что, после этого действия, мы можем импортировать другие модули нашей программы, и функция _() в них будет уже определена.

Запускаем нашу программу…

1:/tmp/mary> ./mary.py
У Мэри был маленький ягнёнок


Ага. Как-то так.

Бонус


Согласно Google Translate, .po-файл для японского языка будет выглядеть примерно так:
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"

msgid "Mary"
msgstr "メアリー"

msgid "lamb"
msgstr "子羊"

msgid "%s had a little %s"
msgstr "%sの%sいた"


И для нормальной поддержки японского языка (помимо русского) нам придётся поменять последнюю строку кода на
print (_("%s had a little %s") % (name, animal)).encode('UTF-8')

Проверим в работе:
1:/tmp/mary> LANG=ja_JP.UTF-8 ./mary.py
メアリーの子羊いた
Теги:
Хабы:
Всего голосов 44: ↑38 и ↓6+32
Комментарии30

Публикации

Истории

Работа

Data Scientist
70 вакансий

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань