Быстрая локализация приложений Delphi при помощи DXGetText

© 2005 Николай Войнов

Введение

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

Для локализации приложений Delphi существуют как встроенные средства, так и несколько десятков альтернативных авторских библиотек. Ссылки на обзоры средств локализации, а также некоторые ссылки на конкретные библиотеки приведены в Приложение 2.

Уверен, что данную тему исследовали для себя многие разработчики, автор тоже не был исключением. И как-то просматривая проекты SourceForge заметил бесплатную библиотеку DXGetText. В следующий раз посмотрел внимательнее и заметил аббревиатуру TM (что такое TM можно посмотреть на www.translationmemory.ru). Некоторое время работал в переводческой компании и знал что это такое. При этом в других инструментах локализации о такой роскоши не упоминали, возможно, просто не заметил.

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

На сайте проекта так описывают преимущества библиотеки (автор позволил себе немного изменить оригинальный порядок):

Не забывают и об ограничениях:

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

Практика

Предварительное знакомство с используемыми методами

Работа с самой библиотекой в действительности очень проста. Все что нам нужно сделать это указать какой язык мы будем использовать, и вызвать метод, который переведет наши визуальные компоненты. Для этого используются три метода UseLanguage, TranslateComponent и RetranslateComponent. Объявлены они следующим образом:

procedure UseLanguage(LanguageCode: string)
Этот метод говорит dxgettext, что нужно использовать язык с кодом LanguageCode
procedure TranslateComponent(AnObject: TComponent; const TextDomain:string='');
А этот переводит компонент AnObject, в соответствии с необязательный параметром TextDomain. Второй параметр это домен перевода. В демонстрационных целях он нам пока не нужен (должны же вы все-таки прочесть документацию).
procedure RetranslateComponent(AnObject: TComponent; const TextDomain:string='');
Вызывается для того чтобы перевести заново уже переведенный компонент.

Некоторые свойства и даже классы нельзя переводить, это может нарушить работоспособность приложения. Так dxgettext не будет никогда переводить имена компонентов. Об остальных ситуациях мы должны позаботиться самостоятельно. Благо с dxgettext это совсем не сложно. Существуют несколько методов, которые указывают библиотеке, что она не должна переводить – это могут быть определенные классы, свойства и даже отдельные объекты - шесть методов начинающихся с префикса TP_

procedure TP_Ignore(AnObject: TObject; const name:string);
procedure TP_IgnoreClass (IgnClass: TClass);
procedure TP_IgnoreClassProperty (IgnClass: TClass;const propertyname:string);
procedure TP_GlobalIgnoreClass (IgnClass:TClass);
procedure TP_GlobalIgnoreClassProperty (IgnClass:TClass;const propertyname:string);
procedure TP_GlobalHandleClass (Hclass: TСlass; Handler: TTranslator);

Архитектурная подготовка

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

Ну а для нового проекта мы тут же выделим два компонента. Первый это менеджер текущего языка локализации и второй это родитель для всех визуальных форм – основная его задача будет автоматизация перевода GUI.

Посмотрев в архив с исходными текстами примеров, прилагаемых к данной статье, вы увидите там два файла uLocalizeManager и fmuCustomGUI - это именно они. uLocalizeManager содержит класс, который управляет информацией о подключенных языках локализации и текущем языке системы. При создании TfmCustomGUI производится ее перевод, а также она содержит метод для повторного перевода формы.

Строим приложение

Тема примера могла быть какой угодно, но выбрано простое приложение, которое будет работать с БД. Будем использовать BDE и демонстрационную базу данных DBDemos.

Добавляем в проект главную форму, наследник от TfmCustomGUI. Кидаем на нее TQuery, TDBGrid, TPanel, TStatusBar. Настроим TQuery на выборку из таблицы Customers и привяжем к TDBGrid, на панель добавим TButton для закрытия приложения и TComboBox для выбора языка интерфейса.

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

procedure TfmMain.FormCreate(Sender: TObject);
var
  i: integer;
begin
  inherited;
  Query1.Open;
  cbLanguage.Clear;
  for i := 0 to TLocalizeManager.Instance.LangNames.Count - 1 do
    cbLanguage.Items.Add(TLocalizeManager.Instance.LangNames[i]);
  cbLanguage.ItemIndex := 0;
end;

procedure TfmMain.FormDestroy(Sender: TObject);
begin
  Query1.Close;
  inherited;
end;

procedure TfmMain.cbLanguageChange(Sender: TObject);
begin
  UseLanguage(
    TLocalizeManager.Instance.LangCodes[cbLanguage.ItemIndex]);
  Retranslate;
end;

procedure TfmMain.Button1Click(Sender: TObject);
begin
  if MessageDlg('Вы действительно хотите выйти из приложения?',
    mtConfirmation, [mbYes, mbNo], 0) = mrYes
    then Close;
