Интернационализация (локализация) приложения на Lazarus

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


За основу взял мануалы: раз, два и три. Увы, материалы написаны в разное время, для разных версий IDE (и как мне показалось, нерабочие). Поэтому результаты своего опыта опишу здесь.

1. Создаем обычный проект, кладем на форму TRadioGroup с тремя Item, два TLabel и кнопку




















2. Заходим в настройки проекта, в пункте i18n указываем папку, в которой будут лежать файлы локализации (у меня этот папка /lang), помечаем чекбокс "Create/Update...", если хотим видеть все компоненты в *.pot файле при сохранении файла формы *.lfm (по умолчанию будет создан файл <название_проекта>.pot)




и сохраняем настройки проекта.


3. Если вы хотите при каждой сборке принудительно обновлять po-файлы с переводом, чекайте крыжик "Force update PO files..."

4. Добавляем в секции interface в uses модуль LCLTranslator (не добавляйте DefaultTranslator - он пустой; если попытаетесь из него вызвать SetDefaultLang, при компиляции получите ошибку "Identifier not found "SetDefaultLang")


5. Теперь компилируем проект и смотрим содержимое папки /lang. Там имеется единственный файл <название проекта>.pot.  Открываем его обычным блокнотом и смотрим содержимое

msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"

#: tform1.button1.caption
msgid "Button1"
msgstr ""

#: tform1.caption
msgid "Form1"
msgstr ""

#: tform1.label1.caption
msgid "Label1"
msgstr ""

 
#: tform1.label2.caption
msgid "Label2"
msgstr ""

Пустые двойные кавычки msgstr "" означают, что их содержимое повторяет содержимое кавычек предыдущей msgid. Если бы я в интерфейсе программы поменял название (Caption), например c "TLabel" на "Надпись", то в файле мы бы увидели msgstr "Надпись" (или загляните в корень своего Лазаруса, в папке /languages откройте любой *.po файл и посмотрите его содержимое).
Чтобы сделать немецкую локализацию, меняем содержимое на следующее

msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" 

#: tform1.button1.caption 
msgid "Button1" 
msgstr "Taste" 

#: tform1.caption 
msgid "Form1" 
msgstr "Bilden" 

#: tform1.label1.caption 
msgid "Label1" 
msgstr "Etikette"



и сохраняем файл под именем <название проекта>.de.po в папке /lang (я ради эксперимента сохранил под именем i18n.de.po, хотя название проекта i18.lpi - соответственно pot-файл у меня сохраняется под именем i18.pot).

То же самое делаем для русской и английской локализации, сохраняя файлы (для наглядности эксперимента) соответственно i18.ru.po и i18.en.po (как у *.pot файла)

Что делать, если какой-то элемент интерфейса не отображается в *.pot файле (например, заголовки Rx-грида) или нам необходимо локализовать пользовательские сообщения (например, в MessageBox)?

Для этого достаточно задать имя и строковое значение сообщения в resourcestring, например
uses
 Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
 StdCtrls, LCLTranslator;

resourcestring
  MsgCaption = 'Hello, World';

type 
...
Тогда в *.pot-файле проекта появится
#: unit1.msgcaption
msgid "Hello, World"
msgstr ""
Достаточно будет определить ее значение для каждого языка, потом использовать в коде.

6. Теперь пропишем смену языка интерфейса по клику на радиокнопки
procedure TForm1.rgLangClick(Sender: TObject);
begin 
case rgLang.ItemIndex of
 0: SetDefaultLang('en','lang');
1: SetDefaultLang('
ru','lang');
2: SetDefaultLang('
de','lang','i18n');//здесь имя файла указываем самостоятельно,
                                             //потому что оно не совпадает с именем pot-файла
 end;
Label2.Caption:= MsgCaption;

end;


У функции SetDefaultLang(Lang: string; Dir: string = ''; LocaleFileName: string = ''; ForceUpdate: boolean = true): string; четыре параметра (из описания к функции):

  • первый (Lang: string;) - язык (например, «ru», «de»); пустой аргумент является языком по умолчанию, т.е.берется исходный файл <название проекта>.po) - название *.po файла (я подозреваю, что того самого префикса),
  • второй (Dir: string = ' ';) -подкаталог пользовательских файлов перевода (например, «mylng»); пустой аргумент означает поиск только в предопределенных подкаталогах,
  • третий (LocaleFileName: string = ' ';) - имя файла пользовательского перевода; пустой аргумент означает, что имя совпадает с именем исполняемого файла,
  • четверый (ForceUpdate: boolean = true;) - true означает принудительное немедленное обновление интерфейса. Должно быть установлено значение false, когда процедура вызывается из раздела инициализации устройства. Пользовательский код обычно не должен указывать его.

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

В результате получаем






















Пример проекта на гитхабе

Популярные сообщения из этого блога

Как я устанавливал Firebird 3 на Debian 8

Как я ставил транковый fpc и lazarus на Debian 7.6 x32

Мемориальные заметки для Lazarus