Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Программирование с использованием gtkmm 3. Интернационализация и локализация

Оригинал: Programming with gtkmm 3
Авторы: Murray Cumming, Bernhard Rieder, Jonathon Jongsma, Ole Laursen, Marko Anastasov, Daniel Elstner, Chris Vine, David King, Pedro Ferreira, Kjell Ahlstedt
Дата публикации: 15 Октября 2013 г.
Перевод: А.Панин
Дата перевода: 10 Апреля 2014 г.

27. Интернационализация и локализация

Приложения на основе gtkmm могут без лишних сложностей поддерживать множество языков, включая такие неевропейские языки, как китайский, а также такие языки с письмом справа налево, как арабский. Корректно разработанное и переведенное приложение на основе gtkmm будет выбирать подходящий язык в процессе работы на основе настроек пользовательского окружения.

Процесс разработки пригодного для перевода исходного кода называется интернационализацией (internationalization), а для его обозначения используется аббревиатура i18n. Процесс локализации (localization), для обозначения которого иногда используется аббревиатура l10n, заключается в переводе на другие языки текста, сформированного благодаря обработке данного исходного кода.

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

Для записи строк в исходном коде должен использоваться английский язык, причем все строки должны быть помещены в макрос. После этого утилита gettext (или intltool) сможет извлечь отмеченные строки для перевода, а также появится возможность подстановки переведенного текста в процессе работы приложения.

27.1. Подготовка вашего проекта

В инструкциях ниже предполагается, что вы не будете использовать утилиту gettext напрямую, а вместо нее будете использовать утилиту intltool, которая была разработана специально для проекта GNOME. Утилита intltool использует утилиту gettext, которая, в свою очередь, извлекает строки из файла исходного кода, но при этом intltool также может объединять строки из различных файлов, например, из файлов меню рабочего стола и файлов описания графического интерфейса пользователя, таких, как файлы приложения Glade и помещать их в файлы стандартного формата gettext с расширениями .pot/.po.

Мы также предполагаем, что вы используете утилиты из набора autotools (т.е., утилиты automake и autoconf) для сборки вашего проекта, причем вы используете сценарий http://git.gnome.org/browse/gnome-common/tree/autogen.sh, который, в том числе, заботится об инициализации утилиты intltool.

Следует создать поддиректорию с именем po в корневой директории вашего проекта. Эта директория в конечном счете будет содержать все ваши переводы. В ней следует создать файл с именем LINGUAS и файл с именем POTFILES.in. Обычной практикой является также создание файла с именем Changelog в директории po для того, чтобы переводчики имели возможность отслеживать изменения в переводах.

Файл LINGUAS содержит отсортированный по алфавиту список кодов, идентифицирующих языки, на которые переведена ваша программа (строки комментариев, начинающиеся с символа #, игнорируются). Каждый код языка, находящийся в файле LINGUAS, должен соответствовать файлу с расширением .po. Таким образом, в том случае, если ваша программа переведена на немецкий и японский языки, ваш файл LINGUAS должен выглядеть примерно следующим образом:
# данный список файлов должен быть отсортирован по алфавиту и содержать по одному коду в строке
de
ja

(В дополнение в вашей директории po должны находится файлы ja.po и de.po, которые должны содержать переводы на немецкий и японский языки соответственно.)

Файл POTFILES.in является списком путей ко всем файлам, которые содержат отмеченные для перевода строки, начиная с корневой директории проекта. Таким образом, к примеру, в том случае, если исходные коды вашего проекта были размещены в поддиректории с именем src и у вас есть два файла, которые содержат строки для перевода, ваш файл POTFILES.in должен выглядеть примерно следующим образом:
src/main.cc
src/other.cc

В том случае, если вы используете утилиту gettext напрямую, вы можете извлечь строки для перевода только в том случае, если они расположены в файле исходного кода. Однако, в том случае, если вы используете утилиту intltool, вы можете извлечь строки для перевода из файлов множества других форматов, включая файлы описания пользовательского интерфейса Glade, файлы формата xml, файлы меню с расширением .desktop и файлы некоторых других форматов. Следовательно, в том случае, если вы спроектировали часть пользовательского интерфейса приложения с помощью приложения Glade, вы также можете добавить ваши файлы с расширением .glade в список, расположенный в файле POTFILES.in.

Теперь, когда у вас есть место для размещения переводов, требуется инициализировать утилиты intltool и gettext. Добавьте следующий код в ваш файл configure.ac, заменив 'programname' на имя вашей программы:
IT_PROG_INTLTOOL([0.35.0])

GETTEXT_PACKAGE=programname
AC_SUBST(GETTEXT_PACKAGE)
AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"],
                   [Домен для использования совместно с gettext])
AM_GLIB_GNU_GETTEXT

PROGRAMNAME_LOCALEDIR=[${datadir}/locale]
AC_SUBST(PROGRAMNAME_LOCALEDIR)

В данном случае переменная PROGRAMNAME_LOCALEDIR будет использоваться позднее в файле Makefile.am для описания макроса, который, в свою очередь, будет использоваться при инициализации библиотеки gettext в вашем исходном коде.

В расположенном уровнем выше файле Makefile.am следует:
  • Добавить po в качестве параметра переменной SUBDIRS. Без этого ваши переводы не будут скомпилированы и установлены в процессе сборки вашей программы.
  • Объявить макрос INTLTOOL_FILES как:
    INTLTOOL_FILES = intltool-extract.in \
                     intltool-merge.in \
                     intltool-update.in
    
  • Добавить макрос INTLTOOL_FILES в список файлов EXTRA_DIST. Это гарантирует то, что в момент выполнения команды make dist, описанные команды будут включены в архив исходного кода.
  • Обновить заначение макроса DISTCLEANFILES:
    DISTCLEANFILES = ... intltool-extract \
                     intltool-merge \
                     intltool-update \
                     po/.intltool-merge-cache
    
В вашем файле src/Makefile следует обновить описание макроса AM_CPPFLAGS, добавив следующее описание макроса препроцессора:
AM_CPPFLAGS = ... -DPROGRAMNAME_LOCALEDIR=\"${PROGRAMNAME_LOCALEDIR}\"

Этот макрос будет использован в момент инициализации библиотеки gettext в вашем исходном коде.

27.2. Отметка строк для перевода

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

Пакет GNU gettext позволяет вам отмечать строки в исходном коде, извлекать эти строки для перевода и использовать переведенные строки в вашем приложении.

