Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Поддержка многоязычного интерфейса

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

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

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

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay