Программирование на С++ у госслужащих - как секс у подростков:
- все об этом думают;
- все об этом говорят;
- все думают, что их ближний это делает;
- почти никто этого не делает;
- тот, кто это делает, делает это плохо;
- все думают, что в следующий раз лучше получится;
- никто не принимает мер безопасности;
- любому стыдно признаться в том, что он чего-то не знает;
- если у кого-то что-то получается, от этого всегда много шума.
Подчас бывает актуально встроить в разрабатываемую программу поддержку нескольких языков. Существует множество средств и компонентов для осуществления подобных задач. У всех этих средств один недостаток - они слишком сложны и тяжеловесны. Предлагаем рассмотреть, как можно обеспечить поддержку многоязычности используя более простой и прозрачный метод.
Первое, что нужно выяснить - это язык, на котором разрабатывать интерфейс первоначально. Есть веские причины за то, чтобы использовать для этого именно тот язык, на котором написана эта статья. Дело в том, что русский язык менее лаконичен других европейских языков. При переводе на английский или немецкий 90% фраз будет компактнее и интерфейс вашей программы искажен не будет.
Для поддержки нескольких языков предлагается следующий простой подход. Интерфейс оформляется на родном языке - русском. Для всех остальных языков составляется словарь в виде:
Строка на языке 1=Строка на языке 2
Строка на языке 1=Строка на языке 2
Например:
Файл=File
Выход=Exit
Отмена=Cancel
И так для всех ресурсов приложения. Словарь поместим в отдельный текстовый файл.
Далее, нам необходимо для каждого текстового свойства любого компонента приложения поискать перевод в нашем словаре. Здесь не обойтись без Delphi RTTI. Через Component.ClassInfo получим ссылку на информацию типа, а затем GetTypeData(TypeInf) даст нам указатель на структуру с его описанием.
Далее проходимся по всем свойствам данного (классового) типа:
GetMem(PropList, NumProps * sizeof(pointer));
try
GetPropInfos(TypeInf, PropList);
for i := 0 to NumProps-1 dobegin
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)thenbegin{ Для дочерних свойств-классов вызов просмотра свойств }if (PropObject is TPersistent) then
UpdateComponent(PropObject as TPersistent);
{ Индивидуальный подход к некоторым классам }if (PropObject is TStrings) thenbeginfor j := 0 to (PropObject as TStrings).Count-1 do
TStrings(PropObject)[j] := TranslateString(TStrings(PropObject)[j]);
end;
if (PropObject is TTreeNodes) thenbeginfor 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) thenbeginfor 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 = setof (lofTrimSpaces);
{опция удаления начальных и завершающих пробелов}
TglLanguageLoader = class(TComponent)
private
sl: TStringList;
FOptions: TLanguageLoaderOptions;
function TranslateString(sString: string): string;
protectedprocedure UpdateComponent(Component: TPersistent); virtual;
public{main function}procedure LoadLanguage(Component: TComponent; FileName: string);
publishedproperty Options: TLanguageLoaderOptions read FOptions write FOptions;
end;
procedure LoadLanguage(Component: TComponent; FileName: string;
Options: TLanguageLoaderOptions);
procedureregister;
implementationuses
TypInfo, dsgnintf;
procedureregister;
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 dobegin
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)thenbegin{ Для дочерних свойств-классов вызов просмотра свойств }if (PropObject is TPersistent) then
UpdateComponent(PropObject as TPersistent);
{ Индивидуальный подход к некоторым классам }if (PropObject is TStrings) thenbeginfor j := 0 to (PropObject as TStrings).Count-1 do
TStrings(PropObject)[j] := TranslateString(TStrings(PropObject)[j]);
end;
if (PropObject is TTreeNodes) thenbeginfor 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) thenbeginfor 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;
beginif lofTrimSpaces in Options then
sString := trim(sString);
if sString = '' thenbegin
Result := '';
exit;
end;
if sl.IndexOfName(sString) <> -1 then
Result := sl.Values[sString]
else
Result := sString;
end;
end.
Текст посвящен реализации поддержки нескольких языков в программе с использованием Delphi. Автор утверждает, что существующие решения слишком сложны и тяжелы, и предлагает более простой подход.
Предложенный подход заключается в создании файла словаря с парами переводов для каждого языка. Например:
Строка на языке 1=Слово на языке 2
Строка на языке 1=Слово на языке 2
Этот файл словаря будет загружен в программу, а затем программа будет использовать его для перевода строк.
Статья также предоставляет пример кода для Delphi, который демонстрирует, как реализовать этот подход. Код использует компонент TglLanguageLoader для загрузки файла словаря и затем перебирает все свойства заданного компонента, используя RTTI (Run-Time Type Information) для перевода строковых свойств с помощью загруженного словаря.
Код подробно описан, но вот некоторые ключевые точки:
Компонент загружает файл словаря с помощью метода LoadFromFile.
Компонент затем перебирает все свойства заданного компонента с использованием RTTI.
Для каждого свойства, имеющего строковый тип, компонент проверяет, есть ли оно в файле словаря и переводит его, если необходимо.
Компонент также обрабатывает специальные случаи, такие как перевод списка строк (например, TStrings) или узлов дерева (TTreeNodes).
В целом, эта статья предлагает простой и эффективный способ реализации поддержки нескольких языков в программах Delphi.
Поддержка многоязычного интерфейса: простой и прозрачный метод использования словаря для перевода строковых ресурсов на различных языках.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.