среда, 21 марта 2012 г.

Урок 37. Локализация интерфейса

Наверное, не правильно говорить о переводе всех названий пунктов меню и кнопок на другие языки, если Вы пишете программу для использования в домашнем хозяйстве. Да, это так, если не учитывать, что на страницах этого сайта я преследую несколько иную цель: не дать исходники, а дать "know how" - умения.

Я уже где-то говорил, и не устану повторять:  для решения той или иной задачи существует несколько способов. Вдаваться в полемику в пользу того или иного мне не интересно. Лишь обозначу, что для решения задачи локализации можно использовать библиотеку, т.е. Dll-ку, а можно - файл настроек. Обязательно найдутся охотники по ратовать за использование реестра Windows. Это, еще раз повторю - дело вкуса. В каждом из способов есть свои достоинства и свои недостатки.

Известно, что файл настроек может иметь вот такую структуру:

[Название секции]
Имя переменной1=Значение переменной1
Имя переменной2=Значение переменной2
...
Имя переменнойN=Значение переменнойN


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

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

Чтобы файлы языковой поддержки не путались с файлами настроек (ini), предлагаю дать им расширение *.lng.

Вот примерное содержимое двух файлов для отображения интерфейса программы "Расходы" на русском и английском языках:

Rus.lng


[MainFrm]
MainFormCap=Расходы (главная форма)
Menu_File=&Файл
Menu_File_Exit=&Выход
Menu_Data=&Данные
Menu_Data_Edit=&Ввести
Menu_Data_Insert=&Новая запись
Menu_Data_Filter=&Фильтр
Menu_Data_Credit=&Кредиты
Menu_Data_Export=&Экспорт
Menu_Data_Import=&Импорт
Menu_Reports=&Отчетные формы
Menu_Reports_Reverse_statement=&Оборотная ведомость
Menu_Reports_Turnovers_account=&Обороты по счету
Menu_Reports_Cost_Analysis_All=&Анализ затрат (все)
Menu_Reports_Cost_Analysis_Gruops=Анализ затрат (по &группам)
Menu_Reports_Cost_Analysis_Trends=Анализ &тенденций
Menu_Codifiers=&Кодификаторы
Menu_Codifiers_Accounts=&Счета
Menu_Codifiers_Group_costs=&Группы затрат
Menu_Codifiers_Memorial_warrants=&Мемориальные ордера
Menu_Codifiers_Valuta=&Валюты
Menu_Service=&Настройки
Menu_Service_SelectDisk=Выбор &диска (пути к базе данных)
Menu_Service_Language=&Выбор языка
Menu_Service_Clear=&Очистка БД
Menu_Help=&Справка
Menu_Help_About=&О программе


Eng.lng


[MainFrm]
MainFormCap=Expenses (Main form)
Menu_File=&File
Menu_File_Exit=&Exit
Menu_Data=&Data
Menu_Data_Edit=&Edit
Menu_Data_Insert=&New Record
Menu_Data_Filter=&Filter
Menu_Data_Credit=&Credits
Menu_Data_Export=&Export
Menu_Data_Import=&Import
Menu_Reports=&Reports
Menu_Reports_Reverse_statement=&Reverse statement
Menu_Reports_Turnovers_account=&Turnovers account
Menu_Reports_Cost_Analysis_All=&Cost analysis (All)
Menu_Reports_Cost_Analysis_Gruops=Cost analysis (&gruops)
Menu_Reports_Cost_Analysis_Trends=Check &Trends
Menu_Codifiers=&Codifiers
Menu_Codifiers_Accounts=&Accounts
Menu_Codifiers_Group_costs=&Group costs
Menu_Codifiers_Memorial_warrants=&Memorial warrants
Menu_Codifiers_Valuta=&Valuta
Menu_Service=&Service
Menu_Service_SelectDisk=&Select path
Menu_Service_Language=&Language
Menu_Service_Clear=&Clear data
Menu_Help=&Help
Menu_Help_About=&About prog

Файлы эти создаются как и файлы ini в любом текстовом редакторе, например: в блокноте.
Важно: имена переменных в этих файлах и количество строк, а следовательно и количество переменных, в этих файлах должно совпадать.

Кстати, в Rashod.ini должна пополниться секция:

[Constants]
LangFileName=Rus.lng

- пусть уж программа стартует с привычного нам языка, чтоб самих себя не испугать :-)

На этом предварительные танцы с бубнами и беличьими хвостиками закончены. Открываем проект "Rashod".

И первое, что необходимо сделать - создать процедуру, устанавливающую локализацию, назову ее SetLang (не забудьте декларировать ее в секции private):

procedure TMainFrm.SetLang();
Var
  ini: TIniFile;
begin

  // Настройка интерфейса программы (по выбранному языку)
  ini:=TiniFile.Create(GetCurrentDir + '\'+Lang);

  MainFrm.Caption:=Ini.ReadString('MainFrm','MainFormCap','');
  N_File.Caption:=Ini.ReadString('MainFrm','Menu_File','');
  N_Exit.Caption:=Ini.ReadString('MainFrm','Menu_File_Exit','');
  N_Data.Caption:=Ini.ReadString('MainFrm','Menu_Data','');
  N_Edit.Caption:=Ini.ReadString('MainFrm','Menu_Data_Edit','');
  N12_Ins.Caption:=Ini.ReadString('MainFrm','Menu_Data_Insert','');
  N10_Filter.Caption:=Ini.ReadString('MainFrm','Menu_Data_Filter','');
  N_Credit.Caption:=Ini.ReadString('MainFrm','Menu_Data_Credit','');
  N_ExportToMDB.Caption:=Ini.ReadString('MainFrm','Menu_Data_Export','');
  N_Import.Caption:=Ini.ReadString('MainFrm','Menu_Data_Import','');
  N_Reports.Caption:=Ini.ReadString('MainFrm','Menu_Reports','');
  N3.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Reverse_statement','');
  N4.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Turnovers_account','');
  N_Analiz.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Cost_Analysis_All','');
  N_AnalizGroup.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Cost_Analysis_Gruops','');
  N_Analiz_Trends.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Cost_Analysis_Trends','');
  N_Kodifikators.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers','');
  N1.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers_Accounts','');
  N_ExpGr.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers_Group_costs','');
  N2.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers_Memorial_warrants','');
  N_Val.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers_Valuta','');
  N_Serv.Caption:=Ini.ReadString('MainFrm','Menu_Service','');
  N7_SelectDisk.Caption:=Ini.ReadString('MainFrm','Menu_Service_SelectDisk','');
  N_Serv_SelLang.Caption:=Ini.ReadString('MainFrm','Menu_Service_Language','');
  N12_ServClear.Caption:=Ini.ReadString('MainFrm','Menu_Service_Clear','');
  N_Help.Caption:=Ini.ReadString('MainFrm','Menu_Help','');
  N_About.Caption:=Ini.ReadString('MainFrm','Menu_Help_About','');

  ini.Free;

end;

Общий смысл этой процедуры, думаю, не вызывает вопросов, поскольку, о работе с ini фалами я уже рассказывал ранее: открыли, считали нужные данные, присвоив их целевым надписям на элементах управления формы, освободились от ini файла.

Необходимая переменная Lang, хранящая имя языкового файла, должна быть объявлена в секции interface модуля формы. А вот значение в нее попадет в момент создания формы (Create), откуда - из Rashod.ini:

procedure TMainFrm.FormCreate(Sender: TObject);
var
  ini: TIniFile;
begin

  // Подключение и считывание из файла настроек ini
  ini:=TiniFile.Create(GetCurrentDir + '\Rashod.ini');
  Lang:=Ini.ReadString('Constants','LangFileName','');
  ini.Free;

  SetLang;
...

- И что? - скажете Вы после компиляции проекта, - никаких изменений нет.
Видимых изменений нет. Попробуйте поменять в Rashod.ini Rus.lng на Eng.lng и еще раз запустить программу.

Можно радоваться? 
Рано.

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

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

procedure TMainFrm.N_Serv_SelLangClick(Sender: TObject);  // Настройка. Выбор языка
var
  ini: TIniFile;
begin

  // Настройка фильтра файлов определенного типа в окне диалога
  OpenDialog1.Filter:='Файлы языковой поддержки|*.lng';

  // Выбор файла на диске
  If not OpenDialog1.Execute
  then                           // Отмена выбора
  begin
    exit;
  end;

  // Извлекается
  Lang:=OpenDialog1.FileName;    // Полный путь
  Lang:=ExtractFileName(Lang);   // Только имя файла

  // Запись в соответствующую секцию Rashod.ini, чтобы
  // в следующий раз программа стартовала с этими настройками
  ini:=TiniFile.Create(GetCurrentDir + '\Rashod.ini');
  Ini.WriteString('Constants','LangFileName',Lang);
  ini.Free;

  // Смена надписей на элементах управления
  SetLang;

end;

В комментариях я постарался дать необходимые пояснения. Вот теперь программа "Rashod" еще больше стала похожа на "взрослую" :-)

Я не затрагиваю так или иначе вопросы оптимизации уже написанного кода - это отдельная непростая история. Но здесь меня просто подмывает сделать оговорку:

вместо двух строк

  Lang:=OpenDialog1.FileName;    // Полный путь
  Lang:=ExtractFileName(Lang);   // Только имя файла

можно написать одну

  Lang:=ExtractFileName(OpenDialog1.FileName);

Иногда приходится жертвовать красотой ради наглядности :-)

Если остались вопросы - пожалуйте на страницу "Обратная связь"







2 комментария:

  1. Не получается задекларировать SetLang в секции private, ошибка, покажите как на примере

    ОтветитьУдалить
  2. Анонимный12/9/15 13:22

    Та же фигня, как задекларировать? Какие параметры у процедуры???

    ОтветитьУдалить