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

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Программирование на С у госслужащих — как секс у подростков:

— все об этом думают;

— все об этом говорят;

— все думают, что их ближний это делает;

— почти никто этого не делает;

— тот, кто это делает, делает это плохо;

— все думают, что в следующий раз лучше получится;

— никто не принимает мер безопасности;

— любому стыдно признаться в том, что он чего-то не знает;

— если у кого-то что-то получается, от этого всегда много шума.

Подчас бывает актуально встроить в разрабатываемую программу поддержку нескольких языков. Существует множество средств и компонентов для осуществления подобных задач. У всех этих средств один недостаток — они слишком сложны и тяжеловесны. Предлагаем рассмотреть, как можно обеспечить поддержку многоязычности используя более простой и прозрачный метод.

Первое, что нужно выяснить — это язык, на котором разрабатывать интерфейс первоначально. Есть веские причины за то, чтобы использовать для этого именно тот язык, на котором написана эта статья. Дело в том, что русский язык менее лаконичен других европейских языков. При переводе на английский или немецкий 90% фраз будет компактнее и интерфейс вашей программы искажен не будет.

Для поддержки нескольких языков предлагается следующий простой подход. Интерфейс оформляется на родном языке — русском. Для всех остальных языков составляется словарь в виде:

Строка на языке 1=Строка на языке 2

Строка на языке 1=Строка на языке 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;

Объединяя все написанное, получим компонент для перевода строковых ресурсов.

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

{main function}

procedure LoadLanguage(Component: TComponent; FileName: string);

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.

{/codecitation}

Добавить комментарий