4 мар. 2009 г.

Gnome Internationalization или интернационализация средствами Glib и intltool

Теперь пришло время, добавить в наш шаблон «Интернационализацию». Основное отличие от «Локализации» заключается в том, что приложение, поддерживающее интернационализацию физически представляет собой один исполняемый файл (с сопутствующими ему файлами с данными), которое равно запускается в операционной системе и отображает локализованную информацию в зависимости от настроек ОС. Локализованное приложение отображает только тот язык, для которого оно было создано. Так, например, в Windows часто вы встречали фразы «Скачать русскую версию». Так вот, это были локализованные приложения.

Наше приложение пока отображает только английскую локаль. Попробуем встроить в него интернационализацию управляемую библиотекой GLib. Для начала нам понадобятся пара пакетов: intltool и gettext. Они помогут нам, избавив от некоторой рутинной работы.

Для начала подправим наш исходный код, и доведём его до следующего состояния (жирным я отмечу то, что дописал непосредственно в файл):

Здесь и далее хочу сказать, что в исходных кодах можно использовать как TAB символы, так и пробелы, в шаблонах configure.ac также, НО в Makefile.am только TAB.

#include <config.h>
#include <stdio.h>
#include <glib/gi18n.h> -- поддержка макросов gettext

int main(int argc, char** argv)
{
    setlocale (LC_ALL, ""); -- устанавливаем текущую локаль, информацию берём с переменных окружения (это можно опустить в GUI программировании)
    bindtextdomain (GETTEXT_PACKAGE, QUOD_LOCALEDIR); -- устанавливаем каталог, где лежат файлы для разных языков
    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); -- устанавливаем кодировку для вывода символов (благо Linux легко поддерживает Unicode)
    textdomain (GETTEXT_PACKAGE); -- установка домена по умолчанию

    printf(_("Hello World!\n")); -- отмечаем текст, который надо перевести с помощью макроса _()

    return 0;
}

Почитав повнимательнее, я выяснил, что оказывается сам GLib локализован с помощью механизма gettext, поэтому он принят стандартом де-факто. Существуют разные макросы, выделяющие наш текст, требующий интернационализации. Например, для строк, к которым не может быть применён вызов gettext (например глобальные переменные переменные принимающие строки в массивах), то к ним применяется макрос N_(). Дополнительную информацию можете поискать в самом Glib'e, либо Internationalising GNOME applications, ещё как вариант скачать intltool, и там прочесть HOW-TO. Вообще, информации много, но вот собранной воедино и с нуля — мало.

Следующим этапом идёт создание каталога po, где будут находиться наши переводы. Заодно, подправим наши шаблоны:

~/gnome-quod$ mkdir po

configure.ac
# ======================== initialization ===============================
AC_INIT([Gnome Quod],
        [0.4.0],
        [vest@mail.com],
        [gnome-quod])

AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([-Wall -Werror dist-bzip2])
AC_DEFINE([NDEBUG], [], [Disable debugging information])

# ==================== basic compiler settings ==========================
AC_PROG_CC
AM_PROG_CC_C_O
AC_HEADER_STDC

# ==================== internationalization i18n ========================
IT_PROG_INTLTOOL([0.40.4]) -- требование библиотеки intltool не ниже 0.40.4
GETTEXT_PACKAGE=gnome-quod -- название пакета выносим в отдельную переменную
AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"], -- которую делаем в качестве
                   [The domain to use with gettext]) -- директивы для препроцессора (см. config.h)
AM_GLIB_GNU_GETTEXT -- не знаю до конца, но скорей всего включает поддержку GETTEXT у Glib (или через)
AC_SUBST(GETTEXT_PACKAGE) -- добавляет опции в компилятор для включения gettext

QUOD_LOCALEDIR=[${datadir}/locale] --указываем, где будут находиться локализации
AC_SUBST(QUOD_LOCALEDIR) -- запоминаем этот путь и добавляем его в опции компилятора

PKG_CHECK_MODULES([GLIB],[glib-2.0 >= 2.18]) -- проверяем, что у нас стоит Glib нужной версии

# ==================== generate files ===================================
AC_CONFIG_FILES([
  Makefile
  src/Makefile
  po/Makefile.in -- выводим ещё один шаблон Makefile'a в новом каталоге po
])
AC_OUTPUT

В HOW-TO, я прочитал следующее, что вместо AM_GLIB_GNU_GETTEXT можно использовать AM_GNU_GETTEXT([external]), а сам autoreconf предложил мне воспользоваться AM_GNU_GETTEXT_VERSION. С одним предупреждением у меня получился следующий вариант:

...
AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"],
                   [The domain to use with gettext])
