Сразу оговорюсь, что python и gtk у меня 2й версии.
Неожиданное желание сделать надписи на главной форме разрабатываемой мной программы не только русскими, но и на других языках (а вдруг она нужна будет не только мне), вынудило начать поиск методов реализации. С наскока найти рабочую документацию по локализации glade форм не удалось, потому было решено написать в будущем эту статью, чтобы другим повезло больше.
Чего нет в этой статье:
— как делать перевод формы в процессе работы. Этого я не смог найти, а хотелось бы знать…
— как делать перевод текста в .py коде в процессе работы.
— пива, блэкджека и остального тут тоже конечно нет.
Перевод будет осуществляться с помощью указания локали при старте (или локали по умолчанию).
Первое что понадобится — готовая glade форма. Т.к. я специально делал простенький тест, чтобы разобраться, то и формочка у меня простая, с меткой, кнопкой и чекбоксом.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.5 on Tue Nov 13 12:44:47 2012 -->
<glade-interface>
<widget class="GtkWindow" id="window1">
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">label text</property>
</widget>
</child>
<child>
<widget class="GtkButton" id="button1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">button text</property>
<property name="response_id">0</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="checkbutton1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">checkbutton text</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>
Также понадобится программа на питоне (раз уж я о нём пишу), которая эту форму показывает:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygtk, gtk, gtk.glade
print "hello to me"
wTree = gtk.glade.XML("localize.glade", "window1")
window = wTree.get_widget("window1")
window.connect("delete_event", gtk.main_quit)
window.show_all()
gtk.main()
Заодно она будет выводить строку в консоль, чтобы показать, что и в консоли язык меняется.
Локализовывать можно по разному. Например, самому писать код обработки каждой строки/виджета и обновлять строки в зависимости от какой-то управляющей команды. В этом есть крошечный плюс — все действия можно производить в любой момент работы программы, но остальное — минусы. И выглядеть и работать это будет ужасно.
Сам собой напрашиватся вопрос — как не изобретать велосипед с квадратными колёсами? Оказалось, что всё уже есть, нужно только уметь воспользоваться (как и в большинстве случаев).
Для начала нужно сбросить настройки локалей в дефолтовые для пользователя (обычно определены в переменной окружения LANG). Это поможет избавиться от возможных проблем в многонитевой программе. Для подобного действия понадобится подключить модуль locale.
locale.setlocale(locale.LC_ALL, '')
Далее будем пользоваться возможностями модуля gettext (потому его тоже придётся подключить). Глянув на его документацию, можно заметить, что ему необходимы некие «binary .mo files».
.mo файлы — это файлы со списком всех переводимых строк программы.
Как их получить:
Сначала нужно выдрать все строки из glade формы (надписи на виджетах), но делать это вручную, конечно же, не стоит. Для этого воспользуемся набором команд intltool:
intltool-extract --type=gettext/glade localize.glade
при желании дополнительные параметры можно посмотреть в man`е. Последний входной параметр — файл glade формы, откуда нужно выдирать текст. Эта команда создаст файл localize.glade.h:
char *s = N_("label text");
char *s = N_("button text");
char *s = N_("checkbutton text");
где, как видно, перечислены все текстовые строки из виджетов формы.
Вспоминаем, что в питоновском файле тоже есть строка, которую хотелось бы переводить. Выдирать её не нужно, надо просто пометить так, чтобы команды локализации поняли, что её нужно переводить. Поэтому документация предлагает записать строку в виде:
print _("hello to me")
т.е. взять в _()
Как можно увидеть выше, в localize.glade.h строки обёрнуты в N_(). Это также своего рода маркер.
Итак, весь необходимый текст помечен и теперь его надо собрать в одном месте. В этом нам поможет команда:
xgettext --language=Python --keyword=_ --keyword=N_ --output=show_form.pot show_form.py localize.glade.h
Опция --keyword показывает программе на какие метки обращать внимание при сборе, потому их тут две "_" и «N_». --output задаёт имя выходного файла, а дальше идёт список всех файлов, где нужно искать метки (можно сделать вывод, что метки могут быть и другими, но я с ними не возился, т.к. это не особо важно).
# Copyright © YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
«Project-Id-Version: PACKAGE VERSIONn»
«Report-Msgid-Bugs-To: n»
«POT-Creation-Date: 2012-11-14 13:54+0300n»
«PO-Revision-Date: YEAR-MO-DA HO:MI+ZONEn»
«Last-Translator: FULL NAME <EMAIL@ADDRESS>n»
«Language-Team: LANGUAGE <LL@li.org>n»
«Language: n»
«MIME-Version: 1.0n»
«Content-Type: text/plain; charset=CHARSETn»
«Content-Transfer-Encoding: 8bitn»
#: show_form.py:14
msgid «hello to me»
msgstr ""
#: localize.glade.h:1
msgid «label text»
msgstr ""
#: localize.glade.h:2
msgid «button text»
msgstr ""
#: localize.glade.h:3
msgid «checkbutton text»
msgstr ""
Это шаблон для всех будущих файлов с переводами. Его редактировать не надо. Теперь настало время определиться с языками. Я использовал английский (en_US), русский (ru) и немецкий (de_DE) (на самом деле из немецкого я знаю только «гитлер капут» и «хандехох» и то не письменно, но до кучи пусть будет). Для каждого из языков нужно из шаблона создать локализованный файл. Это делается командами:
msginit --locale=ru --input=show_form.pot
msginit --locale=en_US --input=show_form.pot
msginit --locale=de_DE --input=show_form.pot
В результате появляется три файла ru.po, de.po и en_US.po. Внутри они почти такие же пустые как и шаблон, но заполнена шапка, правда не совсем теми данными, что мне бы хотелось (возможно чего-то не указал в ключах) и без перевода строк на другие языки (естесственно). Перевод придётся вбивать руками в поля msgstr. Также я поправил charset на utf-8, размер символа на 16 бит и e-mail.
В итоге получилось:
# Copyright © 2012 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# <aaa@bbb>, 2012.
#
msgid ""
msgstr ""
«Project-Id-Version: PACKAGE VERSIONn»
«Report-Msgid-Bugs-To: n»
«POT-Creation-Date: 2012-11-14 13:54+0300n»
«PO-Revision-Date: 2012-11-14 13:58+0300n»
«Last-Translator: <aaa@bbb>n»
«Language-Team: Russiann»
«Language: run»
«MIME-Version: 1.0n»
«Content-Type: text/plain; charset=utf-8n»
«Content-Transfer-Encoding: 16bitn»
«Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11? 0: n%10>=2 && n»
"%10<=4 && (n%100<10 || n%100>=20)? 1: 2);n"
#: show_form.py:14
msgid «hello to me»
msgstr «привет мне»
#: localize.glade.h:1
msgid «label text»
msgstr «метка»
#: localize.glade.h:2
msgid «button text»
msgstr «кнопка»
#: localize.glade.h:3
msgid «checkbutton text»
msgstr «галочка»
# German translations for PACKAGE package.
# Copyright © 2012 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# <aaa@bbb>, 2012.
#
msgid ""
msgstr ""
«Project-Id-Version: PACKAGE VERSIONn»
«Report-Msgid-Bugs-To: n»
«POT-Creation-Date: 2012-11-14 13:54+0300n»
«PO-Revision-Date: 2012-11-14 14:14+0300n»
«Last-Translator: <aaa@bbb>n»
«Language-Team: Germann»
«Language: den»
«MIME-Version: 1.0n»
«Content-Type: text/plain; charset=utf-8n»
«Content-Transfer-Encoding: 16bitn»
«Plural-Forms: nplurals=2; plural=(n != 1);n»
#: show_form.py:14
msgid «hello to me»
msgstr «f»
#: localize.glade.h:1
msgid «label text»
msgstr «d»
#: localize.glade.h:2
msgid «button text»
msgstr «g»
#: localize.glade.h:3
msgid «checkbutton text»
msgstr «e»
# Copyright © 2012 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# <aaa@bbb>, 2012.
#
msgid ""
msgstr ""
«Project-Id-Version: PACKAGE VERSIONn»
«Report-Msgid-Bugs-To: n»
«POT-Creation-Date: 2012-11-14 13:54+0300n»
«PO-Revision-Date: 2012-11-14 13:58+0300n»
«Last-Translator: <aaa@bbb>n»
«Language-Team: Englishn»
«Language: en_USn»
«MIME-Version: 1.0n»
«Content-Type: text/plain; charset=utf-8n»
«Content-Transfer-Encoding: 16bitn»
«Plural-Forms: nplurals=2; plural=(n != 1);n»
#: show_form.py:14
msgid «hello to me»
msgstr «hello to me»
#: localize.glade.h:1
msgid «label text»
msgstr «label text»
#: localize.glade.h:2
msgid «button text»
msgstr «button text»
#: localize.glade.h:3
msgid «checkbutton text»
msgstr «checkbutton text»
Казалось бы — зачем делать родную версию (у меня по умолчанию стоит en_US). Но ведь у другого человека родной может быть, например, de_DE, а он захочет увидеть перевод на английский. Да и разработчики gettext рекомендуют таки создавать.
Некоторые программисты предлагают пользоваться также командой intltool-merge для внесения изменений обратно в форму, но т.к. у меня при этом создавалась точно такая же форма без всяких изменений, то не вижу в ней необходимости.
Итак, есть всё для создания .mo файлов. Это делается командами:
msgfmt ru.po -o locale/ru/LC_MESSAGES/show_form.mo
msgfmt en_US.po -o locale/en_US/LC_MESSAGES/show_form.mo
msgfmt de.po -o locale/de/LC_MESSAGES/show_form.mo
Опция -o (вполне очевидно) указывает каталог, в котором готовый файл будет лежать, причём стоит заметить, что верхний каталог (тут «locale») должен быть один и тот же для всех файлов .mo, а далее должен идти каталог с именем локали (ru, de, en_US, de_DE, ru_RU — т.к. последние два без диалектов, то программы их сокращают до первых букв, но можно использовать и полные имена). Называться он должен так же как указываемый в питоновской программе домен, только с ".mo". Также LC_MESSAGES является одним из нескольких возможных вариантов имени внутреннего каталога (тоже, думаю, лучше использовать одни и те же имена).
Вот что говорит официальная документация по этому поводу:
…
localedir/language/LC_MESSAGES/domain.mo, where languages is searched for in the environment variables LANGUAGE, LC_ALL, LC_MESSAGES, and LANG respectively.
В итоге получились файлы с переводами строк, которые уже можно использовать в программe (такие манипуляции производятся не только для python/glade).
Вернёмся к программе на python`е.
Сперва настроим gettext. После сброса настроек локалей нужно подсказать ему где брать файлы переводов и какие именно файлы. Для этого у меня введены две переменные:
APP="show_form"
DIR="locale"
То, что APP совпадает с названием программы — это осталось от документации, но, думаю, там может быть любое имя. Хотя если смотреть на .mo файлы с названием программы, к которой они относятся, то гораздо проще понимать что к чему.
APP — это имя .mo файлов, DIR — общий каталог с языками. Объяснение этого факта gettext`у производится строками:
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
Теперь надо объяснить питону, что делать со строками вида _(). Для этого "_" присваивается функция взятия перевода из указанного файла. Записать это можно двумя путями:
lang = gettext.translation(APP, DIR)
_ = lang.gettext
или
_ = gettext.gettext
что, если глянуть в код модуля, одно и то же. Так что имеет смысл выбрать запись покороче.
Этого достаточно, чтобы текст в .py файлах выводился на нужном языке. А для локализации glade формы требуется объяснить gtk, где брать перевод и какой:
gtk.glade.bindtextdomain(APP, DIR)
wTree = gtk.glade.XML("localize.glade", "window1", APP)
Итоговый код:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygtk, gtk, gtk.glade
import locale, gettext
APP="show_form"
DIR="locale"
locale.setlocale(locale.LC_ALL, '')
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
_ = gettext.gettext
print _("hello to me")
gtk.glade.bindtextdomain(APP, DIR)
wTree = gtk.glade.XML("localize.glade", "window1", APP)
window = wTree.get_widget("window1")
window.connect("delete_event", gtk.main_quit)
window.show_all()
gtk.main()
Запуск:
LANG=en_US.utf-8 ./show_form.py
LANG=ru_RU.utf-8 ./show_form.py
LANG=de_DE.utf-8 ./show_form.py
А тут честно вылетает ошибка, т.к. немецкая локаль у меня не подключена. Таким образом, для использования локали, её нужно добавить в настройках графической оболочки.
На этом всё.
Автор: huankun