end;

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


Снимок 1. Главное окно приложения

Интерфейс специально сделан русский, опытные локализаторы говорят, что русские слова, как правило длиннее чем английские.

Теперь мы подошли к самому главному – локализации.

Локализация

Локализация выглядит примерно следующим образом:

  1. Извлекаются все строки из папки с исходными тестами в po-файл. Описание формата оставим документации, а сейчас просто будем знать, что нужно извлечь текст в po-файл. Делается это при помощи консольных утилит в составе dxgettext.
  2. Перевести полученный po-файл и скомпилировать его в mo-файл. Это формат для хранения локализации в целевом языке. Попробуйте поискать такие файлы у себя на машине.

Если вы установили dxgettext, то в контекстном меню оболочки должен появиться пункт "Extract translations to template".


Снимок 2. Контекстное меню системы

Становимся на папку с исходным кодом и нажимаем. Появляется диалоговое окно с настройками извлечения текста.


Снимок 3. Настройки извлечения текста

Первая опция указывает на то включать ли вложенные каталоги. При включенной опции "Add likeky ignores to ignore.po file" будет создаваться файл ignore.po с текстом, который нужно игнорировать. Если отметить "Remove items from default domain presents in ignore.po file" то строки, которые попали в ignore.po будут удаляться из формируемого файла для переводов. Включите опцию "Allow non-ASCII text", если ваше приложение не на английском. Изначально gnugettext извлекала только английский текст, и нужно явно указать, что вы используете другой. Ну и при включенной опции "Remember settings", настройки будут сохранены в файле настроек, который будет храниться в выбранном вами каталоге извлечения текста.

В результате работы утилиты извлечения текста мы получим три новых файла:
  default.po - интересующие нас строки для перевода из домена default;
  dxgettext.ini - настройки для этого каталога;
  ignore.po - проигнорированные строки.

languages.po интересующие нас строки для перевода из домена languages (смотри код uLangugeManager).

Теперь нужно вернуться немного назад, и пояснить, как это работает. При старте приложения создается класс менеджера локализации, который загружает все доступные локализации и устанавливает используемый язык. Для простоты изложения будем использовать язык по-умолчанию – Русский.

initialization
  TLocalizeManager.Instance.LoadLanguages;
  UseLanguage('ru');
Метод LoadLanguages будет проверять наличие локализаций. Каждая отдельная локализация должна находится по пути
<App>\locale\<Language Code>\LC_MESSAGES\default.mo,
где <App> папка с исполняемым файлом приложения, <language code> код языка локализации (для английского языка это будет 'en', русского 'ru', украинского 'uk' – остальные коды языков вы сможете найти в документации)

Следовательно, нам нужно перевести полученный файл и положить по интересующему нас пути. Мы будем делать английскую локализацию и, следовательно, нам нужно создать папку

<App>\locale\en\LC_MESSAGES\

poEdit

Для перевода, извлеченного на предыдущем этапе текста, воспользуемся программой poEdit. Это свободно распространяемый и вместе с тем довольно мощный редактор для po-файлов. Основной особенностью, на которой стоит заострить внимание является возможность использования памяти переводов TranslationMemory, кто сталкивался профессионально с переводческой деятельностью знают, о чем говорится. Такие известные программы как Trados, SDLX, мене известные бесплатные OmegaT уже давно и прочно вошли в повседневные рабочие инструменты переводчика. Если вы уже однажды что-то переводили, то новый идентичный или даже похожий текст будет переводиться значительно быстрее. Конечно память poEdit не такая мощная, как у профессиональных программ, но нам это и не нужно – она как раз соответствует нашей цели – локализации.

Итак, если вы еще не установили poEdit - устанавливайте. Скопируем полученный po-файл в целевой каталог локализации и откроем его при помощи poEdit. При первом открытии программа предложит вам установить некоторые настройки, их не много - быстро разберетесь сами.

В общем, открываем файл defatult.po и переводим. Для начала вы должны заполнить свойства каталога Снимок 4 и можно приступать к переводу. И когда вы все перевели, останется просто сохранить каталог.


Снимок 4. Настройки каталога

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

Все, поздравляю, работа сделана. Запускайте приложение снова и переключайте язык!

Конечно без огрехов пока не обошлось - совсем забыли про локализацию подтверждения выхода из программы по кнопке "Закрыть". Это легко исправить - слегка модифицируем код

if MessageDlg(_('Вы действительно хотите выйти из приложения?'),
  mtConfirmation, [mbYes, mbNo], 0) = mrYes
    then Close;
_() - функция gnugettext.pas ее знает парсер извлечения текста.


Снимок 5. Результат работы poEdit

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


Снимок 6. Локализация в действии

Дальнейшее развитие приложения

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

Итак, создаем диалоговую форму и включаем ее в приложение – пусть она вызывается на двойной клик по TDBGrid. Что из этого получилось c локализацией можно увидеть на Снимке 7.


