DxGetText — GNU Gettext for Delphi and C++ Builder

Пощастило мені якось працювати під керівництвом СТО, який за сумісництвом співавтор одного цікавого проекту — GNU Gettext for Delphi and C++ Builder. Зацінив я його тільки в Delphi, але цього достатньо щоб зрозуміти принцип роботи і розібрати якими фічами він володіє.
Коротко це бібліотека, що дозволяє впроваджувати якісну локалізацію в продукт загальноприйнятим способом, працює так:
  1. пишемо код, майже як звичайно;
  2. запускаємо додаток, скануючий исходники на предмет тексту, який потрібно перевести;
  3. генера РВ файли;
  4. переводимо їх в будь-якому зручному редакторі;
  5. компилим РВ файли у МО файли;
  6. на вибір або впроваджуємо переклад прямо в ЕХЕ або кладемо МО файли поруч;
  7. насолоджуємося результатом — мова програми можна змінювати навіть без перезапуску.
Чим цей спосіб крут:
  • мінімум змін в коді програми;
  • ніяких DLL і сторонніх компонентів, всі OpenSource;
  • РВ файли — досить поширений інструмент переказу, що означає переклад можна навіть віддати на аутсорс, і перекладач знає що з цим робити;
  • переклад всього — форми, фрейми, месседжбоксы, і все що завгодно;
  • коректний переклад слів у множині в будь-якій мові;
  • повна підтримка Unicode.

Отже, приступимо до встановлення.
Сайт та исходники давненько не оновлювалися. Можна взяти скомпільовані тулзы тут, а можна взяти исходники тут і зібрати самому. Це інструменти власне для генерації PO файлів і дій з ними. А для проекту Delphi нам потрібно всього лише додати у uses один файл — gnugettext.pas.

Тепер спробуємо використовувати.
При запуску додатка нам невідомий його мову, тобто він буде"Untranslated". Вказати мову можна де завгодно — в initialization, в конструкторі форми, після вибору юзером мови і т. д. Для того, щоб перевелася форма, в її конструкторі слід викликати методtranslatecomponent.
Спробуємо створити VCL Application Form з таким текстом в конструкторі форми:
procedure TForm1.FormCreate(Sender: TObject);
begin
UseLanguage ('EN');
translatecomponent(self);
end;

Список підтримуваних кодів мови можна подивитися тут.
Далі створимо кнопку Caption:='Translate me', після натискання на яку покажемо меседж і перемкнемо мову додатка:
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(_('Changing language'));
UseLanguage ('RU');
Retranslatecomponent(self);
end;

Функція "_" (або"gettext") спробує знайти переклад зазначеного тексту і поверне його, якщо знайдений, або ж поверне текст зі входу.
Зараз програма не має локалізації, РВ і МО файлів немає. Все працює, ніяких помилок, просто без перекладу. Спробуємо це виправити.
В папці програми створимо папку з ім'ям"locale«в ній папки з мовами, в нашому випадку „uk“ „en“і в кожній папці мови створимо папку»LC_MESSAGES", в якій створимо порожній текстовий файл"default.po". Після цього в кореневій папці програми створимо файлик updatepofiles.cmd з таким вмістом:
echo Extracting texts from source code

dxgettext-q --delphi --useignorepo-b .

echo Updating Ukrainian translations
pushd locale\ru\LC_MESSAGES
copy default.po default-backup.po
ren default.po default-old.po
echo Merging
msgmergedx default-old.po ..\..\..\default.po-o default.po
del default-old.po
popd

echo Updating English translations
pushd locale\en\LC_MESSAGES
copy default.po default-backup.po
ren default.po default-old.po
echo Merging
msgmergedx default-old.po ..\..\..\default.po-o default.po
del default-old.po
popd

Він буде генерувати РВ файли з нашого джерела тулзой dxgettext.exe файл default.po в кореневій папці програми. Так як при генерації створюється файл без перекладів, то потрібно врахувати, що при додаванні туди переказів при наступному виклику dxgettext вони зникнуть. Для цього скрипт далі робить злиття (msgmergedx) нового файлу без перекладів зі старим файлом з перекладами. У підсумку ми збережемо всі старі записи і переклади, а нові записи додадуться без перекладу. Потрібно врахувати, що всі однакові тексти в оригіналі будуть згруповані в одну запис і переведені однаково. Тобто якщо у нас на трьох формах буде пункт меню"Save", то в РВ файлі буде лише один запис і переклад буде однаковим для всіх трьох форм. Корисна фіча dxgettext в тому, що в коментарі для запису будуть перераховані всі місця в коді, де вона зустрічається.
Тепер спробуємо щось перекласти. будь редактор з просторів інтернету, можна просто в текстовому редакторі, але я взяв редактор від того самого автора, мого колишнього боса — Gorm. Він точно також з відкритими джерела і насичений фічами більше нікуди. Отже, йдемо в папкуlocale\en\LC_MESSAGES\ і відкриваємо Gorm'омdefault.po.
Для коректної роботи перекладу потрібно вказати яку мову цього файлу, і при бажанні іншу інфу — автор перекладу, версія, і т. д. Для цього відкриваємо File / Edit header в полі Language вибираємо English. Можна помітити, що в переклад потрапив і непотрібний нам пункт з ім'ям шрифту. Щоб його не переводити можна натиснути кнопку Ignore праворуч. А для решти впишемо переклад в полі Translation внизу:

