- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF111:KDE4
Материал из Linuxformat.
- KDE 4 Создаем приложения для самого современного рабочего стола в Linux.
Содержание |
На всех языках мира
KDE4 |
---|
|
- ЧАСТЬ 4 Угадайте, какая тема неизменно замалчивается западными авторами учебников по компьютерным технологиям? Правильно – интернационализация! Андрей Боровский спешит заполнить данный пробел.
Под интернационализацией мы подразумеваем подготовку программ к переводу на различные языки. Локализация – это собственно перевод на определенный язык (а также адаптация программы к правилам написания чисел и дат, принятым в целевой стране, местной системе мер, используемым денежным единицам и т.п.). Интернационализация выполняется на этапе разработки программы, а локализация может быть выполнена после того, как приложение уже собрано.
Кем бы был Джон Рональд Руэл Толкиен, если бы ему довелось дожить до наших дней? Я просто уверен, что Профессор входил бы в команду интернационализации KDE. Чтобы понять это, достаточно прочесть его руководство по переводу топонимов и омонимов для собственных произведений.
На данном уроке мы сосредоточим свое внимание на специфике интернационализации и локализации программ в KDE 4. Система интернационализации KDE 4 основана на стандартном для Linux пакете GNU gettext (http://www.gnu.org/software/gettext), так что если вы хотите изучить вопрос более подробно, рекомендую обратиться к его документации.
Если вы выполняли интернационализацию и локализацию в KDE 3.x, то обнаружите, что изменения, привнесенные в KDE 4.x, невелики, но учитывать их надо. Появилась новая среда сборки программ (она теперь основана на CMake), в архитектуру библиотек Qt/KDE также были внесены изменения. Да и сама система интернационализации обзавелась новыми функциями. В качестве упражнения мы выполним интернационализацию и русскую локализацию программы, которую создает по умолчанию генератор kapptemplate.
Подготовим исходники
Функции и макросы, необходимые для интернационализации KDE- программы, становятся доступны при включении в исходные тексты заголовочного файла klocale.h; генератор шаблонов kapptemplate делает это автоматически. Основой интернационализации, как и прежде, являются функция i18n() и макрос I18N_NOOP(). Напомню, что функция i18n() получает в качестве параметра строку char * в кодировке UTF-8 и заменяет ее на аналог из каталога переведенных строк. Макрос I18N_NOOP() используется там, где функция i18n() применяться не может (она становится доступна только после создания объекта KApplication). Таким образом, в первом приближении задача подготовки исходных текстов к интернационализации сводится к «оборачиванию» строк английского текста функцией i18n() и макросом I18N_NOOP() (если вы посмотрите исходные тексты программы i18demo, то увидите, что это уже сделано). Далее мы извлекаем из файлов исходных текстов строки, подлежащие переводу, с помощью утилиты xgettext, например:
xgettext -C -ki18n -kI18N_NOOP *.cpp
Ключ -C указывает команде, что она обрабатывает исходные тексты на C/C++. Параметры -ki18n и -kI18N_NOOP сообщают, что нужно искать строки, обернутые i18n и I18N_NOOP (формат: -k + имя функции или макроса). В результате на диске появляется файл messages.po, который содержит все помеченные для перевода строки из файлов с расширением .cpp. Затем их нужно перевести на целевые языки и скомпилировать в mo-каталоги. Теперь, чтобы программа «заговорила» на иностранном языке, достаточно просто скопировать эти файлы в нужную директорию.
Однако не все так просто. Наша демонстрационная программа отсчитывает и показывает в специальном окне число дней, прошедших с момента создания проекта. В ситуации, когда программа выводит сообщение о количестве чего-то, мы должны позаботиться о правильном
использовании грамматических форм единственного и множественного числа. Допустим, у нас есть фраза вида «прошу выделить мне %1 кочерги», где спецификатор %1 во время выполнения программы заменяется на некое целое число (подробнее о форматировании строк можно прочитать в документации по Qt). Можно, конечно, упростить себе жизнь и написать по-канцелярски: «прошу выделить мне изделие “кочерга” в количестве %1 штук». Однако в наше время высоких технологий пользователь вправе ожидать, что компьютеры будут говорить, как люди. Мы хотим, чтобы форма слова «кочерги» менялась в зависимости от количества (1 кочергу, 2 кочерги...). Эту задачу можно решить с помощью кода на C++ и учебника грамматики, но такой подход будет работать только для одной группы языков. Многие программы, обработка множественного числа и других языковых особенностей в которых «зашита» в код, по умолчанию поддерживают только правила английского языка. Эти правила также справедливы для некоторых западноевропейских языков, но не для восточноевропейских, что существенно затрудняет (а иногда и делает невозможной) локализацию программ. Основное правило написания программы, предназначенной для перевода на другие языки: никаких предположений касательно грамматики.
Некоторые конструкции могут затруднить подготовку программы к переводу на другие языки. Самый распространенный пример того, чего не следует делать – это сборка строки из нескольких кусков: i18n(“Time left is”) + seconds + i18n(“seconds”). В таком варианте функция i18n() не будет правильно работать с множественным числом. В программе, предполагающей перевод на другие языки, не следует рассчитывать на то, что текст всегда будет выводится слева направо, и уж конечно, не следует проектировать интерфейсы, исходя из длины строк в пикселях.
На первый взгляд, задача построения системы интернационализации, которая бы подходила для любого языка, кажется неразрешимой. Тем не менее, разработчики GNU gettext с честью справились с этой проблемой, а программисты KDE создали удобные инструменты для ее решения на своей платформе. Для обработки строк, написание которых должно меняться в зависимости от значения целочисленного аргумента, в систему интернационализации KDE добавлена функция i18np(). В исходном в файле i18ndemoview.cpp заменяем строку
i18n("This project is %1 days old", Settings::val_time())
на
i18np("This project is %1 day old", "This project is %1 days old", Settings::val_time())
Кстати, обратите внимание на то, что функции i18n() и i18np() позволяют форматировать строки, заменяя спецификаторы вида %n значениями переменных, подобно методу QString::arg(). У функции i18np() должно быть как минимум три параметра: строка, в которой об исчисляемом предмете говорится в единственном числе, строка, в которой исчисляемый предмет упомянут во множественном числе, и целочисленная переменная, играющая роль управляющего числительного. Проницательный читатель заметит, что описанный синтаксис вызова i18np() предполагает использование всего лишь одной формы для множественного числа, тогда как в русском языке их больше. На самом деле беспокоиться не о чем. Интерфейс программы мы пишем по-английски, и вполне естественно, что мы ориентируемся на грамматику английского языка. Особенности русской грамматики будут учтены в ходе локализации.
Сосчитай до ста
О сочетании существительных и числительных в русском языке написаны целые трактаты, и если вы думаете, что знаете об этом все, то наверняка ошибаетесь (подумайте, например, как правильно сочетаются существительное «сутки» и числительное «двадцать два»). В большинстве случаев мы пользуемся тем фактом, что управляемое числительным имя существительное может находиться в одной из трех форм. Первая форма – для единственного числа и всех числительных, заканчивающихся на «один», вторая форма – для всех числительных, оканчивающихся на «два», «три» и «четыре», третья форма – для всех числительных, оканчивающихся на «пять», «шесть», «семь», «восемь», «девять», а так же для числительных, содержащих в двух младших разрядах 11–19, и всех числительных, соответствующих цифрам, у которых в младшем разряде находится 0 (сами формы для разных существительных выглядят по-разному, но с этим у читателей русского LXF не должно возникнуть проблем).
Мы подготовили исходный текст программы для интернационализации и можем вызвать утилиту xgettext для генерации каталога фраз, предназначенных для перевода. Теперь вызов xgettext будет выглядеть так:
xgettext -C -ki18n -kI18N_NOOP -ki18np:1,2 *.cpp
В результате на диске появится файл messages.po (имя можно изменить с помощью ключа -o). Если мы откроем его в текстовом редакторе, то, помимо прочего, увидим в нем следующее:
msgid "This project is 1 day old" msgid_plural "This project is %1 days old"
то есть в каталог сообщений добавлены обе формы. Для перевода строк на русский язык файл messages.po придется «обработать напильником». Найдите в заголовке файла строку
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\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"
Здесь мы указываем, что общее количество форм множественного числа равно трем (значение параметра nplurals), и присваиваем параметру plural однострочное выражение на C, которое позволяет выбрать нужную форму в зависимости от значения управляющей переменной. Это выражение взято из документа, который можно найти по ссылке http://www.gnu.org/software/automake/manual/gettext/Plural-forms.html. Там же приведены аналогичные выражения для других языков. Обратите внимание на последовательность форм, которая задается выражением plural. Первая форма (индекс 0) соответствует единственному числу и всем числительным, заканчивающимся на 1 (см. врезку). Вторая форма (индекс 1) – числительным, оканчивающимся на 4–9 и 11–19, третья форма – всем остальным числительным.
Теперь, наконец, приступим к самому переводу. Редактирование с этой целью строк, собранных в каталогах po, можно выполнить в любом текстовом редакторе, но KDE, по традиции, предоставляет нам специальный инструмент. В предыдущих версиях KDE данную функцию выпол- няла программа KBabel, а начиная с KDE 4.1 нужная утилита называется Lokalize (рис. 1).
Если открыть файл каталога в программе Lokalize, в списке слева (окно «Содержание») появятся строки, импортированные утилитой xgettext с переводами на другой язык, если таковые уже имеются. Новые переводы добавляются в окне справа. Интересно поведение программы Lokalize в случае, когда мы выбираем строку с несколькими формами для передачи множественного числа. При этом в окне переводов открывается несколько вкладок (как показано на рисунке). Их число соответствует значению nplurals в заголовке файла каталога (в нашем случае их будет три). Последовательность вкладок соответствует последовательности значений, возвращаемых выражением plural. В каждой из этих вкладок мы вводим перевод, не забывая указывать спецификатор %1 в подобающем ему месте.
После завершения следует должны скомпилировать файл каталога po в двоичный файл с расширением mo. Это делается с помощью утилиты msgfmt:
msgfmt messages.po -o i18ndemo.mo
Ключ -о позволяет указать имя результирующего файла, которое по умолчанию должно совпадать с именем собираемой программы. Нам осталось скопировать двоичный файл каталога в директорию, в которой приложения KDE ищут свои ресурсы локализации (программа может загрузить их только из специальной директории и никак иначе – это относится, в том числе, и к текущему рабочему каталогу). Система предоставляет две директории для размещения ресурсов локализации – глобальную, для приложений, установленных в системе, и локальную, для отдельных пользователей. Запись файлов в локальную директорию не требует прав root. По умолчанию, глобальным каталогом является общая директория ресурсов локализации /usr/share/locale/ (в отличие от KDE 3.x, в которой использовалась собственная директория). Локальная директория расположена в ~/.kde4/share/locale/. Файлы переводов сообщений на русский язык необходимо размещать в поддиректории ru/LC_MESSAGES/ одной из указанных директорий. Теперь наша тестовая программа дружит с падежами (рис. 2).
Контексты
В предыдущих версиях Qt/KDE уже были реализованы средства, призванные решить еще одну проблему перевода, связанную с многозначностью слов английского языка. Например, английское слово build может быть существительным «сборка» и глаголом «собрать». Чтобы локализаторы могли предоставить адекватный перевод одного и того же слова, используемого в разных значениях, в KDE 4 существует система контекстов. Контекст задается с помощью функции i18nc(), принимающей два аргумента: строку контекста и фразу, подлежащую переводу, например: i18nc("noun", "Build"), i18nc("verb", "Build").
Если одна и та же переводимая строка используется в разных контекстах, каждый из них вносится в файл messages.po. Разумеется, программа Lokalize позволяет ввести для каждого контекста свой вариант перевода.
Чтобы извлечь из исходных текстов строки, помеченные для перевода с помощью функции i18nc(), нужно использовать специальный синтаксис ключа -k утилиты xgettext. Там, где при использовании i18n() мы написали бы -ki18n, в случае с i18nc() нужно писать -ki18nc:1c,2 (иначе говоря, мы указываем, что первым аргументов функции i18nc() является идентификатор контекста, а вторым – строка, предназначенная для перевода). Как вы уже, наверное, поняли, утилита xgettext ничего не знает о семантике функций i18n*, а при их обработке руководствуется подсказками, которые мы передаем в ключе -k. Для обработки строк с указанием контекста и несколькими численными формами служит функция i18ncp(), которую можно рассматривать как гибрид i18nc() и i18np(). Ее первый аргумент – контекст, далее следуют строки для единственного и множественного числа и целочисленная переменная. Для извлечения строк, помеченных функцией i18ncp(), утилите xgettext должен быть передан ключ -ki18ncp:1c,2,3.
KLocale: даты, числа, деньги
Множество полезных функций локализации сосредоточено в классе KLocale. Его объект, управляющий локалью, создается в программе KDE автоматически. Для того чтобы получить указатель на глобальный объект класса Klocale, воспользуемся вызовом KGlobal::locale():
KLocale * aLocale = KGlobal::locale();
По умолчанию программа использует локаль, выбранную в настройках системы. Для указания локали явным образом можно использовать метод setLanguage(). В KDE 3.x ему передавалась одна строка с именем локали. В KDE 4.x мы можем передать методу список QStringList с именами нескольких локалей, из которых система выберет первую, для которой сможет найти ресурсы локализации:
aLocale->setLanguage(QStringList("ru"));
Теперь мы можем воспользоваться всеми удобствами класса KLocale. Если нам нужно вывести данные о денежной сумме в формате, соответствующем выбранной локали, к нашим услугам метод formatMoney(). Например, вызов
aLocale->formatMoney("10000.67");
вернет строку “10.000,67 руб.”. Подобным же образом работает метод formatNumber().
Для форматирования значений даты и времени нам предоставлены методы formatDate(), formatDateTime(), formatTime(). Их первым параметром должно быть значение типа QDate, QDateTime (или KDateTime) и QTime соответственно. Вторым аргументом у функций formatDate() и formatDateTime() является одна из констант типа DateFormat: ShortDate – «короткая» дата (с численным обозначением месяца), LongDate – «длинная» дата (с буквенным обозначением месяца и указанием дня недели), FancyShortDate или FancyLongDate – аналоги ShortDate и LongDate, для которых недавние (в пределах недели) даты заменяются словами «сегодня», «вчера», «понедельник» и т.д.
Помимо прочего, с помощью глобального объекта KLocale мы можем управлять загрузкой ресурсов интернационализации. Статический метод setMainCatalogue() позволяет задать имя каталога сообщений, используемого по умолчанию. Его нужно вызывать в самом начале функции main(), до того как будет создан глобальный объект KLocale. Обычно программа ищет перевод своего интерфейса в файле, название которого совпадает с ее собственным именем. С помощью метода setMainCatalogue() мы можем изменить это поведение. Так можно поступить, например, в том случае, когда фразы, требующие перевода в вашей программе, образуют подмножество строк другого, широко распространенного приложения, скажем, Konqueror. В этом случае, используя каталог сообщений программы Konqueror, вы «бесплатно» получаете перевод интерфейса вашего приложения на все поддерживаемые языки. Нестатический метод insertCatalog() класса KLocale позволяет добавить еще один элемент в список каталогов, в которых программа ищет переводы строковых ресурсов. При вызове методов загрузки каталогов имена файлов следует указывать без расширения, например:
KLocale::setMainCatalogue("konqueror");
Обновление правок
Предположим, что вы обновляете версию программы, для которой когда-то был выполнен перевод на другие языки (и, возможно, не только вами). Вы внесли некоторые изменения в интерфейс программы, но значительная часть строк осталась неизменной. У переводчиков программы хранятся файлы PO с прежним переводом и они, естественно, не хотят переводить заново то, что уже было переведено. Как перенести старый перевод в новый файл PO? Алгоритм решения таков: сгенерировать новый файл PO при помощи xgettext; объединить новый файл PO и старый файл с переводом с помощью утилиты msgmerge; перевести непереведенные строки. В простейшем случае вызов msgmerge выглядит так:
msgmerge старый_файл новый_файл
где старый_файл – файл PO с уже выполненным переводом, а новый_файл – файл PO, сгенерированный xgettext. По умолчанию утилита выводит результат своей работы в стандартный поток вывода, но ничто не мешает нам перенаправить его в файл. Схематично механизм слияния можно описать так: для каждой строки нового файла msgmerge ищет перевод в старом файле. Если перевод найден, он добавляется в результирующий текст. Таким образом, если в ходе обновления программы из ее интерфейса была удалена какая-либо строка, она уже не попадет в файл, полученный в результате слияния.
Объединять файлы каталогов можно и с помощью Lokalize. Загрузите основной файл каталога в Lokalize и выберите команду меню Синхронизация > Открыть файл для синхронизации/объединения. В появившемся окне нужно указать файл каталога для объединения с главным. В нижней части окна Lokalize откроется панель Синхронизация. На ней будут отображаться различия в переводах между вспомогательным и основным файлом (для этого в окне Содержание необходимо выделить строку переводимого текста, для которой различаются переводы). С помощью команд Перенести из источника слияния и Перенести все новые переводы можно добавить переводы из вспомогательного файла в основной.
Перевод из файлов ui
А как быть с теми строками, которые были заданы в файле описания интерфейса (ui)? Утилита xgettext не справляется с их извлечением, но нам на помощь приходит утилита extractrc. Эта программа извлекает помеченные для перевода строки из файлов UI и RC (имеются в виду файлы XMLGUI, а не скрипты runcommand) и генерирует файл cpp, в котором эти строки оборачиваются функциями i18n(). Утилита всегда вываливает сгенерированный текст в стандартный поток вывода, но это можно изменить перенаправлением.
Я не стал переводить интерфейс диалогового окна настроек программы i18ndemo – оставляю это вам в качестве домашнего задания (подсказка: для объединения существующего перевода и перевода строк интерфейса диалогового окна воспользуйтесь msgmerge или Lokalize). LXF