 | Уровень сложности: средний Мацей Катафиш, cтудент, Computer Science
04.02.2008 В статье объясняется, как с помощью библиотеки GTK+ можно создавать приложения с графическим интерфейсом пользователя, который поддерживает разные языки мира. Эта статья поможет избежать распространенных ошибок и создавать приложения, которые можно будет использовать в разных странах.
Мир меняется. Сегодня нельзя игнорировать ни существование международного рынка, ни то, что компьютеры по сути дорогие игрушки для тех, кто готов потратить время и деньги на их изучение. С другой стороны, возросла потребность международного сообщества в коробочных приложениях с встроенной поддержкой разных национальных языков.
Графический интерфейс пользователя (GUI) и, как его частный случай, приложения GTK+ помогает в локализации приложений. Фактически в GTK+ V2.x была существенно улучшена поддержка локализациии по сравнению с GTK+ V1.x (далее мы обозначаем ее как i18n, что соответствует 18 буквам между i и n).
В этой статье рассказывается, как использовать возможности GTK+ для создания GUI-приложений, которые будут понятны пользователям из разных стран и будут полностью удовлетворять их требованиям.
Потребность в локализации
 |
UTF-8
UTF-8 - один из возможных вариантов кодировки (преобразование символов в последовательность байтов) Юникода (Unicode), смысл которой в том, что осуществляется 8-битное кодирование текста. Существует много других кодировок Unicode, включая UTF-16 (и родственную, но устаревшую UCS-2), и UTF-32 (также известную как UCS-4).
UTF-8 имеет несколько преимуществ перед другими кодировками: она совместима с ASCII, поэтому существующие приложения, в основном, должны поддерживать текст в кодировке UTF-8, хотя они и не отображают символ, интерпретируемый значением выше 127. UTF-8 достаточно удобная кодировка (особенно для текстов на западных языках), устойчивая к ошибкам, возникающим во время передачи текста (например, при потере дополнительного символа), и наконец, UTF-8 широко поддерживается множеством приложений.
По причинам, указанным выше, выбор UTF-8 обычно всегда себя оправдывает. Однако могут возникнуть ситуации, когда требуется снизить нагрузку на центральный процессор или экономить память; в этих случаях можно использовать UCS-4 для приложений, которые используются только внутри компании. Однако если новая программа должна будет взаимодействовать с программами сторонних разработчиков, то необходимо использовать UTF-8.
Есть еще один момент, связанный с UTF-8, который никогда нельзя забывать: UTF-8 - мультибайтная кодировка, поэтому нельзя определить, сколько байтов займет следующий символ, до тех пор пока не закодируете текущий символ. Вследствие этого факта не следует использовать вообще арифметику указателей при работе с UTF-символами, а нужно применять специальные функции для работы с UTF-символами. Ознакомьтесь с документацией по низкоуровневой библиотеке GLib и интерфейсу API для более подробной информации (см. раздел Ресурсы).
|
|
Потребность в локализации была очевидна создателям GTK+ с самого начала разработки этой библиотеки, поэтому она полностью поддерживает национальные языки. Также существуют и другие возможности, позволяющие создавать приложения, которые будут работать корректно в различных языковых средах.
К этим возможностям относятся:
-
Использование Юникода повсеместно везде внутри библиотеки. С помощью Юникода возможно создавать по настоящему многоязычные приложения, а не просто приложения с поддержкой нескольких языков. Например, создание приложения на арабском языке или отображение комментариев на русском языке к японскому тексту при использовании Юникода уже не будет сложной проблемой. Для реализации подобной программы весь текст из нее с помощью GTK+ будет перекодирован в UTF-8, если не была задана другая кодировка (см. врезку о UTF-8).
-
Использование библиотеки Pango для отображения текста на разных языках в высоком качестве. Pango разработана для преобразования фрагмента текста Юникод в правильное изображение на дисплее с учетом таких особенностей теста, как шрифт, длина текста, другие типографские особенности печатного текста, и реализацию двунаправленности текста (Unicode Bidi algorithm) для корректного отображения таких языков, как арабский, где буквы записываются справа налево. Pango должен использоваться для решения любой задачи, связанной с отображением теста на экране дисплея.
-
Использование библиотеки GNU gettext для локализации. С помощью этой библиотеки GTK+ может отобразить любую программу, выполняемую пользователем, на его родном языке (если, конечно, есть соответствующие файлы с языковыми данными). GTK+ также по умолчанию включает поддержку использования gettext через GLib, хотя никто не мешает использовать альтернативные решения.
-
Отсутствие строгой привязки по координатам для элементов графического интерфейса. Хотя это и не связано напрямую с локализацией, но все равно очень важно: GTK+ никогда не использует фиксированные позиции элементов графического интрефейса пользователя, поскольку это может помешать корректному отображению в различных языках. Если в локализованной версии приложения выводимые сообщения урезаны в тех местах, где заканчиваются их английские аналоги, то это означает, что библиотека GUI не смогла расширить размеры окна сообщений. Подобная ситуация никогда не возникнет в GTK+. GTK+ всегда распределяет пространство экрана как необходимо, а не как было определено при создании исходной англоязычной версии приложения.
 |
Подготовка приложения для локализации
Для успешной локализации нужно выполнить два взаимосвязанных требования. Во-первых, программист не должен быть связан понятиями, которые меняют свое значение со сменой языка, и понимать и точно знать, что изменится в приложении при переходе на другой язык. Второе условие - это наличие подходящего инструментального средства, которое будет поддерживать свободный от языковых нюансов стиль программирования.
Ниже приведены краткие описания возможных проблем и пути их решения. Этот краткий обзор ни в коем случае нельзя считать исчерпывающим: корректная локализации - это процесс со множеством деталей. В тех местах, где кратко объяснены аспекты этого процесса, я оставил ссылки на ресурсы со всей необходимой информацией.
Необходимо правильное приложение для локализации
Я не буду разбирать неудачные попытки локализации в прошлом (например, поставка приложений с несовместимыми двоичными кодами для различных языков или коверканье файлов шестнадцатеричным редактором). Единственный способ корректно провести локализацию - это правильно выделить и извлечь все, что должно быть локализовано, в отдельные объекты, которые будут обработаны независимо от остальных данных. Как это сделать, можно узнать в разделе "Программный код."
Понимание различий в языках
Область, где сразу же проявляются различия в языках - это автоматически генерируемые сообщения. Обычно они достаточно сложные и содержат информацию различных типов. Рассмотрите эти два подхода:
printf ("Retrieved %d file%s\n", n_files, n_files != 1 ? "s" : "");
|
и
printf ("Retrieved %d file(s)\n", n_files);
|
Оба подхода являются неправильными, неприменимы ни к каким другим языкам, кроме английского (и даже в английском могут возникнуть трудности, если обрабатывать текстовые блоки вместо файлов), и мешают создать удобный интерфейс пользователя. Ко всему прочему первое решение страдает от серьезных проблем с удобочитаемостью.
Вместо этого следует использовать специализированное решение, например, функцию ngettext() из библиотеки GNU gettext:
printf (ngettext ("%d file removed", "%d files removed", n_files),
n_files);
|
Используя предоставляемый числовой параметр, также как и правила, предоставляемые переводчиками на иностранные языки, функция ngettext() может определить правильную форму для данного языка во время выполнения или использовать запасные, резервные строки (две строки, показанные выше), когда невозможно определить правильную форму для языка. Для детальной инструкции по использованию функции ngettext(), изучите ее руководство (см. раздел Ресурсы).
Из примера выше видно, что не следует пытаться генерировать сообщения конкатенацией строк, иначе переводчик, если ему понадобится, не сможет изменить порядок слов в предложении и вынужден будет обрабатывать обрывки предложений. Всегда задавайте форматирующую строку для емких по смыслу предложений и не разбивайте связанный по смыслу текст.
Соблюдайте национальную специфику
Локализация - это нечто большее, нежели банальный перевод текста. Языки различаются разделителем целой и дробной части десятичного числа, форматом представления даты, использованием 12-часовой системы вместо 24-часовой и прочими тонкостями. В дополнение к этому требуется решить нетривиальную задачу алфавитной сортировки и реализовать основные операции с текстом, такие как определить что за буква, где она находится в алфавите, какие присутствуют знаки препинания и прочее. Все эти детали находятся в так называемых местных файлах описания, которые должны быть в дистрибутиве операционной системы. GLib абстрагируется от различий между операционными системами и предоставляет несколько текстовых функций. Изучите самостоятельно разделы о Юникоде, дате, времени и строках в справке по GLib API (см. раздел Ресурсы).
Службы локализации, которые предоставляет операционная система и язык С, имеют кроме плюсов также и ряд минусов. Необходимо помнить, что по умолчанию работа большинства библиотечных функций C зависит от национального языка. Это значит, что, к примеру, если сохранить значение с плавающей точкой при помощи функции strtod() на американском компьютере и позже попробовать использовать на польском компьютере, то возникнет ошибка из-за того, что в Америке и Польше разные разделители десятичной и вещественной части. Вместо этого для сохранения данных (например, конфигурационных файлов) нужно использовать семейство функций g_ascii_* из GLib.
Осторожность не бывает лишней
Большинство программистов плохо понимают серьезность проблем локализации. Например, нужно тщательно выбирать графическое оформление, например, невинная иконка может выглядеть серьезным оскорблением для человека из другой культуры. В особенности это правило относится к пиктограммам, изображающим части человеческого тела. Всегда лучше использовать пиктограммы по умолчанию, которые предоставляет система. Если они не подходят, то необходимо предусмотреть возможность их смены на другие местными поставщиками приложений без изменения исходного кода.
Необходимо полностью реализовывать возможность повторного использования кода: неразумно заново писать функции для национальных языков, которые идентичны функциям из используемых программистом библиотек. И если программист найдет функцию, которой еще нет в библиотеке, ему следует проконсультироваться с коллегами из списка рассылки, использующими GTK+ (см. раздел Ресурсы): вполне возможно, что это ошибка, и тогда вы сможете принести большую пользу людям, когда выпустите патч.
Будьте предельно осторожными со всем, что несет политический оттенок: трижды подумайте прежде чем делать ссылку на флаг, карту или имя с политическим контекстом. Ошибка в скрипте для сортировки может раздражать, но гораздо больше раздражения вызовет отзыв продукта с большей части континента, как сделала Microsoft®® с операционой системой Microsoft Windows® 95.
Всегда используйте библиотеку Pango для отображения текста
Невозможно реализовать в приложении поддержку всех языков мира, поэтому не имеет смысла собирать текст из различных текстовых блоков, потому что связного текста в таком случае не получится. Так что лучше использовать библиотеку Pango.
Программный код
Теперь, после объяснения основных принципов локализации рассмотрим их применение в GTK+ коде.
Сперва нужно определить несколько имен, которые позволят библиотеке gettext найти правильные сообщения для разрабатываемого приложения. Помните, что в реальных приложениях определение выполняется автоматически, но мы для большей наглядности объявим их вручную:
#define GETTEXT_PACKAGE "foo-app"
#define LOCALEDIR "mo"
|
После этого нужно корректно вставить заголовки gettext. Простейший путь сделать это - использовать готовые заголовки из GLib (доступно в версиях 2.4 и выше):
Этот заголовок предоставляет макросы _() и N_() для маркирования строк и их перевода, что будет продемонстрировано далее.
Теперь нужно пометить видимые конечному пользователю строки таким образом, чтобы gettext смог распознать их и перевести, когда приложение будет выполняться. Для этой двойной цели следует использовать макрос _(), который является коротким псевдонимом для вызова функции gettext().
Функция gettext() отыскивает и отправляет строки в свой каталог сообщений, чтобы убедиться, что перевод будет осуществлен корректно для данного языка. Если так, то тогда эта функция возвращает перевод строки; в противном случае возвращает саму строку без перевода. При подготовке исходного кода для перевода нужно учитывать, что слово gettext при сканировании будет распознано как промаркированное, поэтому те сообщения, что должны быть переведены, могут быть выделены из общего кода и помещены в отдельный файл.
Вооруженный этими знаниями, программист сможет реализовывать поддержку различных языков. Прежде чем новая программа начнет работать, нужно инициализировать gettext:
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
|
Теперь мы просто заменяем каждое вхождение текста, который нужно перевести, на вызов функции gettext(). Таким образом, строка:
gtk_label_set_markup(GTK_LABEL (label1), "<b>Normal mode Foo, translated:</b>");
|
станет:
gtk_label_set_markup(GTK_LABEL (label1), _("<b>Normal mode Foo, translated:</b>"));
|
Это все, что можно сказать по рассматриваемой нами проблеме, кроме двух небольших исключений. Одно из них - это статические строки, для которых нельзя применять gettext(), потому что это функции. В этих ситуациях используйте макрос N_(), который распознается как ключевое слово для маркирования на перевод. Таким образом:
const char *msg = N_("Important message");
/* ... */
do_important_stuff(_(msg));
|
Второе исключение - это ситуация, когда введена некоторая цифровая переменная, к примеру, число файлов, которые надо открыть. Для этого случая используйте функцию ngettext(), которая понимает числа. Детали использования этой функции рассмотрены в справочной системе библиотеки GNU gettext вместе с описанием функции gettext().
В заключение подчеркну, что надо внимательно выбирать текст для перевода. Обычно все видимые пользователю строки должны быть переведены, однако отладочные и других сообщения, предназначенные для разработчиков, лучше оставить без перевода, чтобы другие программисты могли разобраться в исходном коде приложения. Как пример такого поведения я покажу виджет FooWidget (см. раздел "Создание своих виджетов" ниже), который включает в себя режим отладки сбоев, в котором слова с маркером RTL или LTR остаются непереведенными.
Общий совет такой: не переводите то, что не относится к практическому применению приложения. Например, не переводите флаги статуса протокола TCP/IP. Подобные ошибки совершаются постоянно, например, в сетевых утилитах Microsoft Windows элементы типа SYN_ACK были переведены на польский язык и при этом потерялся весь смысл - ZGODN_POTW. Как результат, все, включая самих поляков, не могли понять или даже прочитать такие сообщения.
Создание своих виджетов
Осуществление перевода строк является большой частью работы. Остальная часть работы - это корректное отображение этих строк. Приложение должно корректно работать у пользователей, где направление написанного текста справа налево, а не как в английском языке - слева направо. Ввиду того, что текст для приложений, используемых в арабских странах, выводится справа налево, графический интерфейс должен быть зеркально отражен (см. рисунок 1).
Рисунок 1. Арабский вариант приложения с использованием GTK+
К счастью, в большинстве случаев программисту ничего не надо делать для осуществления подобного режима работы. GTK+ сама разберется, где необходимо зеркально отобразить интерфейс, благодаря программному коду, который оперирует терминами логических отношений между виджетами, а не жестко привязанными экранными координатами.
Даже если программист создает свои собственные виджеты, то он может воспользоваться библиотекой GTK+ для реализации поддержки режима "справа налево". Пока его виджет по сути составлен из других виджетов, реализация режима отображения текста справо налево будет корректно работать. Однако если этот виджет содержит его собственный программный код, рисующий что-либо, что не может быть автоматически зеркально отображено, или другие ситуации, где важно направление текста, поэтому программисту придется самим реализовывать логику работы "справа налево".
В качестве примера я приведу простой виджет FooWidget, который не делает ничего особо полезного, но реагирует на направление написания текста и выводит соответствующее сообщение. Вся его логика работы по сути является функцией gtk_widget_get_direction(). Следующий код реализации FooWidget помещен внутрь функции _init():
GtkTextDirection dir = gtk_widget_get_direction(GTK_WIDGET (self));
if(dir == GTK_TEXT_DIR_LTR)
{
priv->label = gtk_label_new(ltr);
}
else
{
priv->label = gtk_label_new(rtl);
}
|
Как видно из примера, отобразить текст справа налево достаточно просто, и зависит это только от сложности разрабатываемого виджета. Если он труден для понимания, то его программный код тоже может быть более большим и сложным. Но для большинства пользователей простая проверка текста на направление и изменение этого текста - это все, что должно гарантировать корректную работу приложения.
Заключительные заметки
Ограниченный объем статьи не позволяет объяснить все детали локализации. В частности, в ней не рассматривается использование библиотеки Pango. К счастью, программисты не часто используют библиотеку Pango, только в тех случаях, когда им нужно писать много виджетов под заказ. Однако если потребуется корректно отображать текст, то обязательно используйте Pango.
Другой важный аспект, который я не рассмотрел, - это интеграция с средой разработки. Необходимо корректно интегрировать локализацию со своей средой разработки, чтобы переводы приложений не устаревали. Одна возможность для этого - использование автоматических инструментов GNU, у которых есть встроенная поддержка библиотеки GNU gettext и связанных с нею утилит. Особенно важно это для проектов с открытым исходным кодом, где эти автоматические инструментальные средства и являются средой разработки по умолчанию. Однако несмотря на всю свою гибкость автоматические инструментальные средства плохо подходят для решения проблем, связанных с локализацией. Если возникнет сложная проблема, то всегда можете попросить совета у других программистов из списка рассылки пользователей GTK+ (см. раздел Ресурсы). Наверняка кто-то из коллег-программистов раньше уже сталкивался с этой проблемой.
Прочитайте руководство к библиотеке GNU gettext. Даже если не планируете использовать gettext, прочитайте хотя бы вступление: в нем объясняется, как и почему было применено какое-либо конкретное решение в библиотеке. Эти решения в достаточной степени независимы от инструментального средства, с которым работает программист.
Заключение
Читатели статьи познакомились с наиболее часто встречающимися проблемами, связанными с локализацией приложений, и научились решать их, а также узнали, как сделать графический интерфейс приложения чувствительным к требованиям пользователей из различных стран и культур. Используя предоставленные ссылки на дополнительные ресурсы, программист сможет подобрать тот инструмент разработки программ, который наиболее полно отвечает требованиям пользователей по всему миру.
Ресурсы Научиться
Получить продукты и технологии
-
IBM trial software: ознакомительные версии программного обеспечения для разработчика, которые можно загрузить со страницы developerWorks.
(EN)
Обсудить
Об авторе  | |  | Мацей Катафиш (Maciej Katafiasz) является аспирантом по информатике и еще будучи студентом стал использовать программные продукты и технологии Open Source. Он также является пользователем рабочего стола GNOME с момента его появления - версии 1.0. После выхода релиза версии 2.0 рабочего стола GNOME Мацей изучил GTK+, чтобы создавать с ее помощью приложения для рабочего стола |
Выскажите мнение об этой странице
|  |