Збережемо. То ж зробимо і для російської мови, вказавши його в хэдере. Тепер щоб переклад з'явився в нашому додатку, РВ файл потрібно скомпілювати. Якщо був встановлений dxgettext, це можна зробити подвійним кліком по РВ файлу, можна прямо в Gorm'е — Tools / Compile to MO file. Для того, щоб наше додаток побачило файли перекладу, потрібно у проекту поміняти Output directory на кореневу папку проекту (порожній шлях або "."). Тепер запустимо його.


Крім того, можна позбутися від МО файлів, і додаток буде працювати з перекладами без них. Для цього створимо ще один скриптик в кореневій папці приложенияи назвемо йогоtranslate.cmd. Він нам скомпилит РВ файли (так, ще один спосіб це зробити) і впровадить перекази в ЕХЕ файл.

set sourceroot=%CD%\

echo Compiling language files and embedding those
echo English...
cd %sourceroot%locale\en\LC_MESSAGES
msgfmt.exe -o default.mo default.po
echo Russian...
cd ..\..\ru\LC_MESSAGES
msgfmt.exe -o default.mo default.po
cd ..\..\..

echo Embedding translations...
copy Project1.exe Project1_Translated.exe
assemble.exe --dxgettext Project1_Translated.exe

echo Compiling language files and embedding those completed

cd %sourceroot%
pause

Тепер у нас є файл Project1_Translated.exe, який можна перемістити в іншу папку, на іншу машину, і переклади в ньому вже вбудовані.

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

Рядок у форматі
Іноді потрібно в текст втсавить значення змінної. Для перекладу рядка не потрібно розбивати на шматки, адже тоді втрачається сенс і перекладачеві буде важко зрозуміти про що цей шматок. Переказ можна зробити таким чином:
var s:string;
d:integer;
begin
s:=_('Apple');
d:=7;
showmessage(format(_('%s count:%d'),[s,d]));
end;

Функція перекладу повинна бути всередині формату, тоді переклад на російську приміром буде "%s: %d". При цьому Gorm буде показувати попередження, якщо кількість параметрів формату в перекладі відрізняється.

Множина
У різних мовах множина може перекладатися по-різному в залежності від кількості считаемых предметів. DxGetText може впоратися з цим завданням за допомогою функції ngettext. Їй потрібно передати слово в однині, в множині, і кількість для якого нам потрібен переклад. Приклад:
var i:integer;
s:string;
begin
s:=emptystr;
for i:=1 to do 11
s:=s+format(('%d %s'),[i, ngettext('apple', 'apples', i)])+slinebreak;
i:=21;
s:=s+format(('%d %s'),[i, ngettext('apple', 'apples', i)]);
showmessage(s);
end;


Тут проявляється перший мінус Gorm'а — такі переклади він не вміє редагувати. Тому викличемо updatepofiles.cmd для нашого проекту і спробуємо написати переклад в текстовому редакторі. Для цього відкриємоlocale\ru\LC_MESSAGES\default.po знайдемо наші яблука. Переклад буде ось таким:
msgid «apple»
msgid_plural «apples»
msgstr[0] «яблуко»
msgstr[1] «яблука»
msgstr[2] «яблук»
Скомпилим РВ файл і запустимо програму. Месседж з яблуками буде виглядати так:
English Українська
1 apple 1 яблуко
2 apples 2 яблука
3 apples 3 яблука
4 apples 4 яблука
5 apples 5 яблук
6 apples 6 яблук
7 apples 7 яблук
8 apples 8 яблук
9 apples 9 яблук
10 apples 10 яблук
11 apples 11 яблук
21 apples 21 яблуко
Однакові слова з різними значеннями
Якщо в додатку зустрічаються слова, які при різному значенні в мові за замовчуванням пишуться однаково (омоніми), то dxgettext.ехе, який витягує перекази в РВ файл, вважає це одним і тим же словом, і переклад відповідно буде однаковий у всіх місцях, де зустрічається це слово. Приміром, якщо у нас дивовижним чином в додатку потрібно перекласти слово «bow» як цибуля (який стріляє), і як смичок для музичних інструментів, то вийде невизначеність. Приклад:
var s:string;
begin
s:=_('bow');
Showmessage(
format(_('Use %s to shoot enemies.'),[s]) + slinebreak +
format(_('Use %s to play violin.'),[s]));
end;

Тут вже або стріляти смичком, або грати цибулею. Вирішити таку проблему можна розділивши слово за значенням.
var s1,s2:string;
begin
s1:=_('bow_music');
s2:=_('bow_weapon');
Showmessage(
format(_('Use %s to shoot enemies.'),[s2]) + slinebreak +
format(_('Use %s to play violin.'),[s1]));
end;


Результат:
Використовуй цибулю для стрільби по ворогам.
Використовуй смичок для гри на скрипці.
Домени
Іноді додаток розділене на модулі, і ці модулі потрібно перекладати окремо. Для цього в dxgettext використовуються домени. За замовчуванням усі переклади потрапляють в домен«default»тому і РО файл так називається. Але якщо ми будемо використовувати функції dgettext або dngettext домен де вказується параметром, то переклад буде потрапляти в РВ файл з вказаним доменом і відповідно пошук перекладу буде виконуватися у файлі з ім'ям домена. Крім того, домен можна встановити перманентно процедурою textdomain.

Висновки

Функціоналу dxgettext вистачає з головою для локалізації навіть професійних та великих програмних продуктів. Працює шустро, при впровадженні переказів в виконуваний файл додає кілька мегабайт до його об'єму, що стерпно у разі Delphi, де невеликий додаток і так вже важить кілька десятків мегабайт ;)

P. S: Исходники розглянутого прикладу можна скачати тут.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.