Поддержка многоязычного интерфейса

Поддержка многоязычного интерфейса.
Подчас бывает актуально встроить в разрабатываемую программу поддержку нескольких языков. Существует множество средств и компонентов для осуществления подобных задач. У всех этих средств один недостаток - они слишком сложны и тяжеловесны. Предлагаем рассмотреть, как можно обеспечить поддержку многоязычности используя более простой и прозрачный метод. 
Первое, что нужно выяснить - это язык, на котором разрабатывать интерфейс первоначально. Есть веские причины за то, чтобы использовать для этого именно тот язык, на котором написана эта статья. Дело в том, что русский язык менее лаконичен других европейских языков. При переводе на английский или немецкий 90% фраз будет компактнее и интерфейс вашей программы искажен не будет. 
Для поддержки нескольких языков предлагается следующий простой подход. Интерфейс оформляется на родном языке - русском. Для всех остальных языков составляется словарь в виде:
 Строка на языке 1=Строка на языке 2
 Строка на языке 2=Строка на языке 2
 ...
Например:
 Файл=File
 Выход=Exit
 Отмена=Cancel
И так для всех ресурсов приложения. Словарь поместим в отдельный текстовый файл. 
Далее, нам необходимо для каждого текстового свойства любого компонента приложения поискать перевод в нашем словаре. Здесь не обойтись без Delphi RTTI. Через Component.ClassInfo получим ссылку на информацию типа, а затем GetTypeData(TypeInf) даст нам указатель на структуру с его описанием.
 TypeInf := Component.ClassInfo;
 AName := TypeInf^.Name;
 TypeData := GetTypeData(TypeInf);
 NumProps := TypeData^.PropCount;
Далее проходимся по всем свойствам данного (классового) типа.
 GetMem(PropList, NumProps * SizeOf(Pointer));
 try
 GetPropInfos(TypeInf, PropList);
 for i := 0 to NumProps-1 do
 begin
 PropName := PropList^[i]^.Name;
 PropTypeInf := PropList^[i]^.PropType^;
 PropInfo := PropList^[i];
 case PropTypeInf^.Kind of
 tkString, tkLString: // ... это то, что нам нужно
 if PropName <> 'Name' then { Переводить свойство Name не следует }
 begin
 { Получение значения свойства и поиск перевода в словаре }
 StringPropValue := GetStrProp(Component, PropInfo);
 SetStrProp( Component, PropInfo, TranslateString(StringPropValue) );
 end;
Отдельный случай - списки TStrings и коллекции типа TTReeNodes и TListItems. Их придется обработать персонально. 
 tkClass:
 begin
 PropObject := GetObjectProp(Component, PropInfo{, TPersistent});
 if Assigned(PropObject)then
 begin
 { Для дочерних свойств-классов вызов просмотра свойств }
 if (PropObject is TPersistent) then
 UpdateComponent(PropObject as TPersistent);
 { Индивидуальный подход к некоторым классам }
 if (PropObject is TStrings) then
 begin
 for j := 0 to (PropObject as TStrings).Count-1 do
 TStrings(PropObject)[j] := TranslateString(TStrings(PropObject)[j]);
 end;
 if (PropObject is TTreeNodes) then
 begin
 for j := 0 to (PropObject as TTreeNodes).Count-1 do
 TTreeNodes(PropObject).Item[j].Text :=
 TranslateString(TTreeNodes(PropObject).Item[j].Text);
 end;
 if (PropObject is TListItems) then
 begin
 for j := 0 to (PropObject as TListItems).Count-1 do
 TListItems(PropObject).Item[j].Caption
 := TranslateString(TListItems(PropObject).Item[j].Caption);
 end;
 { Здесь можно добавить обработку остальных классов }
 end;
 end;
Объединяя все написанное, получим компонент для перевода строковых ресурсов.
{
Globus Delphi VCL Extensions Library
Freeware
Copyright (c) 1998,2001 Chudin A.V, chudin@yandex.ru
 ===================================================================
 glLanguageLoader Unit 04.2001 component TglLanguageLoader
 ===================================================================
 Load new string resources from file to components
 Словарь в виде текста вида:
 Строка на языке 1=Строка на языке 2
 ...
 Строка на языке 1=Строка на языке 2
 ===================================================================
}
unit glLanguageLoader;
interface
{$I glDEF.INC}
uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, comctrls, grids;
type
 TLanguageLoaderOptions = set of (lofTrimSpaces);
 { опция удаления начальных и завершающих пробелов }
 TglLanguageLoader = class(TComponent)
 private
 sl: TStringList;
 FOptions: TLanguageLoaderOptions;
 function TranslateString(sString: string): string;
 protected
 procedure UpdateComponent(Component: TPersistent); virtual;
 public
 procedure LoadLanguage(Component: TComponent; FileName: string); {main function}
 published
 property Options: TLanguageLoaderOptions read FOptions write FOptions;
 end;
procedure LoadLanguage(Component: TComponent; FileName: string; Options: TLanguageLoaderOptions);
procedure Register;
implementation
uses TypInfo, dsgnintf;
procedure Register;
begin
 RegisterComponents('Gl Components', [TglLanguageLoader]);
end;
{ Функция для загрузки словаря без предварительного создания компонента }
procedure LoadLanguage(Component: TComponent; FileName: string; Options: TLanguageLoaderOptions);
var
 LanguageLoader: TglLanguageLoader;
begin
 LanguageLoader := TglLanguageLoader.Create(nil);
 try
 LanguageLoader.LoadLanguage(Component, FileName);
 finally
 LanguageLoader.Free;
 end;
end;
{ TglLanguageLoader }
{ Загрузка словаря, обход указанного компонента и }
{ всех его дочерних компонентов }
procedure TglLanguageLoader.LoadLanguage(Component: TComponent; FileName: string);
 procedure UpdateAllComponents(Component: TComponent);
 var i: integer;
 begin
 { обработка своцств компонента }
 UpdateComponent(Component);
 for i := 0 to Component.ComponentCount-1 do
 UpdateAllComponents(Component.Components[i]);
 end;
begin
 sl := TStringList.Create;
 try
 { Загрузка словаря из заданного файла }
 sl.LoadFromFile(FileName);
 sl.Sorted := true;
 UpdateAllComponents(Component);
 finally
 sl.Free;
 end;
end;
{ Проход по всем свойствам компонента }
{ Для всех строковых свойств - загрузка перевода из словаря }
procedure TglLanguageLoader.UpdateComponent(Component: TPersistent);
var
 PropInfo: PPropInfo;
 TypeInf, PropTypeInf: PTypeInfo;
 TypeData: PTypeData;
 i, j: integer;
 AName, PropName, StringPropValue: string;
 PropList: PPropList;
 NumProps: word;
 PropObject: TObject;
begin
 { Playing with RTTI }
 TypeInf := Component.ClassInfo;
 AName := TypeInf^.Name;
 TypeData := GetTypeData(TypeInf);
 NumProps := TypeData^.PropCount;
 GetMem(PropList, NumProps*sizeof(pointer));
 try
 GetPropInfos(TypeInf, PropList);
 for i := 0 to NumProps-1 do
 begin
 PropName := PropList^[i]^.Name;
 PropTypeInf := PropList^[i]^.PropType^;
 PropInfo := PropList^[i];
 case PropTypeInf^.Kind of
 tkString, tkLString:
 if PropName <> 'Name' then { Переводить свойство Name не следует }
 begin
 { Получение значения свойства и поиск перевода в словаре }
 StringPropValue := GetStrProp( Component, PropInfo );
 SetStrProp( Component, PropInfo, TranslateString(StringPropValue) );
 end;
 tkClass:
 begin
 PropObject := GetObjectProp(Component, PropInfo{, TPersistent});
 if Assigned(PropObject)then
 begin
 { Для дочерних свойств-классов вызов просмотра свойств }
 if (PropObject is TPersistent) then
 UpdateComponent(PropObject as TPersistent);
 { Индивидуальный подход к некоторым классам }
 if (PropObject is TStrings) then
 begin
 for j := 0 to (PropObject as TStrings).Count-1 do
 TStrings(PropObject)[j] := TranslateString(TStrings(PropObject)[j]);
 end;
 if (PropObject is TTreeNodes) then
 begin
 for j := 0 to (PropObject as TTreeNodes).Count-1 do
 TTreeNodes(PropObject).Item[j].Text :=
 TranslateString(TTreeNodes(PropObject).Item[j].Text);
 end;
 if (PropObject is TListItems) then
 begin
 for j := 0 to (PropObject as TListItems).Count-1 do
 TListItems(PropObject).Item[j].Caption :=
 TranslateString(TListItems(PropObject).Item[j].Caption);
 end;
 { Здесь можно добавить обработку остальных классов }
 end;
 end;
 end;
 end;
 finally
 FreeMem(PropList, NumProps*sizeof(pointer));
 end;
end;
{ Поиск перевода для заданной строки в словаре }
function TglLanguageLoader.TranslateString(sString: string): string;
begin
 if lofTrimSpaces in Options then sString := trim(sString);
 if sString = then
 begin
 Result := ;
 exit;
 end;
 if sl.IndexOfName(sString) <> -1 then Result := sl.Values[sString] else Result := sString;
end;
end.
Author: Андрей Чудин, ЦПР ТД Библио-Глобус
Source: http://delphi.chertenok.ru
ID: 04556
Category:Разработка приложений