Несмотря на то, что мировая культура в лице Википедии и Пола Маккартни уверяет нас, что Mary had a little lamb, на территории одной восьмой части суши продолжают считать, что на самом деле «У Мэри был ягнёнок». Кто же на самом деле был у Мэри, и как записать это на разных языках мира? Попробуем выяснить это (а также понять, что думают по этому поводу японцы) вместе с нашим любимым Python-ом и встроенным в него модулем поддержки многоязычных переводов gettext.
Начнём с того, что напомним, что библиотека gettext используется для перевода не только программ на Python, а на многих различных языках. Он позволяет использовать в нашей программе шаблоны фраз, которые можно переводить с помощью отдельных и независимых файлов перевода. В самой программе мы, как и прежде, выводим текст сразу на экран, на диск, в логи или ещё куда-нибудь, всего лишь пометив переводимые строки особым образом; библиотека gettext же позволяет взять эти переводимые строки, наборы файлов перевода, и, при наличии подходящего для текущего языка файла перевода, подставить нужную строку.
В Python доступ к механизмам библиотеки gettext осуществляется с помощью идущего в комплекте с Python-ом модуля gettext. Так что не будем путать систему gettext как таковую (внешнюю по отношению к Python-у и совершенно не требующуюся ему для работы сущность; тем не менее, в комплект которой входят удобные утилиты для работы с файлами gettext) и встроенный в Python модуль gettext.
Напишем для начала базовую программу (назовём её mary.py), которую мы и будем пытаться перевести на различные языки:
При использовании модуля gettext принято помечать переводимые строки вызовом функции
Вы уже, наверное, подумали, что сейчас мы будем создавать новый текстовый файл с ассоциациями, в котором надо будет не забыть указать все переводимые строки из программы? В нашем случае таких строк всего 3, но в серьёзной программе их может быть намного больше…
… вы почти угадали. Создавать файл мы будем. Но при этом воспользуемся приятной возможностью системы gettext — анализом файлов с исходниками на предмет переводимых строк. Поскольку мы их благоразумно пометили вызовом функции _() ещё до того, как этот вызов стал всерьёз использовать gettext, теперь синтаксический анализатор может их быстро собрать.
Поскольку система gettext ориентирована для использования в любых языках программирования, в её состав входит программа xgettext, способная сформировать шаблонный файл для перевода из исходников на достаточно большом количестве языков — C, C++, ObjectiveC, C#, Java, Perl, Python, PHP, Lisp… Но это в том случае, если вы не поленились поставить сам комплект программ gettext («aptitude install gettext», или как там в вашем дистрибутиве). Но мы пишем программу на Python-е, который для перевода программ самодостаточен; поэтому мы воспользуемся входящим в состав Python-а скриптом pygettext.py (или pygettext под юниксами).
Запускаем pygettext:
Что это такое? Это шаблон для перевода всей нашей программы. Если у нас есть большая команда переводчиков, то мы можем дать этот шаблон каждому переводчику для каждого целевого языка, и он должен нам будет вернуть заполненный шаблон для его языка. Обычно шаблоны имеют расширение .pot, а заполненные файлы имеют расширение .po.
Синтаксис у файла достаточно прозрачный. Комментарии, пометки авторских прав на перевод, пары из оригинальных строк и переводов. Выкинем из файла всё лишнее, кроме строчки с «Content-Type:» и необходимых для перевода строк, укажем кодировку UTF-8 и напишем переводы:
В нашем случае файл достаточно маленький и простой; будь он посложнее, было бы удобнее использовать специализированные редакторы .po-файлов, наподобие Poedit, или «специализированного редактора всего» Emacs.
Итак, строки в нашей программе мы перевели. Зря, кстати. gettext направлен исключительно на перевод законченных готовых предложений, и перевод отдельных слов и шаблонов предложений в нём делать опасно… (например, gettext совершенно не поддерживает падежи и рода и кое-как поддерживает разве что различение единственного и множественного числа; так что, чтобы подставить вместо Мэри «Таню» или «Свету», придётся учитывать падеж для каждого возможного употребления исходного имени.) Ну да ладно — в нашем случае это не принципиально. Сейчас у нас задача в другом: подготовить файл перевода к использованию.
Использовать исходный текстовый файл было бы неудобно по соображениям производительности (для программ, в которых много переводимого текста), поэтому система gettext использует скомпилированные в специальный формат файлы. Для компиляции мы можем воспользоваться либо тулой msgfmt из комплекта gettext, либо msgfmt.py из комплекта Python (в дебианоподобных дистрибутивах она входит в состав пакета python2.5-examples). Воспользуемся второй:
Ага, видим файл mary.mo. В отличие от mary.po он уже явно не предназначен для ручного редактирования.
Если бы мы подготавливали программу к инсталляции в служебные директории, то мы бы создали примерно такую иерархию (в случае Debian linux): системный каталог /usr/share/locale, в нём подкаталоги для разных языков — ru, en и т.п.; в них — по каталогу LC_MESSAGES, а там уже — файл наподобие mary.mo (с максимально уникальным именем, чтобы не пересечься с другими программами). Но в нашем учебном случае мы просто сделаем подкаталог locale в нашем каталоге, создадим в нём подкаталоги ru/LC_MESSAGES, а в последний уже положим mary.mo.
Теперь наконец добавим в нашу программу поддержку gettext:
Что изменилось? Мы проимпортировали модуль gettext (ну, это очевидно). А ещё мы проинсталлировали в глобальное пространство программы функцию _(), которая для перевода строк в подкаталоге ./locale (второй аргумент) найдёт каталог с нашей текущей локалью (тот самый подкаталог ru), а в его подкаталоге LC_MESSAGES будет искать юникодный (третий аргумент) файл mary.mo перевода программы mary (первый аргумент).
Что имеется в виду под словом «проинсталлировали»? А то, что, после этого действия, мы можем импортировать другие модули нашей программы, и функция _() в них будет уже определена.
Запускаем нашу программу…
Ага. Как-то так.
Согласно Google Translate, .po-файл для японского языка будет выглядеть примерно так:
И для нормальной поддержки японского языка (помимо русского) нам придётся поменять последнюю строку кода на
Проверим в работе:
Приступим
Начнём с того, что напомним, что библиотека 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
メアリーの子羊いた