Ïåðåâîä â Delphi-ïðèëîæåíèÿõ

Èñòî÷íèê: delphiplus

Ââåäåíèå

Ðåàëèçîâàòü ïåðåâîä â ïðèëîæåíèÿõ 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)+'<' + PPropInfo(Props.Items[i]).Name + '>');
        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]
  MP @@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;

Ñòðàíèöà ñàéòà http://test.interface.ru
Îðèãèíàë íàõîäèòñÿ ïî àäðåñó http://test.interface.ru/home.asp?artId=20842