Однако, библиотека Glib включает макросы для поддержки функции gettext(), являющиеся укороченными обертками, которые проще использовать. Для использования этих макросов следует подключить заголовочный файл <glibmm/i18n.h>, после чего, к примеру, заменить вызов:
display_message("Getting ready for i18n.");
на вызов:
display_message(_("Getting ready for i18n."));
Следует упомянуть о том, что есть возможность генерации файла, который будет содержать все строки из файлов исходного кода вашего приложения, даже тех, которые не отмечены для перевода, вместе с именами файлов и ссылками на номера строк. Для генерации такого файла с именем my-strings выполните следующую команду в директории исходного кода:
xgettext -a -o my-strings --omit-header *.cc *.h
Наконец, для того, чтобы ваша программа использовала перевод, соответствующий текущей системной локализации, добавьте данный код в начало вашего файла исходного кода main.cc для инициализации библиотеки gettext:
bindtextdomain(GETTEXT_PACKAGE, PROGRAMNAME_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
textdomain(GETTEXT_PACKAGE);

27.2.1. Как работает пакет gettext

Сценарий intltool/xgettext извлекает строки и помещает их в файл <имя программы>.pot. Переводчики вашего приложения создают свои переводы, копируя этот файл с расширением .pot и переименовывая его в <имя локализации>.po. Локализация устанавливает язык и кодировку для данного языка, включая форматы даты и чисел. Позднее, когда текст в вашем файле исходного кода изменяется, сценарий msmerge может использоваться для обновления файлов <имя локализации>.po на основе повторно сгенерированного файла с расширением .pot.

В процессе установки файлы с расширением .po конвертируются в файлы бинарного формата (с расширением .mo), которые размещаются в системной директории для файлов локализации, например, в директории /usr/share/locale/.

При запуске приложения библиотека gettext проверяет системную директорию в поисках файла с расширением .mo, соответствующего локализации пользовательского окружения (вы можете изменить текущую локализацию, к примеру, выполнив команду "export LANG=de_DE.UTF-8" в консоли bash). Позднее, в момент, когда в программе будет осуществлен вызов функции gettext(), будет выполнен поиск перевода определенной строки. В том случае, если строка не найдена, будет использована оригинальная строка.

27.2.2. Тестирование и добавление переводов

Чтобы убедиться в том, что вы сделали все правильно, вы можете добавить перевод для новой локализации. Для этого следует перейти в поддиректорию вашего проекта и выполнить следующую команду:
intltool-update --pot

С помощью данной команды будет создан файл с именем <имя программы>.pot. Теперь следует скопировать этот файл и переименовать его в <код языка>.po, например в de.po или hu.po. Также следует добавить данный код языка в файл LINGUAS. Файл с расширением .po содержит заголовок и список строк на английском языке с полями для переводов этих строк, которые должны быть заполнены. Убедитесь в том, что вы установили кодировку UTF-8 для файла с расширением .po (она задается в заголовке, причем все строки также должны использовать эту кодировку).

Возможны ситуации, в которых определенные строки в файле с расширением .po будут иметь пометку "fuzzy". Эти переведенные строки не будут подменять оригинальные строки. Для того, чтобы они использовались, просто уберите пометки "fuzzy".

27.2.3. Ресурсы

27.3. Использование кодировки UTF8

Корректно интернационализированное приложение не должно делать предположений о количестве байт, использующихся для кодирования одного символа строки. Это значит, что вы не должны использовать арифметику с указателями для обхода символов в строке и это также значит, что вы не должны использовать класс std::string и такие стандартные функции языка C, как strlen(), так как они делают такие же предположения.

Однако, вы, скорее всего, уже избегаете использования обычных символьных массивов char* и арифметики с указателями, используя класс std::string, поэтому вам придется всего лишь перейти к использованию класса Glib::ustring вместо него. Обратитесь к главе "Базовые сведения" для получения информации о классе Glib::ustring.

27.3.1. Классы Glib::ustring и std::iostreams

К сожалению, интеграция со стандартными потоками ввода-вывода выполнена не полностью надежно. gtkmm конвертирует строки типа Glib::ustring в специфичную для локализации кодировку (которая обычно не является кодировкой UTF-8) в том случае, если вы передаете их в поток вывода с помощью оператора <<. Аналогично, при извлечении строк типа Glib::ustring из потока ввода с помощью оператора >> происходит преобразование в противоположном направлении. Но эта схема дает сбой в том случае, если вы используете класс std::string в качестве промежуточного звена, т.е., извлекаете текст из потока в строку типа std::string и затем непосредственно преобразовываете строку к типу Glib::ustring. В том случае, если строка содержала не относящиеся к таблице ASCII символы, а также текущая локализация не использовала кодировку UTF-8, в результате получится поврежденная строка типа Glib::ustring. Вы можете обойти этот недостаток, осуществив преобразование в ручном режиме. Например, для извлечения строки типа std::string из строки типа std::ostringstream может использоваться следующий код:
std::ostringstream output;
output.imbue(std::locale("")); // использование пользовательской локализации для этого потока
output << percentage << " % выполнено";
label->set_text(Glib::locale_to_utf8(output.str()));

27.4. Часто встречающиеся ошибки

Существует несколько часто встречающихся ошибок, с которыми в конечном счете столкнетесь и вы. Этот раздел может помочь вам избежать их.

27.4.1. Одни и те же строки с разными семантиками

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

В подобных случаях вам следует добавлять дополнительные символы в строки. Например, следует использовать строки "jumps[noun]" (для существительного "переходы") и "jumps[verb]" (для глагола "переходит") вместо "jumps" для отделения переводов вне вызова функции библиотеки gettext. В том случае, если вы добавите дополнительные символы, вы также должны добавить комментарий для переводчиков перед вызовом функции библиотеки gettext. Такие комментарии будут размещаться в файлах переводов с расширением .po. Например, комментарий может быть следующим:
// примечание для переводчиков: не переводите часть строки "[noun]" - она используется
// исключительно для отделения текущей строки от другой строки "jumps"
text = strip(gettext("jumps[noun]"), "[noun]");

27.4.2. Объединение строк

Разработчики, использующие язык программирования C, применяют функцию sprintf() для форматирования и объединения строк. Язык программирования C++ предоставляет в распоряжение разработчика потоки, но, к сожалению, их использование затрудняет процесс перевода, так как каждый фрагмент текста переводится отдельно и у переводчиков нет возможности изменения порядка следования фрагментов переведенной строки в зависимости от грамматики языка.

Например, данный код может оказаться проблемным:
std::cout << _("Current amount: ") << amount
          << _(" Future: ") << future << std::endl;

label.set_text(_("Really delete ") + filename + _(" now?"));
Вы должны либо избегать подобных ситуаций, либо прибегать к использованию метода Glib::ustring::compose(), который позволяет использовать аналогичный следующему синтаксис:
std::cout << Glib::ustring::compose(
             _("Current amount: %1 Future: %2"), amount, future) << std::endl;

label.set_text(Glib::ustring::compose(_("Really delete %1 now?"), filename));

27.4.3. Предварительная оценка длины используемых строк

Вы никогда не сможете узнать заранее о том, как много места на экране займет строка после перевода. Вполне вероятно, что переведенная строка будет в два раза длиннее оригинальной строки на английском языке. К счастью, у большинства виджетов из состава gtkmm есть возможность расширения до необходимого размера в процессе работы приложения.

27.4.4. Необычные слова

Вы должны избегать использования неизвестных аббревиатур, слэнга и жаргона. Строки с подобными словами обычно сложно переводить, причем эти слова обычно плохо понятны даже тем людям, для которых английский является родным языком. Например, для обозначения приложения предпочтительнее использовать слово "application", а не "app".

27.4.5. Использование не-ASCII символов в строках

На данный момент библиотека gettext не поддерживает символы из исходного кода, не относящиеся к таблице ASCII (т.е., символы с кодами больше 127). Например, вы не сможете использовать символ, предназначенный для обозначения авторских прав (©).

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

27.5. Содействие в создании переводов

В том случае, если ваша программа является свободным программным обеспечением, у вас есть возможность участия в отдельном подпроекте проекта GNOME под названием GNOME Translation Project, предназначенном для содействия в создании переводов.

Этот проект работает следующим образом: вы загружаете ваш исходный код в репозиторий git, в котором переводчики будут иметь доступ к нему, после чего регистрируетесь в списке рассылки gnome-i18n и обращаетесь с просьбой о добавлении вашей программы в список модулей для перевода.

После этого вы должны убедиться в том, что обновили файл POTFILES.in в поддиректории po/ (команда intltool-update -M может помочь в этом) для того, чтобы переводчики имели доступ к всегда обновленным файлам <имя программы>.pot и просто "замораживать" строки как минимум за несколько дней перед выпуском нового релиза, объявляя об этом в списке рассылки gnome-i18n. В зависимости от количества строк в исходном коде вашей программы и ее популярности, впоследствии силами переводчиков начнут создаваться переводы в виде файлов <имя языка>.po.

Учтите, что большинство команд переводчиков приложений на определенный язык состоит из 1-3 человек, поэтому в том случае, если ваша программа содержит большое количество строк для перевода, может пройти некоторое время перед тем, как кто-либо найдет время для работы над ее переводом. Также большинство переводчиков не не желает терять свое время (перевод занимает очень много времени), поэтому в том случае, если они не считают ваш проект действительно серьезным (в смысле проработки и поддержки), они могут решить потратить свое время на работу над каким-либо другим проектом.


Следующий раздел : Собственные виджеты.