Интернационализация (локализация) приложения на Lazarus
Видя мучения дельфистов, которые для добавления многоязычного интерфейса вынуждены либо пользоваться сторонним софтом, помещая варианты перевода в ресурсы, либо загружая из базы данных, я решил выяснить, как с этим обстоит дело в Лазаре.
2. Заходим в настройки проекта, в пункте i18n указываем папку, в которой будут лежать файлы локализации (у меня этот папка /lang), помечаем чекбокс "Create/Update...", если хотим видеть все компоненты в *.pot файле при сохранении файла формы *.lfm (по умолчанию будет создан файл <название_проекта>.pot)
msgid "" msgstr "Content-Type: text/plain; charset=UTF-8"
и сохраняем файл под именем <название проекта>.de.po в папке /lang (я ради эксперимента сохранил под именем i18n.de.po, хотя название проекта i18.lpi - соответственно pot-файл у меня сохраняется под именем i18.pot).
То же самое делаем для русской и английской локализации, сохраняя файлы (для наглядности эксперимента) соответственно i18.ru.po и i18.en.po (как у *.pot файла)
Что делать, если какой-то элемент интерфейса не отображается в *.pot файле (например, заголовки Rx-грида) или нам необходимо локализовать пользовательские сообщения (например, в MessageBox)?
Для этого достаточно задать имя и строковое значение сообщения в resourcestring, например
6. Теперь пропишем смену языка интерфейса по клику на радиокнопки
У функции SetDefaultLang(Lang: string; Dir: string = ''; LocaleFileName: string = ''; ForceUpdate: boolean = true): string; четыре параметра (из описания к функции):
В результате получаем
За основу взял мануалы: раз, два и три. Увы, материалы написаны в разное время, для разных версий IDE (и как мне показалось, нерабочие). Поэтому результаты своего опыта опишу здесь.
1. Создаем обычный проект, кладем на форму TRadioGroup с тремя Item, два TLabel и кнопку
и сохраняем настройки проекта.
3. Если вы хотите при каждой сборке принудительно обновлять po-файлы с переводом, чекайте крыжик "Force update PO files..."
4. Добавляем в секции interface в uses модуль LCLTranslator (не добавляйте DefaultTranslator - он пустой; если попытаетесь из него вызвать SetDefaultLang, при компиляции получите ошибку "Identifier not found "SetDefaultLang")
5. Теперь компилируем проект и смотрим содержимое папки /lang. Там имеется единственный файл <название проекта>.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 ""
Пустые двойные кавычки msgstr "" означают, что их содержимое повторяет содержимое кавычек предыдущей msgid. Если бы я в интерфейсе программы поменял название (Caption), например c "TLabel" на "Надпись", то в файле мы бы увидели msgstr "Надпись" (или загляните в корень своего Лазаруса, в папке /languages откройте любой *.po файл и посмотрите его содержимое).
Чтобы сделать немецкую локализацию, меняем содержимое на следующее
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 файл и посмотрите его содержимое).
Чтобы сделать немецкую локализацию, меняем содержимое на следующее
#: 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 файла. Именно поэтому для немецкой локали мы указываем его имя.
В результате получаем
Пример проекта на гитхабе