Снимок 7. Развитие приложения, автоматический перевод

Как можно заметить, часть нового диалога уже переведена, хотя мы ровным счетом ничего не сделали для этого! У нас остались не переведенные участки текста, давайте исправим эту ситуацию и повторим действия, описанные в разделе Локализация. В результате мы получим новую версию po-файла.

Теперь у нас есть два варианта не делать работу дважды. Первый вариант наиболее прямой – мы можем объединить наш предыдущий перевод и новый po-файл. Сделать это можно используя контекстное меню оболочки при помощи команды "Merge to template". В результате мы увидим, что большая часть полученного файла уже переведена, и нам нужно лишь дополнить пробелы (Снимок 8.). Неплохо, не правда ли – лишней работы делать не нужно?


Снимок 8. Результат объединения перевода

Мы могли бы получить примерно такой же вариант, если бы добавили существующий файл перевода в TranslationMemory для этого проекта, и потом использовали команду "Перевести, автоматически используя TM".

Теперь нам остается лишь перевести вновь появившиеся строки, и локализация снова будет полной.

Без дополнительных настроек извлечения текста вы можете получить некоторое количество мусора в po-файле. Если вы используете множество сторонних компонент - можно получить добрую половину мусора. Автор постоянно использует FIBPlus, FastReport, FastScript и мусора на первых порах видел достаточно. К счастью dxgettext позволяет вам дополнительно настроить какие свойства, классы и файлы нужно игнорировать при извлечении. Случайно наткнулся на эту информацию при присмотре новостной группы проекта на GetText YahooGroup. К сожалению, в документации упоминания об этой возможности отсутствует, но вы найдете его в архиве с исходными текстами.

Сказки о силе

Хотелось бы также вкратце рассмотреть локализацию чужого кода, когда у вас нет исходных текстов.

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

Автор занимается поддержкой библиотеки прямого доступа к Interbase/Firebird FIBPlus. Часто к нам обращаются пользователи с просьбами и даже предложениями локализации версий нашей библиотеки на разные языки. Круг покупателей довольно обширный и многонациональный и мы сами не можем поддерживать локализацию каждой новой версии на дюжину другую языков.

В этой ситуации нам также поможет dxgettext. Для демонстрации этой возможности при помощи контекстного меню оболочки извлекаем текст из FIBPlusX.bpl (в частности было переведено несколько опций TpFIBDataSet), загружаем в poEdit и переводим. Кидаем перевод в папку локализации и переименовываем как FIBPlusX.po, компилируем. Создаем простое демонстрационное приложение и смотрим, что у нас получилось.

Кидаем на форму TpFIBDataSet, подключаем модуль pFIBDatasetOptions в uses и пишем на OnCreate такой код:

procedure TForm1.FormCreate(Sender: TObject);
begin
  EditOptions([pfibdataset1], 0);
  HookIntoResourceStrings(True, True);
  AddDomainForResourceString('fibplus7');
  UseLanguage('ru');
  TranslateComponent(Self);
  EditOptions([pfibdataset1], 0);
end;

Сначала открывается стандартный встроенный в FIBPlus редактор опций датасета. Потом добавляется домен с локализацией, устанавливается русский язык и снова вызывается этот же редактор. Что получилось можно увидеть на Снимок 9 и Снимок 10


Снимок 9. Использование bpl до локализации


Снимок 10. Использование bpl после локализации

Заключение

Мы коротко рассмотрели еще один способ локализации приложений для Delphi /C++ Builder. Кончено и в этом проекте можно найти некоторые недостатки, да где их нет. Но, на мой взгляд, описанный подход выделяется среди остальных коммерческих решений. Выделяется, прежде всего, богатой историей перевода свободного программного обеспечения и, конечно же, использованием памяти переводов, что значительно сокращает усилия и программиста и переводчика.

О дополнительных возможностях dxgettext читайте в документации. Там вы найдете главу про миграции с Borland ITE, отладку приложений локализации, работы с доменами переводов, а также полный справочник по функциям библиотеки.

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

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

Хотелось бы надеяться, что мой труд по написанию этой статьи не будет напрасным и кому-то эта статья поможет.

Приложения

Приложение 1. Исходные тексты примера

Приложение 2. Некоторые ссылки по теме

  1. Localization Tools for Delphi
  2. Tools > Project > Multilanguage. Torry's Delphi Pages
  3. Korzh.com developer tools: localization/globalization tool kit for apllications.
  4. language localizator – informations
  5. SQLManager | EMS Quick Localizer | Product Files
  6. RTFM::Книги->Delphi->Delphi 5->[ Локализация приложении ]

Copyright© 2005 Николай Войнов  Специально для Delphi Plus


Пожалуйста, оцените статью
Отлично
Хорошо
Средне
Плохо
Очень плохо