Autor Beitrag
~~MAINFRAIME²~~
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 16

WinXP 32/64bit
Delphi 2005
BeitragVerfasst: Di 15.07.08 17:16 
Als ich in mein Programm eine Unterstützung für mehrere Sprachen einbauen wollte und nach einigen Suchanfragen bei Google auf die Unit von AXMD gestoßen bin diese mir aber nicht so ganz zusagte habe ich mir eine eigene Unit gebastelt.


Was die Unit bietet

    -Komponenten können wie in Delphi mit ihrem Namen angesprochen werden

    -Unterstützte properties Caption, Text, Hint, Lines[0].

    -Sprachdateien sind einfache INI Dateien für jede Form eine Section und für jede Komponente ein Eintrag

    -Es können auch noch zusätzlich Strings für Dialoge eingebunden werden mit Platzhaltern (Die Datei "%0" konnte nicht gefunden werden!)
    Diese können ganz einfach mit "GetLocalStr('MyError')" abgerufen werden.
    Und mit "GetLocalStr('MyError', ['MyReplaceStr'])" können gleich die Platzhalter ersetzt werden.
    Es werden auch mehrer Zeilen unterstützt einfach mit "|" trennen.

    -Unterstützung für Variablen(vom Programm(%MyAppVar%) und in der INI(!MyINIVar!)) welche in der ganzen Übersetzung verwendet werden können.




Und eine solche INI würde dann etwa so aussehen
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
;Die INI Variablen (!MyIniVar!)
[Vars]
Translator=MySelf
Transmail=MySelf@translator.www
SomeAppVar=%MyVarName%
Local strings to use with dialogs or whatever:

;Die Strings für Dialoge
[LocalStrings]
My_Local_String1=This is my local string one.
Error_FileNotFound=Sorry but the file does not exist!|%0
Error_SomeOtherError=Some error occurred!
About_Dialog=This app was created by %AppCreator%.|And translated by !Translator! <!Transmail!>.

;Und zum Schluss die Forms
[Form1]
Form1.Caption=MyApp v0.00 by %AppCreator% translation by !Translator! <!Transmail!>
Label1.Caption=SomeLabel
Label1.Hint=LabelHint

[Form2]
Memo1.Lines[0]=Some text
Memo1.Lines[1]=
Memo1.Lines[2]=more text
Memo1.Hint=MemoHint



Eigentlich ganz einfach zu benutzen hier ein Beispiel
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
procedure TForm1.UpdateLocalization(NewLocalFile: TFileName = '');
var s: string;
begin

  s:= Settings.Directories.root + '\Data\Lang\' + NewLocalFile;

  if (Settings.Localization = nilthen
  begin
    Settings.Localization:= TMyFormLocalization.Create([Form1], s);

    //Prepare the user vars
    Settings.Localization.AddUserReplaceVars('APPNAME',         ProgName);
    Settings.Localization.AddUserReplaceVars('APPNAMELONG',     AddProgInfo);
    Settings.Localization.AddUserReplaceVars('APPVER',          Version);
    Settings.Localization.AddUserReplaceVars('APPCREATOR',      Creator);
    Settings.Localization.AddUserReplaceVars('APPLICENSE',      License);
    Settings.Localization.AddUserReplaceVars('APPLICENSEURL',   LicenseURL);

  end else begin
    Settings.Localization.SetNewForms([
                                        Form1, //MainForm
                                        Form2, //Settings
                                        Form3, //Wizard
                                        Form4, //About
                                        Form5, //Script editor
                                        Form6  //OnlineUpdate
                                       ]);

    Settings.Localization.AddUserReplaceVars('APPCREATORMAIL',  Settings.OnlineInfo.eMail);
    Settings.Localization.AddUserReplaceVars('APPURL',          Settings.OnlineInfo.HomePageURL);
    
    if NewLocalFile <> '' then
      Settings.Localization.CurLangFile:= s
    else
      Settings.Localization.RefreshLocalization;
  
  end;

end;




Und hier die Unit
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
{
FormLacalization.pas
Version: R1
by ~~MAINFRAIME²~~


This unit is used to localize your VCL application without a lot of coding
you only need one ini file for every language.

Usage:

  uses FormLacalization;
  var  LocalMan: TMyFormLocalization;
  
  procedure MyExampleLocal_Init(LocalFile: TFileName = '');
  begin
    //Create a instance of "TMyFormLocalization"
    LocalMan:= TMyFormLocalization.Create( LocalFile, [Form1] );

    //Add some user vars for the localization
    LocalMan.AddUserReplaceVars( 'AppName',        'MyApp'           );
    LocalMan.AddUserReplaceVars( 'AppCreator',     'MySelf'          );
    LocalMan.AddUserReplaceVars( 'AppUrl',         'www.myapp.com'   );
    LocalMan.AddUserReplaceVars( 'AppCreatorMail', 'myself@myapp.com');
  end;

  procedure MyExampleLocal_Upd(LocalFile: TFileName = '');
  begin
    //Set new forms
    LocalMan.SetNewForms( [Form1, Form2, Form3] );

    //Update a var
    LocalMan.AddUserReplaceVars( 'AppUrl', 'www.mynewapp.com' );

    if LocalFile <> '' then
      //Change localization
      LocalMan.CurLangFile:= LocalFile
    else
      //Reload
      LocalMan.RefreshLocalization

    //Show what language is loaded
    StatusBar1.Panels.Items[0].Text:= LocalMan.GetLocalStr('String_CurrentLanguage');
  end;

  procedure MyExampleLocal_Free;
  begin
    LocalMan.Free;
  end;


  
  Change your localization like this
  CurLangFile:= '.\Lang\English.ini';

  The local strings
  GetLocalStr('MyLocalString1');
  or use this one which replaces %0, %1, .. with the array index.
  GetLocalStr('MyMissingFile', ['FileName']);

  Add some vars through your program
  SetUserReplaceVars(MyNameValuePairStringList);
  and or
  AddUserReplaceVars('MyVarName', 'MyVarValue');
  and access them with %MyVarName%.

  Manually reload localization
  RefreshLocalization;
  not needed when "CurLangFile:= CurrentLocalization" is used.



  The INI file:

  You can add some vars to be replaced by a value
  !MyVarName!
  [Vars]
  Translator=MySelf
  Transmail=MySelf@translator.www
  SomeAppVar=%MyVarName%

  Local strings to use with dialogs or whatever:
  [LocalStrings]
  My_Local_String1=This is my local string one.
  Error_FileNotFound=Sorry but the file does not exist!|%0
  Error_SomeOtherError=Some error occurred!

  
  For every form you want to translate just create one ini section for it.
  You can use the following properties
  Caption         >
  Text            >
  Hint            > Will also set ShowHint:= (Hint <> '')
  Lines[0]        > You have to start with 0 but you can skip up to 16 empty lines by default,
                  > if this is not enough change "cMaxLinesMissing".
                  
  [NameOfYourForm]
  Label1.caption=My translated caption
  Edit1.text=My translated text
  GroupBox1.hint=My translated hint.|Next line

  Memo1.Lines[0]=1st line
  Memo1.Lines[1]=2nd line
  Memo1.Lines[2]=and so on
  Memo1.Lines[3]=...

  [AboutForm]
  Label_Title.Caption=%APPNAME%
  Label_Creator.Caption=%APPCREATOR%
  Label_Translator.Caption=!Translator! <!Transmail!>

}


unit FormLocalization;

interface

uses
  Classes, SysUtils,
  Controls, StdCtrls, Forms,
  INIFiles, TypInfo;

type

  TMyFormLocalization = class
  protected
    const
      cMaxLinesMissing  = 16;

    var
      FCanUpdate         : boolean;

      FFormArray         : Array of TCustomForm;
      FCurLangFile       : TFileName;
      FLocalStrings      : TStringList;
      FReplaceVarsINI    : TStringList;//[Vars] aVarName=aVarValue > !aVarName!
      FReplaceVarsUsr    : TStringList;//Name value pairs > %aVarName%

    procedure   Initialize;

    procedure   SetCurLangFile(aLangIniFile: TFileName);

    function    StringReplacer(s: string): string;//Replaces all vars by it's values


  public
    constructor Create(aFormArray: Array of TCustomForm; aLangIniFile: TFileName; UpdateLocal: boolean = true);
    destructor  Destroy; override;


    property    CurLangFile: TFileName read FCurLangFile write SetCurLangFile;

    procedure   SetNewForms(aFormArray: Array of TCustomForm);
    procedure   SetUserReplaceVars(aNameValuePairStringList: TStrings);
    procedure   AddUserReplaceVars(aVarName, aVarValue: string);

    //Returns a local string with replaced vars
    function    GetLocalStr(aName: string): string;                                 overload;
    //Here you can replace additional strings > %ArrayIndex > %0, %1, ..
    function    GetLocalStr(aName: string; ReplaceStr: array of string): string;    overload;

    procedure   RefreshLocalization;
  end;

implementation

//Create & destroy
constructor TMyFormLocalization.Create(aFormArray: Array of TCustomForm; aLangIniFile: TFileName; UpdateLocal: boolean = true);
var i: integer;
begin
  Initialize;

  SetLength(FFormArray, Length(aFormArray)-1);
  for i:=0 to Length(FFormArray)-1 do
    FFormArray[i]:= aFormArray[i];

  FCanUpdate:= UpdateLocal;
  SetCurLangFile(aLangIniFile);
  FCanUpdate:= true;
end;

procedure TMyFormLocalization.Initialize;
begin
  FLocalStrings   := TStringList.Create;
  FReplaceVarsINI := TStringList.Create;
  FReplaceVarsUsr := TStringList.Create;

  FLocalStrings.NameValueSeparator   := '=';
  FReplaceVarsINI.NameValueSeparator := '=';
  FReplaceVarsUsr.NameValueSeparator := '=';
end;

destructor TMyFormLocalization.Destroy;
begin
  FLocalStrings.Free;
  FReplaceVarsINI.Free;
  FReplaceVarsUsr.Free;
end;


//Set new forms
procedure TMyFormLocalization.SetNewForms(aFormArray: Array of TCustomForm);
var i: integer;
begin
  SetLength(FFormArray, Length(aFormArray)-1);
  for i:=0 to Length(FFormArray)-1 do
    FFormArray[i]:= aFormArray[i];
end;
//Set new localization
procedure TMyFormLocalization.SetCurLangFile(aLangIniFile: TFileName);
begin
  if FileExists(aLangIniFile) and (aLangIniFile <> FCurLangFile) then
  begin
    FCurLangFile:= aLangIniFile;
    if FCanUpdate then RefreshLocalization;
  end;
end;
//Set user replace vars
procedure TMyFormLocalization.SetUserReplaceVars(aNameValuePairStringList: TStrings);
begin
  FReplaceVarsUsr.Assign(aNameValuePairStringList);
end;
procedure TMyFormLocalization.AddUserReplaceVars(aVarName, aVarValue: string);
begin
  if FReplaceVarsUsr.Values[aVarName] <> '' then
    FReplaceVarsUsr.Values[aVarName]:= aVarValue
  else
    FReplaceVarsUsr.Add(aVarName + FReplaceVarsUsr.NameValueSeparator + aVarValue);

end;



//Replace strings
function TMyFormLocalization.StringReplacer(s: string): string;

  function ReplaceNameValuePairs(aInput: string; aTS: TStrings; aVarIdent: Char): string;
  var i: integer;
  begin
    result:= aInput;
    for i:=0 to aTS.Count-1 do
      result:= StringReplace(result,
                             aVarIdent + aTS.Names[i] + aVarIdent,
                             aTS.ValueFromIndex[i],
                             [rfIgnoreCase, rfReplaceAll]);
  end;

begin
  result:= s;

  //Replace vars
  result:= ReplaceNameValuePairs(result, FReplaceVarsINI, '!');
  result:= ReplaceNameValuePairs(result, FReplaceVarsUsr, '%');

  //New lines
  result:= StringReplace(result, '|'#13, [rfIgnoreCase, rfReplaceAll]);
end;




//Local strings
function  TMyFormLocalization.GetLocalStr(aName: string): string;
begin
  result:= FLocalStrings.Values[aName];

  if FLocalStrings.IndexOf(aName + FLocalStrings.NameValueSeparator + result) <> -1 then
    result:= StringReplacer(result)
  else
    result:= aName;
end;
function  TMyFormLocalization.GetLocalStr(aName: string; ReplaceStr: array of string): string;
var i: integer;
begin
  result:= GetLocalStr(aName);
  for i:=0 to Length(ReplaceStr)-1 do
    result:= StringReplace(result, '%'+ IntToStr(i), ReplaceStr[i], [rfIgnoreCase, rfReplaceAll]);

  result:= StringReplacer(result);
end;



//Load the localization
procedure TMyFormLocalization.RefreshLocalization;

  procedure SetStringProperty(aCmp: TComponent; propName, propValue: string);
  var p: PPropInfo;
  begin
    p:= GetPropInfo(aCmp.ClassInfo, propName);
    if (p <> niland (p^.SetProc <> nilthen
      SetStrProp(aCmp, propName, propValue)
  end;

  procedure SplitNameProp(const s: stringvar aName, aProp: string);
  var i: integer; b: boolean;
  begin
    b:= false; aName:= ''; aProp:= '';
    for i:=1 to Length(s) do
      if not b then
      begin
        if s[i] = '.' then
          b:= true
        else
          aName:= aName + s[i];
          
      end else
        aProp:= aProp + s[i];

  end;
  
var INI: TMemINIFile; i, j, ii, i3: integer;
    cmp_name, prop, old_cmp_name: string; cmp: TComponent;
    TS: TStringList;
    replaced_str: string;
begin
  if FileExists(FCurLangFile) then
  begin
    INI:= TMemINIFile.Create(FCurLangFile);
    TS:=  TStringList.Create;

    FReplaceVarsINI.Clear;
    INI.ReadSectionValues('Vars', FReplaceVarsINI);
    FLocalStrings.Clear;
    INI.ReadSectionValues('LocalStrings', FLocalStrings);

    for i:=0 to Length(FFormArray)-1 do
      if (FFormArray[i] <> niland INI.SectionExists(FFormArray[i].Name) then
      begin
        TS.Clear; INI.ReadSection(FFormArray[i].Name, TS); TS.Sort;
        for j:=0 to TS.Count-1 do
        begin
          SplitNameProp(TS.Strings[j], cmp_name, prop);

          //Get the component
          if LowerCase(cmp_name) = Lowercase(FFormArray[i].Name) then
            cmp:= FFormArray[i]
          else if (cmp_name <> old_cmp_name) then
            cmp:= FFormArray[i].FindComponent(cmp_name);

          //Set a new value to the property
          if (cmp <> nilthen
          begin
            replaced_str:= StringReplacer( INI.ReadString(FFormArray[i].Name, TS.Strings[j], '') );

            if (LowerCase(prop) = 'caption')
            or (LowerCase(prop) = 'text')
            then
              SetStringProperty(cmp, prop, replaced_str)

            else if LowerCase(prop) = 'hint' then
            begin
              SetStringProperty(cmp, prop, replaced_str);
              (cmp as TControl).ShowHint:= (prop <> '');
            end else if LowerCase(prop) = 'lines[0]' then
            begin
              ii:= 0; i3:= 0;
              (cmp as TCustomMemo).Clear;
              while i3 < cMaxLinesMissing do
              begin
                if INI.ValueExists(FFormArray[i].Name, cmp_name + '.lines[' + IntToStr(ii) + ']'then
                begin
                  (cmp as TCustomMemo).Lines.Add( StringReplacer(
                  INI.ReadString(FFormArray[i].Name, cmp_name + '.lines[' + IntToStr(ii) + ']','')
                  ) );
                  i3:= 0;//reset empty line count
                end else begin
                  (cmp as TCustomMemo).Lines.Add('');
                  Inc(i3);
                end;

                Inc(ii);

              end;//while

            end;

          end;//cmp <> nil

          old_cmp_name:= cmp_name;

        end;//for TS

      end;//form <> nil

    INI.Free; TS.Free;
  end;//if FileExists(FCurLangFile) then
end;



END.



Wer diese unit verwenden möchte kann dies gerne tun und wer noch
Verbesserungsvorschläge, Fehler, Fragen oder Kommentare hat einfach posten.