AM_GNU_GETTEXT_VERSION
...

$ autoreconf
...
autoreconf: configure.ac: AM_GNU_GETTEXT_VERSION is used, but not AM_GNU_GETTEXT

с другим вариантом:

autoreconf: configure.ac: AM_GNU_GETTEXT is used, but not AM_GNU_GETTEXT_VERSION
configure.ac:23: required file `./config.guess' not found
configure.ac:23: `automake --add-missing' can install `config.guess'
configure.ac:23: required file `./config.rpath' not found
configure.ac:23: required file `./config.sub' not found
configure.ac:23: `automake --add-missing' can install `config.sub'
configure.ac:23: required file `./ABOUT-NLS' not found

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

// Makefile.am
SUBDIRS = src po -- добавляем лишний каталог

@INTLTOOL_DESKTOP_RULE@ -- этот макрос позволяет intltool определить, где какие файлы требуют перевода (в частности, в корневой директории может находиться будущий ярлык программы)

INTLTOOL_FILES = \
        intltool-extract.in \
        intltool-merge.in \
        intltool-update.in

EXTRA_DIST = $(INTLTOOL_FILES)

DISTCLEANFILES = \
        intltool-extract \
        intltool-merge \
        intltool-update


# здесь я говорю, что вышеследующие файлы (шаблоны) будут распространятся в дистрибутиве без обработки, в случае очистки сгенерированные с шаблонов файлы следует стирать

# эти строки я встречал не всегда. Они отвечают за очистку кеша, создаваемого intltool
clean-local:
        rm -f po/.intltool-merge-cache


// src/Makefile.am
quod_CFLAGS = \ -- задаём флаги для конкретного исполняемого файла (quod)
        -DQUOD_LOCALEDIR=\"${QUOD_LOCALEDIR}\" \ -- где лежат наши локали
        $(GLIB_CFLAGS) \ -- флаги для компиляции с Glib
        -I$(top_srcdir) -- путь, с заголовками уровнем выше (обычно для config.h)

quod_LDADD = \ -- тоже самое, но для линковщика
        $(INTLLIBS) \ -- библиотеки intltool
        $(GLIB_LIBS) -- библиотеки Glib

bin_PROGRAMS = quod
quod_SOURCES = main.c

В предыдущем посте я описывал, почему я использовал макрос AM_PROG_CC_C_O. Именно благодаря ему, я указал для конкретного бинарного файла нужные флаги для компилятора. Теперь следует дописать небольшие файлы в po каталог, и затем приступить к запуску скриптов. Процедура создания файлов немного автоматизирована:

~/gnome-quod$ intltoolize -- этот скрипт создаст файл po/Makefile.in.in

// вручную создаём po/LINGUAS
# please keep this list sorted alphabetically
#
ru -- по алфавиту указываем какие языки у нас будут присутствовать

// вручную создаём po/POTFILES.in
# List of source files containing translatable strings.
# Please keep this file sorted alphabetically.
src/main.c -- здесь описываем все файлы, где нужен перевод (даже файлы с ярлыками для рабочего стола)

// Проверяем:
~/gnome-quod/po$ ls -l
итого 8
-rwxr-xr-x 1 vest vest 51 2008-12-10 10:50 LINGUAS
lrwxrwxrwx 1 vest vest 34 2009-03-04 18:52 Makefile.in.in -> /usr/share/intltool/Makefile.in.in
-rwxr-xr-x 1 vest vest 68 2009-03-04 19:04 POTFILES.in

На самом деле видите, что Makefile.in.in это всего-навсего ссылка. Далее попытаемся создать pot-файл, содержащий строки, нуждающиеся в переводе:

~/gnome-quod/po$ intltool-update --pot -- выполнять это надо внутри каталога po

~/gnome-quod/po$ cat gnome-quod.pot
# SOME DESCRIPTIVE TITLE.
# Copyright (C) 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: 2009-03-04 19:08+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"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: ../src/main.c:13
#, c-format
msgid "Hello World!\n"
msgstr ""

Вот так выглядит шаблон, созданный скриптом. Сам pot-файл не нужен, так как генерируется автоматически. То, что требуется от вас, это сделать копию файла с именем того языка, на который вы будете создавать свою локализацию (в нашем случае ru.po). Сам файл хорошо документирован, вам следует обратить внимание на то, что CHARSET следует исправить в UTF-8. Есть другой способ создать шаблон нужной локализации средствами gettext (но про UTF-8 строки следует не забывать. Помните наши изменения в исходном файле main.c):

~/gnome-quod/po$ msginit --locale=ru
Новый каталог сообщений должен содержать ваш адрес электронной почты,
чтобы пользователи могли присылать свои замечания по поводу ваших
передов, а также чтобы сопроводители программ могли связаться с вами в
том случае, если возникнут непредвиденные технические проблемы.

Which is your email address?
1 Vest@home.tula.net
...
9 vest@vest-desktop

Please choose the number, or enter your email address.
9 -- я ввёл это чисто (просто так, на самом деле введите либо номер либо свой реальный адрес)
A translation team for your language (ru) does not exist yet.
If you want to create a new translation team for ru, please visit
 http://www.iro.umontreal.ca/contrib/po/HTML/teams.html
 http://www.iro.umontreal.ca/contrib/po/HTML/leaders.html
 http://www.iro.umontreal.ca/contrib/po/HTML/index.html

Создано ru.po. -- вот это самое главное.

~/gnome-quod/po$ cat ru.po
# Russian translations for Gnome Quod package.
# Copyright (C) 2009 THE Gnome Quod'S COPYRIGHT HOLDER
# This file is distributed under the same license as the Gnome Quod package.
# Vladislav Volodin <vest@vest-desktop>, 2009.
#
msgid ""
msgstr ""
"Project-Id-Version: Gnome Quod 0.4.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-03-04 19:08+0300\n"
"PO-Revision-Date: 2009-03-04 19:18+0300\n"
"Last-Translator: Vladislav Volodin <vest@vest-desktop>\n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n" -- как видите, это надо поменять на UTF-8
"Content-Transfer-Encoding: 8bit\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"

#: ../src/main.c:13
#, c-format
msgid "Hello World!\n"
msgstr ""

Далее переводим строку следующим образом. Оставляем оригинал вверху, нужный перевод внизу, и всё сохраняем в формате UTF-8:

...
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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"

#: ../src/main.c:13
#, c-format
msgid "Hello World!\n"
msgstr "Привет Мир!\n"

И пробуем реконфигурировать наш проект, собирать, и устанавливать. Остаётся проверить:

vest@vest-desktop:~/gnome-quod$ autoreconf && ./configure && make
...
checking whether NLS is requested... yes
checking for intltool >= 0.40.4... 0.40.5 found
checking for intltool-update... /usr/bin/intltool-update
checking for intltool-merge... /usr/bin/intltool-merge
checking for intltool-extract... /usr/bin/intltool-extract
checking for xgettext... /usr/bin/xgettext
checking for msgmerge... /usr/bin/msgmerge
checking for msgfmt... /usr/bin/msgfmt
checking for gmsgfmt... /usr/bin/msgfmt
...
checking for GLIB... yes
...
# INTLTOOL_MAKEFILE
make all-recursive
make[1]: Вход в каталог `/home/vest/gnome-quod'
Making all in src
make[2]: Вход в каталог `/home/vest/gnome-quod/src'
gcc -DHAVE_CONFIG_H -I. -I.. -DQUOD_LOCALEDIR=\"/usr/local/share/locale\" -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I.. -g -O2 -MT quod-main.o -MD -MP -MF .deps/quod-main.Tpo -c -o quod-main.o `test -f 'main.c' || echo './'`main.c
mv -f .deps/quod-main.Tpo .deps/quod-main.Po
gcc -DQUOD_LOCALEDIR=\"/usr/local/share/locale\" -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I.. -g -O2 -o quod quod-main.o -lglib-2.0
...
~/gnome-quod$ quod
Привет Мир!
$

Теперь можно вас поздравить с тем, что у вас «интернационализовано» ваше приложение. Далее попробуйте уже сами создать пакет с помощью make dist, и просмотреть какие файлы распространяются с пакетом, а какие нет.

И ещё, не забывайте каждый раз обновлять файл POTFILES.in, когда добавляете новые исходные файлы в src каталог. И затем выполняйте intltool-update ru. Она также обновляет файлы с переводом, если у вас добавляются новые строки, указывая вам на число переведённых или не переведённых строк. Смотрите, что будет если подправить исходный код следующим образом:

// main.c
printf(_("Hello World!!\n")); -- лишний восклицательный знак

:~/gnome-quod$ cd po
~/gnome-quod/po$ intltool-update ru
... завершено.
0 переведенных сообщений, 1 неточный перевод.
~/gnome-quod/po$ cat ru.po

#: ../src/main.c:13
#, fuzzy, c-format -- здесь он подправил, что наше исходной сообщение теперь другое. После исправления это слово можно стереть
msgid "Hello World!!\n" -- ведь появился восклицательный знак
msgstr "Привет Мир!!\n" -- добавим его здесь

Вот собственно и всё, жду Ваших комментариев и дополнений.

Комментариев нет: