Среда, 08.05.2024
Королевство Delphi
Главное меню
Статьи
Наш опрос
Как часто ви на этот сайт заходите?
Всего ответов: 159
Статистика
Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Главная » Статьи » Разные » Разные

Перевод в Delphi-приложениях

Реализовать перевод в приложениях Delphi можно реализовать несколькими способами:

  • стандартный способ локализации.
  • локализация с помощью текстовых ресурсов: ini-файл или xml-файл.

Стандартный способ локализации приложений

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

Этот способ имеет как преимущества, так и недостатки.

К преимуществам, можно отнести: скорость работы данной реализации, а так же то, что этот способ реализован в самом Delphi.

Недостатки:

  • Нужно переводить прямо в среде разработки Delphi.
  • По умолчанию, извлекается ресурс, того языка, какой установлен в Windows.

Локализация с помощью текстовых ресурсов

К сожалению, локализация с помощью текстовых ресурсов в Delphi не предусмотрена. Хотя, иногда данный способ может быть более предпочтительным, чем перевод с помощью ресурсов, реализованный в Delphi.

К преимуществам данного способа можно отнести:
- Возможность перевода без среды Delphi. Более того, из любого текстового редактора. - Как следствие предыдущего пункта – возможность перевода сот рудниками, не знаючими Delphi и не умеюми в нем работать.
- Совместимость разных версий с разными версиями программы.

К недостаткам данного способа можно отнести:
- Меньшую скорость работы, чем через ресурсы.
- Не реализован данный способ в стандартной поставке Delphi.
- Больший размер файла, чем ресурсного файла.

В текстовый формат можно сохранять в виде: ini-файла, xml-файла или текст с заданными разделителями.

Есть компоненты, которые реализуют подобную задачу, но чаще всего, эти компоненты платные.

В данной статье мы опишем способ локализации в формате xml.

Локализация с помощью xml-файлов

Для локализации, воспользуемся некоторыми из функций проекта XMLWorks: http://www.DelphiHome.com/xml

Прежде всего, нужно определиться с тем, что мы переводим.

Мы переводим:

- строковые ресурсы;

- вариантные типы;

- символьные типы;

Все остальные типы данных мы не переводим.

Процесс перевода можно разделить на 2 этапа:

1-й этап. Генерация текстового файла для последующего перевода. Сохранение его. Перевод. Перенос в каталог соответствующего языка.

2-й этап. Загрузка в приложение из xml-файла.

Генерация текстового файла для последующего перевода

Для того, чтобы сгенерировать файл для перевода нам необходимо перебрать все компоненты и все свойства, сформировать текстовый файл.

Необходимо учитывать, что на форме могут находиться не только компоненты, но и фреймы, которые сами в себя включают другие компоненты.

Так же могут быть компоненты, которые мы не хотим переводить. Их нужно исключить из перевода. Так, например, не желательно переводить TDBEdit, TDBDateTimeEditE, TDBLookupComboboxEh, т.к. нам не нужно переводить информацию, взятую из базы данных.

Ниже, приводим функцию, которая формирует xml-файл для перевода.

function GenSQLLang(SelfInp: TObject): String;
Var i, b: integer;
BandTmp: TcxGridDBBandedTableView;
begin

 if (SelfInp is TComponent) then
Begin

 With (SelfInp as TComponent) Do
Begin
Result:=ObjectToXMLElements_Lang(SelfInp,-4);

 Result:=Result+Chr(13)+';
 

for i:=0 to ComponentCount-1 Do
begin
 

if (Trim(Components[i].Name)<>')And
(not((RusCompare(Components[i].ClassName,'TSaveDBGridEh'))
Or(RusCompare(Components[i].ClassName,'TpFIBTransaction'))
Or(RusCompare(Components[i].ClassName,'TpFIBStoredProc'))
Or(RusCompare(Components[i].ClassName,'TDBEdit'))
Or(RusCompare(Components[i].ClassName,'TDBDateTimeEditEh'))
Or(RusCompare(Components[i].ClassName,'TDBLookupComboboxEh'))
Or(RusCompare(Components[i].ClassName,'TDBComboBoxEh'))
)) then
begin
Result:=Result+Chr(13)
+'<'+Components[i].Name+'>'+Chr(13)+ObjectToXMLElements_Lang(Components[i],4)
+'+Chr(13);

 

 end;
end;

 Result:=Result+'+Chr(13)+Chr(13);

 End;
End;
end;

Функция для формирования xml для заданной компоненты:

function ObjectToXMLElements_Lang(const aObject:TObject; Space_Inp: integer): String;
var
i : Integer;
s : string;
StringList : TStringList;
Props: TList;
IsLangSet: Boolean;
begin
result := ';

 StringList := TStringList.Create;
try

 Props := GetPropertyList(aObject.ClassInfo);
try

 for i := 0 to Props.Count-1 do
begin

 s := GetPropAsString_Lang(AObject, PPropInfo(Props.Items[i]), IsLangSet, Space_Inp+4);
 

 if (IsLangSet)And(UpperCase(PPropInfo(Props.Items[i]).Name)<>UpperCase('Name'))And(Trim(PPropInfo 
 (Props.Items[i]).Name)<>') then
 StringList.Add(Space(Space_Inp)+'<' + PPropInfo(Props.Items[i]).Name + '>' + s + Space(Space_Inp)+'
 end;
result := StringList.Text;

 finally
 Props.Free;
end;

 finally
 StringList.Free;
end;

 end; 

Функция для формирования xml для заданного свойства:

function GetPropAsString_Lang(const Instance: TObject; const PropInfo: PPropInfo; 
Var IsLangSet: Boolean; Space_Inp: Integer): string;
var
 ObjectProp : TObject;
Intf: IXMLWorksObject;
begin
 

if (not Assigned(PropInfo^.PropType^))Or(UpperCase(Trim(PropInfo^.PropType^.Name))='NAME')
then Exit;
 

result := ';
IsLangSet:=False;

 case PropInfo^.PropType^.Kind of

 tkString,
tkLString,
tkWString:
Begin
 IsLangSet:=True;
 

 if AnsiSameText(PropInfo^.PropType^.Name, 'XMLString') then
 result := Trim(GetStrProp(Instance, PropInfo))
 else if AnsiSameText(PropInfo^.PropType^.Name, 'XMLMIMEString') then
 result := Base64Encode(GetStrProp(Instance, PropInfo))
 else
 begin
 result := StrToXML(Trim(GetStrProp(Instance, PropInfo)));
 end;
End;

 tkInt64: ;
tkSet,
tkInteger: ;
tkFloat: ;
tkVariant:
begin
 IsLangSet:=True;
 

 if GetVariantProp(Instance, PropInfo)=null
 then result := StrToXML(')
 else result := VariantToXML(Trim(GetVariantProp(Instance, PropInfo)));
end;

 tkChar,
tkWChar:
begin
 IsLangSet:=True;
 result := StrToXML(Chr(GetOrdProp(Instance, PropInfo)));
end;

 tkEnumeration: ;

 tkClass:
begin
end;

 tkInterface:
begin
 IsLangSet:=True;
 result := InterfaceToXML(GetIntfProp_Lang(Instance, PropInfo));
end;

 end;
end; 

Функции, которые используются в данном коде:

function GetIntfProp_Lang(Instance: TObject; PropInfo: PPropInfo): IUnknown;
asm

 { -> EAX Pointer to instance }
{ EDX Pointer to property info }
{ ECX Pointer to result interface }
PUSH ESI
PUSH EDI
MOV EDI,EDX

 MOV EDX,[EDI].TPropInfo.Index { pass index in EDX }
CMP EDX,$80000000
JNE @@hasIndex
MOV EDX,ECX { pass value in EDX }
@@hasIndex:
MOV ESI,[EDI].TPropInfo.GetProc
CMP [EDI].TPropInfo.GetProc.Byte[3],$FE
JA @@isField

 JB @@isStaticMethod
@@isVirtualMethod:
MOVSX ESI,SI { sign extend slot offset }
ADD ESI,[EAX] { vmt + slot offset }
CALL DWORD PTR [ESI]
JMP @@exit

 @@isStaticMethod:
CALL ESI
 

JMP @@exit

@@isField:
AND ESI,$00FFFFFF
ADD EAX, ESI
MOV EDX,[EAX]
MOV EAX, ECX
CALL AssignIntf

 @@exit:
POP EDI
POP ESI
end;


 function GetIntfProp(Instance: TObject; PropInfo: PPropInfo): IUnknown;
asm

 { -> EAX Pointer to instance }
{ EDX Pointer to property info }
{ ECX Pointer to result interface }

 PUSH ESI
PUSH EDI
MOV EDI,EDX

 MOV EDX,[EDI].TPropInfo.Index { pass index in EDX }
CMP EDX,$80000000
JNE @@hasIndex
MOV EDX,ECX { pass value in EDX }
@@hasIndex:
MOV ESI,[EDI].TPropInfo.GetProc
CMP [EDI].TPropInfo.GetProc.Byte[3],$FE
JA @@isField
JB @@isStaticMethod
@@isVirtualMethod:

 MOVSX ESI,SI { sign extend slot offset }
ADD ESI,[EAX] { vmt + slot offset }
CALL DWORD PTR [ESI]
JMP @@exit
@@isStaticMethod:
CALL ESI

 JMP @@exit
 

@@isField:
AND ESI,$00FFFFFF
ADD EAX, ESI
MOV EDX,[EAX]
MOV EAX, ECX
CALL AssignIntf

 @@exit:
POP EDI
POP ESI

 end;

Загрузка в приложение из xml-файла

Нам необходимо загрузить текстовый файл, декодировать информацию в нем и установить свойства.

Итак, процедура декодирования текстового файла:

Procedure DecodeSQLLang(SelfInp: TObject;StrInp: String);
Var PosTmp, PosTmp2: integer;
i: integer;
StrTmp: String;
begin
 

PosTmp:=0;

if SelfInp is TComponent then
With SelfInp as TComponent Do
Begin

 PosTmp:=Pos('ComponentsForm', StrInp);

if PosTmp=0
then StrTmp:=Copy(StrInp,1,Length(StrInp))
else StrTmp:=Copy(StrInp,1,PosTmp-2);
 

setXMLObject_Lang(SelfInp, StrInp);

 for i:=0 to ComponentCount-1 Do
begin

 if (Trim(Components[i].Name)<>')And
(not((RusCompare(Components[i].ClassName,'TSaveDBGridEh'))
Or(RusCompare(Components[i].ClassName,'TpFIBTransaction'))
Or(RusCompare(Components[i].ClassName,'TpFIBStoredProc'))
Or(RusCompare(Components[i].ClassName,'TDBEdit'))
Or(RusCompare(Components[i].ClassName,'TDBDateTimeEditEh'))
Or(RusCompare(Components[i].ClassName,'TDBLookupComboboxEh'))
Or(RusCompare(Components[i].ClassName,'TDBComboBoxEh'))
)) then
begin
 StrTmp:=RFastParseTagXML(StrInp,Components[i].Name);
 setXMLObject_Lang(Components[i], StrTmp);
end;

 end;

 End;

end;

Получение текста между тегами:

function RFastParseTagXML(const Source, Tag: AnsiString{; var Index: Integer}):
AnsiString;
var
 NestLevel: Integer;
 StartTag, StopTag: AnsiString;
 StartLen, StopLen, SourceLen: Integer;
 StartIndex, StopIndex: Integer;

 begin
 

SourceLen := Length(Source);
StartIndex := 0;
result := ';

 if (StartIndex < SourceLen) then
begin
StartTag := '<' + Tag + '<';
StartLen := Length(StartTag);

 if StartLen < 2 then
begin
 StopTag := '
 StopLen := Length(StopTag);
 StartIndex := Pos(StartTag,Source);
 StopIndex := Pos(StopTag,Source);
 result := Copy(Source, StartIndex+StartLen, StopIndex-StartIndex-StartLen{- 1});
end;

 end;

 end; 

Установка свойств:

procedure setPropAsString_Lang(Instance: TObject; PropInfo: PPropInfo; const value :
string);
var
ObjectProp : TObject;
Intf: IXMLWorksObject;
vTemp : variant;
StrTmp: String;
begin

 // No property
if (PropInfo = Nil) OR (value = ') or
// a read only simple type
((PropInfo^.SetProc = NIL) and not (PropInfo^.PropType^.Kind in [tkClass, tkInterface]))

 then
exit;

case PropInfo^.PropType^.Kind of

 tkString,
tkLString,
tkWString:
if AnsiSameText(PropInfo^.PropType^.Name, 'XMLString') then
 SetStrProp(Instance, PropInfo, Value)
else if AnsiSameText(PropInfo^.PropType^.Name, 'XMLMIMEString') then
 SetStrProp(Instance, PropInfo, Base64Decode(Value))
else
 SetStrProp(Instance, PropInfo, XMLToStr(Value));
 

tkSet, tkInteger:
if AnsiSameText(PropInfo^.PropType^.Name, 'XMLRGBTColor') then
 SetOrdProp(Instance, PropInfo, SwapRandB(StrToInt(XMLToStr(Value))))
else
 SetOrdProp(Instance, PropInfo, StrToInt(XMLToStr(Value)));
 

tkFloat:;

SetFloatProp(Instance, PropInfo, StrToFloat(XMLToStr(Value)));

tkVariant:
begin
 vTemp := GetVariantProp(Instance,PropInfo);
 XMLToVariant(value,vTemp);
 SetVariantProp(Instance, PropInfo, vTemp);
end;

 tkInt64: SetInt64Prop(Instance, PropInfo, StrToInt64(XMLToStr(Value)));
 

tkChar,
tkWChar:
begin
StrTmp:=XMLToStr(Value);

 if Length(StrTmp)>0 then
 SetOrdProp(Instance, PropInfo, Ord({XMLToStr(Value)}StrTmp[1]));

 end;

 tkEnumeration: SetOrdProp(Instance, PropInfo, GetEnumValue( PropInfo^.PropType^, XMLToStr(Value)));

tkClass :
begin
try

 ObjectProp := TObject(GetOrdProp(Instance, PropInfo));
 

if Assigned(ObjectProp) then
begin

 if ObjectProp.GetInterface(IXMLWorksObject, Intf) then
Intf.ElementText := Value
 else if (ObjectProp is TXMLCollection) then
 TXMLCollection(ObjectProp).ElementText := Value
 else if (ObjectProp is TXMLCollectionItem) then
 TXMLCollectionItem(ObjectProp).ElementText := Value
 else if (ObjectProp is TXMLObject) then
 TXMLObject(ObjectProp).ElementText := Value
 else if (ObjectProp is TXMLList) then
 TXMLList(ObjectProp).ElementText := Value
 else if (ObjectProp is TStrings) then
 TStrings(ObjectProp).CommaText := XMLToStr(Value)
 

end;

 except
 on e: Exception do
 raise EXMLException.Create('(' + e.Message + ')Error with property - ' + PropInfo^.Name);
end;

 end;

 tkInterface:
XMLtoInterface(Value,GetIntfProp(Instance, PropInfo));

 {
Types not supported :
tkRecord
tkArray
tkDynArray
tkMethod
tkUnknown
}
 

end;

 end;

Установка компонента:

procedure setXMLObject_Lang(Instance: TObject; p_sXML: AnsiString);
var
 CurrentTagIndex, OverAllIndex: Integer;
 CurrentTag, CurrentTagContent :string;
begin
 try
 CurrentTagIndex := 1;
 OverallIndex := 1;

 repeat
 CurrentTag := FastParseTag(p_sXML, '<' , '>', OverallIndex);
 CurrentTagContent := FastParseTagXML(p_sXML, CurrentTag, CurrentTagIndex);
 

 if (Length(CurrentTag) > 0) then
 SetPropAsString_Lang(Instance, GetPropInfo(Instance.ClassInfo, CurrentTag), CurrentTagContent);
 
 

 OverAllIndex := CurrentTagIndex;
 until (OverAllIndex<1) or (OverAllIndex > Length(p_sXML));

 except
 on EXMLException do
 raise;
 on e : Exception do
 raise EXMLException.Create('(' + e.Message + ')Error Processing XML - '
 +CurrentTag+' ('+CurrentTagContent+') '+iif_Str(Assigned(Instance),Instance.ClassName,'));
end;

 end;

Сохранение и загрузка перевода

Имеея описанные выше процедуры и функции мы без труда можем реализовать сохранение и загрузку информации.

Файлы для разных языков мы записываем в различные каталоги, поэтому реализуем функцию для выдачи пути к файлу перевода:

Function LangPath: String;
Begin
 Result:=NormalDir(NormalDir(ExtractFilePath(Application.ExeName))
 +'Langs'+User_Sets.LangInterface);
End;

В данной функции:

User_Sets.LangInterface – название текущего языка. Вместо этой переменной поставьте свою.

NormalDir – нормализует каталог. Эта функция взята из JVCL. Можно обойтись и без этой функции.

Формирование файла для перевода:

Procedure SaveLangTranslate(ObjInp: TObject{; LangInp: String});
Var TransTmp: String;
begin

 TransTmp:=GenSQLLang(ObjInp);
 

 if not DirectoryExists(LangPath)
 then ForceDirectories(LangPath);
 SaveStringToFile(TransTmp, LangPath{+Trim(LangInp)}+ObjInp.ClassName+'.xml');
End;

Загрузка перевода:

Procedure LoadLangTranslate(ObjInp: TObject{; LangInp: String});
Var TransTmp: String;
begin
 TransTmp:=LoadStringFromFile(LangPath{+Trim(LangInp)}+ObjInp.ClassName+'.xml');
 DecodeSQLLang(ObjInp,TransTmp);
end;

Перевод переменных, констант

От констант придется отказаться.

Следуем традиции и реализуем перевод с помощью xml. Для этого используем TXMLCollectionItem и TXMLCollection.

Элементы перевода (TXMLCollectionItem):

TCorp_Const_StringCollectionItem = class(TXMLCollectionItem)
private

 FIndexName: String;
FMessString: String;
 

public

 destructor Destroy; Override;
published
 property IndexName: String read FIndexName write FIndexName;
 property MessString: String read FMessString write FMessString;
end;

Коллекция элементов перевода (TXMLCollection):

TCorp_Const_StringCollection = class(TXMLCollection)

 private
 FLangInfo: String;

 public

 constructor Create;
destructor Destroy; Override;
Function AddNewItem: TCorp_Const_StringCollectionItem;
Procedure AddString(IndexNameInp, MessStringInp: String);
Procedure AddIfNotExist(IndexNameInp, MessStringInp: String);
function GetItemByIndex(index:integer): TCorp_Const_StringCollectionItem;
function GetItemByName(NameInp: String): TCorp_Const_StringCollectionItem;
function GetMessByName(NameInp: String): String;
 
 procedure Assign(Source: TPersistent); override;
published
 Property LangInfo: String read FLangInfo write FLangInfo;
End;

 …

 var Corp_Const_String: TCorp_Const_StringCollection;

 …

 constructor TCorp_Const_StringCollection.Create;
begin
 inherited Create(TCorp_Const_StringCollectionItem);
 FLangInfo:='Uk';
end;

 destructor TCorp_Const_StringCollection.Destroy;
begin
 Clear;
 inherited;
end;

 function TCorp_Const_StringCollection.AddNewItem: TCorp_Const_StringCollectionItem;
begin
 Result:=TCorp_Const_StringCollectionItem.Create(Self);
end;

 procedure TCorp_Const_StringCollection.AddString(IndexNameInp,
MessStringInp: String);
begin

 With AddNewItem Do
Begin
 IndexName:=IndexNameInp;
 MessString:=MessStringInp;
End;

 end;

 procedure TCorp_Const_StringCollection.AddIfNotExist(IndexNameInp,
MessStringInp: String);
Var ItemTmp: TCorp_Const_StringCollectionItem;
begin
ItemTmp:=GetItemByName(IndexNameInp);

 if not Assigned(ItemTmp) then
begin
 Corp_Const_String.AddString(IndexNameInp, MessStringInp);
end
else
begin

 ItemTmp.IndexName:=IndexNameInp;
 ItemTmp.MessString:=MessStringInp;

end;
 
end;

 function TCorp_Const_StringCollection.GetItemByIndex(
index: integer): TCorp_Const_StringCollectionItem;
begin
 result:=TCorp_Const_StringCollectionItem(items[index])
end;

 function TCorp_Const_StringCollection.GetItemByName(
NameInp: String): TCorp_Const_StringCollectionItem;
var i: integer;
begin

 result:=nil;

 for i:=0 to Count-1 Do
begin
if RusUpperCase(Trim(GetItemByIndex(i).IndexName))=RusUpperCase(Trim(NameInp))
then result:=GetItemByIndex(i);
 
end;

end;

 function TCorp_Const_StringCollection.GetMessByName(NameInp: String): String;
Var CorpConstTmp: TCorp_Const_StringCollectionItem;
begin

 CorpConstTmp:=GetItemByName(NameInp);

 if not Assigned(CorpConstTmp)
then Result:='{NameInp}
else Result:=CorpConstTmp.MessString;
 
end;

 procedure TCorp_Const_StringCollection.Assign(Source: TPersistent);
begin

 inherited Assign(Source); 

end;

Процедура для перевода всех ресурсов:

Procedure Gen_Corp_String;
Begin
 


if not Assigned(Corp_Const_String)
then Corp_Const_String:=TCorp_Const_StringCollection.Create;

 // Corp_Const_String.Clear;
Corp_Const_String.AddIfNotExist('1', 'Документ-источник не является счёт-фактурой');
Corp_Const_String.AddIfNotExist('2', 'По этому документу построен другой документ!');
Corp_Const_String.AddIfNotExist('3', 'Необходимо удалить вначале зависимый документ.');
Corp_Const_String.AddIfNotExist('4', 'Документа-источника нет!');
Corp_Const_String.AddIfNotExist('5', 'Зависимого документа нет!');
…

 End;

Демонстрационный проект

Исходные тексты демонстрационного проекта:
Translate.zip

Скомпилированный демонстрационный проект: Project1_sfx.zip

Рудюк С.А.

Получить ссылку на материал

Категория: Разные | Добавил: Барон (15.12.2011)
Просмотров: 1183 | Теги: приложение, перевод, delphi | Рейтинг: 0.0/0
[ Пожертвования для сайта ] [ Пожаловаться на материал ]

Если вам помог материал сайта кликните по оплаченной рекламе размещенной в центре

Поиск
Категории раздела
Delphi.NET [3]
Kylix Delphi for Linux [9]
Советы Дельферу [6]
Хитрости в Delphi [2]
Обзор Delphi [45]
Инсталлятор [11]
Пользовательский интерфейс [18]
Примеры Delphi [93]
Функции и процедуры [15]
Разные [31]
Королевство Delphi © 2010-2024
Яндекс цитирования