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

Материал из DRKB


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

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

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