Поддержка многоязычного интерфейса
|
Программирование на С++ у госслужащих - как секс у подростков:
- все об этом думают;
- все об этом говорят;
- все думают, что их ближний это делает;
- почти никто этого не делает;
- тот, кто это делает, делает это плохо;
- все думают, что в следующий раз лучше получится;
- никто не принимает мер безопасности;
- любому стыдно признаться в том, что он чего-то не знает;
- если у кого-то что-то получается, от этого всегда много шума.
|
Подчас бывает актуально встроить в разрабатываемую программу поддержку нескольких языков. Существует множество средств и компонентов для осуществления подобных задач. У всех этих средств один недостаток - они слишком сложны и тяжеловесны. Предлагаем рассмотреть, как можно обеспечить поддержку многоязычности используя более простой и прозрачный метод.
Первое, что нужно выяснить - это язык, на котором разрабатывать интерфейс первоначально. Есть веские причины за то, чтобы использовать для этого именно тот язык, на котором написана эта статья. Дело в том, что русский язык менее лаконичен других европейских языков. При переводе на английский или немецкий 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.
|
|