Простой пример
Дальше: Совместное использование Вверх: Интернационализация сообщений Назад: Интернационализация сообщений
Простой пример
Сначала мы напишем совсем крохотную программу, а затем снабдим ее механизмами интернационализации. Ограничимся двумя языками, но немного усложним себе задачу тем, что сделаем нашу программу псевдо-интерактивной. Пусть программа будет выдавать в бесконечном цикле какое-либо сообщение с учетом установок locale, сделанных через переменные окружения перед запуском, а при нажатии, например, Ctrl-C изменять язык на ``противоположный'' -- с русского на английский и наоборот.
На самом первом этапе текст программы выглядит так (``реализация'' интернационализации сделана на псевдокоде, который заключен в C-комментарии просто для того, чтобы программа компилировалась):
/* dummy1.c */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> char *message = "This is a message"; char *description = "Ctrl-C handled, language switched. Press Ctrl-\\ to quit."; void switch_message (int sig) { /* * если (текущий язык английский) * установить (русский) * иначе * установить (английский) */ printf ("%s\n", /* перевести (*/ description /*)*/); message = /* перевести (*/ message /*)*/; } int main (int argc, char *argv[]) { signal (SIGINT, switch_message); while (1) { sleep (1); printf ("%s\n", message); } return 0; }
Наша программа содержит две строковые константы, переводы которых нам нужно предоставить. Для генерации шаблона .po-файла воспользуемся программой xgettext пакета gettext. Эта программа извлекает из указанного множества .c- и .h- файлов строковые константы (специальным образом помеченные или может быть все) и создает нужный шаблон .po-файла:
[user@host:somedir] xgettext -a dummy.c -o dummy.po
Мы получили .po-файл, который выглядит следующим образом:
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Free Software Foundation, Inc. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 1999-05-23 11:31+0400\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" #: dummy1.c:8 msgid "This is a message" msgstr "" #: dummy1.c:9 msgid "Ctrl-C handled, language switched. Press Ctrl-\\ to quit." msgstr "" #: dummy1.c:18 dummy1.c:27 #, c-format msgid "%s\n" msgstr ""
Вначале идет некоторый заголовок, (формат его мы обсуждать не будем, однако поля рекомендуется заполнить),а далее последовательность элементов, которые устроены следующим образом:
- после знака #: идет имя файла и номер строки исходного кода, где встречается соответствующая строка; таких вхождений может быть несколько; эта информация является комментарием и не включается в .mo-файл, однако избавляться от нее не следует;
- ``msgid'' -- идентификатор сообщения, обьект, являющийся ключом при поиске перевода; роль ключа по соглашению играет английский вариант строки, хотя это в сущности все равно -- строковые константы в коде могут быть и русскими, и даже на разных языках;
- ``msgstr'' -- ``перевод'' соответствующей строки.
С учетом всего сказанного создадим ``русский'' .po-файл, который назовем ru.po. Он будет выглядеть примерно так (заметим, что последнее вхождение не нуждается в переводе и может быть безболезненно удалено):
# Русский .po-файл для одной глупой программы. # Copyright (C) 1999 Dummy Software Foundation, Inc. # User (user@host), 1999. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: dummy 1.0\n" "POT-Creation-Date: 1999-05-11 06:31+0400\n" "PO-Revision-Date: 1999-4-14 11:51+2\n" "Last-Translator: user <user@host>\n" "Language-Team: ru <ru@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=8-bit\n" "Content-Transfer-Encoding: koi8\n" #: dummy1.c:12 msgid "This is a message" msgstr "Это сообщение" #: dummy2.c:13 msgid "Ctrl-C handled, language switched. Press Ctrl-\\ to quit." msgstr "Перехвачен Ctrl-C, язык переключен. Для выхода нажмите Ctrl-C."
Соответственно ``английский'' .po-файл (en.po) выглядит так:
# Английский .po-файл для одной глупой программы. # Copyright (C) 1999 Dummy Software Foundation, Inc. # User (user@host), 1999. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: dummy 1.0\n" "POT-Creation-Date: 1999-05-11 06:31+0400\n" "PO-Revision-Date: 1999-4-14 11:51+2\n" "Last-Translator: user <user@host>\n" "Language-Team: ru <en@en.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=8-bit\n" "Content-Transfer-Encoding: iso8859-1\n" #: dummy.c:12 msgid "This is a message" msgstr "This is a message" #: dummy.c:13 msgid "Ctrl-C handled, language switched. Press Ctrl-\\ to quit." msgstr "Ctrl-C handled, language switched. Press Ctrl-\\ to quit."
Теперь можно создавать .mo-файлы:
[user@host:somedir] msgfmt ru.po -o ru.mo [user@host:somedir] msgfmt en.po -o en.mo
Данные локализации созданы. Теперь осталось только немного изменить программу.
``Перевод'' (по сути, поиск данных в соответствующем .mo-файле) выполняется функцией gettext(), прототип которой находится в заголовочном файле libintl.h. Если ``перевод'' будет найден, то он возвращается, иначе возвращается ключ. Это допускает, например, отсутствие английского .mo-файла (при условии, что все строковые константы в коде английские). Также будут использованы функции для установки locale, так что придется подключить еще и locale.h.
Функция gettext() для определения того, какой язык является установленным в данный момент, использует переменные окружения LANGUAGE, LC_ALL, LC_xyz (в соответствии с установленным locale), LANG (в порядке убывания приоритета). Будем использовать переменную LANGUAGE. Соответственно псевдокод в теле функции switch_language() нашей программы изменим так (обратите внимание, что первоначальное значение переменной message необходимо сохранить):
void switch_message (int sig) { char *lang; static char *message_key = "This is a message"; if (strcmp ((lang = getenv ("LANGUAGE")), "ru") == 0) setenv ("LANGUAGE", "en", 1); else if (strcmp (lang, "en") == 0) setenv ("LANGUAGE", "ru", 1); printf ("%s\n", gettext (description)); message = gettext (message_key); }
Осталось только инициировать опереации локализации, т.е. указать, где находятся два наших файла с данными локализации и как они называются. Для этого используются функции textdomain() и bindtextdomain(). По соглашению, исходный код (обычно функция main()) содержитт их вызовы в таком порядке:
int main (int argc, char *argv[]) { /* ... */ setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); /* ... */ }
Здесь PACKAGE -- имя программы, а LOCALEDIR -- имя директории, в
соответствующих поддиректориях которой ожидается наличие .mo-файлов. Обычно эти
макросы инициализируются в процессе конфигурации программы при запуске configure, потому
что данные локализации по соглашению устанавливаются в $(prefix)/share/locale. Однако наша
программа вряд ли будет устанавливаться куда-либо таким способом, поэтому в качестве имени
программы (PACKAGE) пусть будет ``dummy'', a значением LOCALEDIR -- текушая рабочая
директория, на которую далее будем ссылаться по имени wd. Данные локализации разумно
вынести в отдельную директорию, которую назовем wd/localedir. Далее в этой директории
нужно создать
по отдельной директории для каждого .po-файла. Так как мы используем два значения
LANGUAGE -- ``ru'' и ``en'',эти директории будут соответственно называться
wd/localedir/en и wd/localedir/ru. Далее в каждой из этих директорий создается по
директории со специальным именем LC_MESSAGES, в которые уже непосредственно помещается
файл с данными локализации, имя которого есть значение макроса PACKAGE с расширением
.mo, т.е dummy.mo в нашем случае. После всех этих манипуляций русскaя локализация
должна находиться в wd/localedir/ru/LC_MESSAGES/dummy.mo, а английская соответственно
в
wd/localedir/en/LC_MESSAGES/dummy.mo. Окончательный исходный текст программы выглядит так:
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <libintl.h> #include <locale.h> #define PACKAGE "dummy" #define LOCALEDIR "localedir" char *message = "This is a message"; char *description = "Ctrl-C handled, language switched. Press Ctrl-\\ to quit."; void switch_message (int sig) { char *lang; static char *message_key = "This is a message"; if (strcmp ((lang = getenv ("LANGUAGE")), "ru") == 0) setenv ("LANGUAGE", "en", 1); else if (strcmp (lang, "en") == 0) setenv ("LANGUAGE", "ru", 1); printf ("%s\n", gettext (description)); message = gettext (message_key); } int main (int argc, char *argv[]) { setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); signal (SIGINT, switch_message); message = gettext (message); while (1) { sleep (1); printf ("%s\n", message); } return 0; }
Теперь установим переменную окружения LANGUAGE
[user@host:somedir] export LANGUAGE=ruи запустим программу. Диалог с ней будет выглядеть примерно так:
[user@host:somedir] ./dummy Это сообщение Это сообщение Это сообщение [Нажатие Ctrl-C] Ctrl-C handled, language switched. Press Ctrl-\ to quit. This is a message This is a message This is a message This is a message This is a message [Нажатие Ctrl-C] Перехвачен Ctrl-C, язык переключен. Для выхода нажмите Ctrl-C. Это сообщение Это сообщение [Нажатие Ctrl-C] Ctrl-C handled, language switched. Press Ctrl-\ to quit. This is a message This is a message This is a message [Нажатие Ctrl-\] Quit [user@host:somedir]
Одно важное замечание. Вообще говоря, в этой программе нарушен принцип полной независимости исходных текстов от данных локализации -- исходный код содержит предположение, что имеются данные локализации для двух языков (английского и русского). Если бы кто-то написал для нашей программы еще одно locale, например французкое, то без изменения исходного текста программа не заговорила бы по-французки. Аналогичная ситуация наблюдается в программах с графическим интерфейсом. Допустим, имеется меню, с помощью которого предлагается выбрать язык. Один пункт меню выбирает соответствующий язык. Сколько же пунктов должно быть в этом меню ?
Напрашивается следующее решение. В процессе инициализации программы надо просмотреть всю директорию LOCALEDIR в поисках .mo-файлов. Так как они должны находиться в директориях, соответствующих названию языка (en, ru, fr и т.п.), то таким образом сначала можно определить, какие locale доступны, подсчитать их общее количество, а затем передать эти данные функции, создающей меню. Похожее решение можно было бы применить и в нашей программе.
Dmitry A. Antipov
1999-05-26