Локализация простой pygtk программы c glade формой в Linux

от автора

Сразу оговорюсь, что python и gtk у меня 2й версии.

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

Чего нет в этой статье:
— как делать перевод формы в процессе работы. Этого я не смог найти, а хотелось бы знать…
— как делать перевод текста в .py коде в процессе работы.
— пива, блэкджека и остального тут тоже конечно нет.

Перевод будет осуществляться с помощью указания локали при старте (или локали по умолчанию).


Первое что понадобится — готовая glade форма. Т.к. я специально делал простенький тест, чтобы разобраться, то и формочка у меня простая, с меткой, кнопкой и чекбоксом.

xml код формы

<?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 задаёт имя выходного файла, а дальше идёт список всех файлов, где нужно искать метки (можно сделать вывод, что метки могут быть и другими, но я с ними не возился, т.к. это не особо важно).

В результате получился файл следующего содержания:

# SOME DESCRIPTIVE TITLE.
# 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 VERSION\n»
«Report-Msgid-Bugs-To: \n»
«POT-Creation-Date: 2012-11-14 13:54+0300\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»
«Language: \n»
«MIME-Version: 1.0\n»
«Content-Type: text/plain; charset=CHARSET\n»
«Content-Transfer-Encoding: 8bit\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 ""

Это шаблон для всех будущих файлов с переводами. Его редактировать не надо. Теперь настало время определиться с языками. Я использовал английский (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.

В итоге получилось:

ru.po

# Russian 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 VERSION\n»
«Report-Msgid-Bugs-To: \n»
«POT-Creation-Date: 2012-11-14 13:54+0300\n»
«PO-Revision-Date: 2012-11-14 13:58+0300\n»
«Last-Translator: <aaa@bbb>\n»
«Language-Team: Russian\n»
«Language: ru\n»
«MIME-Version: 1.0\n»
«Content-Type: text/plain; charset=utf-8\n»
«Content-Transfer-Encoding: 16bit\n»
«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 «галочка»

de.po

(да, там не немецкий язык, но это вобщем-то не важно):
# 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 VERSION\n»
«Report-Msgid-Bugs-To: \n»
«POT-Creation-Date: 2012-11-14 13:54+0300\n»
«PO-Revision-Date: 2012-11-14 14:14+0300\n»
«Last-Translator: <aaa@bbb>\n»
«Language-Team: German\n»
«Language: de\n»
«MIME-Version: 1.0\n»
«Content-Type: text/plain; charset=utf-8\n»
«Content-Transfer-Encoding: 16bit\n»
«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»

en_US.po

# English 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 VERSION\n»
«Report-Msgid-Bugs-To: \n»
«POT-Creation-Date: 2012-11-14 13:54+0300\n»
«PO-Revision-Date: 2012-11-14 13:58+0300\n»
«Last-Translator: <aaa@bbb>\n»
«Language-Team: English\n»
«Language: en_US\n»
«MIME-Version: 1.0\n»
«Content-Type: text/plain; charset=utf-8\n»
«Content-Transfer-Encoding: 16bit\n»
«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 

image

LANG=ru_RU.utf-8 ./show_form.py 

image

LANG=de_DE.utf-8 ./show_form.py 

А тут честно вылетает ошибка, т.к. немецкая локаль у меня не подключена. Таким образом, для использования локали, её нужно добавить в настройках графической оболочки.

На этом всё.

ссылка на оригинал статьи http://habrahabr.ru/post/159629/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *