Entwickler-Ecke

Open Source Units - Mehrsprachenunterstützung(multi-language support) für VCL


~~MAINFRAIME²~~ - Di 15.07.08 17:16
Titel: Mehrsprachenunterstützung(multi-language support) für VCL
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 [http://www.delphi-forum.de/viewtopic.php?t=38632] gestoßen bin diese mir aber nicht so ganz zusagte habe ich mir eine eigene Unit gebastelt.


Was die Unit bietet




Und eine solche INI würde dann etwa so aussehen

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

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

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.