[erledigt] i18n
-
- Beiträge: 768
- Registriert: Mo 4. Mai 2009, 13:24
- OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
- CPU-Target: x86_64-linux-qt/gtk2
- Kontaktdaten:
[erledigt] i18n
Per DefaultTranslator kann man recht einfach eine Übersetzung einbauen. Ich habe unter languages/(de|en|fr|it|es) die po-Files abgelegt, fände es aber schöner, wenn diese Dateien als Ressource eingebettet wären. Gibt es dafür eine fertige Routine?
Zuletzt geändert von Scotty am Do 26. Aug 2010, 19:12, insgesamt 1-mal geändert.
-
- Beiträge: 768
- Registriert: Mo 4. Mai 2009, 13:24
- OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
- CPU-Target: x86_64-linux-qt/gtk2
- Kontaktdaten:
Re: i18n
Gleich noch eine zweite Frage hinterher: Wenn ich den DefaultTranslator benutze, wie kann ich die Sprache im Betrieb wechseln? Einfach per LRSTranslator:=TPoTranslator.Create('/Pfad/zu/der/po/Datei'); überschreiben und potentielle Speicherlecks ignorieren?
-
- Lazarusforum e. V.
- Beiträge: 7192
- Registriert: So 19. Nov 2006, 12:06
- OS, Lazarus, FPC: Linux Mint 19.3
- CPU-Target: AMD
- Wohnort: Oldenburg(Oldenburg)
Re: i18n
ich würde vorher Prüfen ob "LRSTranslator" zugewiesen ist wenn ja Frei geben....
Aber die meisten Klassen haben eine Entsprechende LoadFrom Methode. Schau dir mal in Create bei TPoTranslator an, was da genau passiert.
Aber die meisten Klassen haben eine Entsprechende LoadFrom Methode. Schau dir mal in Create bei TPoTranslator an, was da genau passiert.
MFG
Michael Springwald
Michael Springwald
-
- Beiträge: 768
- Registriert: Mo 4. Mai 2009, 13:24
- OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
- CPU-Target: x86_64-linux-qt/gtk2
- Kontaktdaten:
Re: i18n
Ich habe mich gegen po-Dateien entschieden, weil mir da Flexibilität fehlt, ich während der Laufzeit die Sprache nicht ändern kann und die po-Dateien etwas unübersichtlicher sind als bei meiner Lösung.
Meine Lösung benutzt ini-Dateien, die sind IMHO übersichtlich und einfach. Wählt man eine <CurrentLanguge>, die noch nicht definiert ist, wird diese Datei mit den gerade aktuellen Einstellungen aufgefüllt. Die IDs sind im Gegensatz zu po nicht die Texte selbst sondern die Variablenbezeichner. Ich achte beim Programmieren auf ordentliche Namen inklusive Variablentyp, z.B. miFile für TMenuItem 'File' oder tbSave für Toolbutton 'Save'. Alle zur Laufzeit benutzen Strings müssen unter resourcestring angegeben werden. Zum Beispiel für Form1.OnShow(): Showmessage(rHello); rHello='Hello World'; Natürlich muss ulanguage dann in dieser unit eingebunden werden.
Meine Lösung benutzt ini-Dateien, die sind IMHO übersichtlich und einfach. Wählt man eine <CurrentLanguge>, die noch nicht definiert ist, wird diese Datei mit den gerade aktuellen Einstellungen aufgefüllt. Die IDs sind im Gegensatz zu po nicht die Texte selbst sondern die Variablenbezeichner. Ich achte beim Programmieren auf ordentliche Namen inklusive Variablentyp, z.B. miFile für TMenuItem 'File' oder tbSave für Toolbutton 'Save'. Alle zur Laufzeit benutzen Strings müssen unter resourcestring angegeben werden. Zum Beispiel für Form1.OnShow(): Showmessage(rHello); rHello='Hello World'; Natürlich muss ulanguage dann in dieser unit eingebunden werden.
Code: Alles auswählen
unit ulanguage;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Inifiles, ExtCtrls, Typinfo, CheckLst, Grids, Buttons;
type
TLanguage=class
private
FCurrentLanguage : string;
FAuthor, FDate : string;
FVersion : integer;
function GetInfo(index: byte): string;
procedure SetLanguage(const aValue: string);
function HasProperty(comp:TComponent;prop:string):boolean;
function GetProp(Comp:TComponent;Prop:string):string;
procedure SetProp(comp:TComponent;const prop,value:string);
public
constructor Create;
destructor Destroy;override;
property CurrentLanguage:string read FCurrentLanguage write SetLanguage;
property Info[index:byte]:string read GetInfo;
end;
var Language : TLanguage;
resourcestring
//alle resourcestrings kommen hier hin
implementation
constructor TLanguage.Create;
begin
inherited Create;
FCurrentLanguage:='';
end;
destructor TLanguage.Destroy;
begin
inherited Destroy;
end;
function TLanguage.HasProperty(comp: TComponent; prop: string): boolean;
begin
Result:=(GetPropInfo(Comp.classInfo,Prop)<>nil) and (Comp.Name<>'');
end;
function TLanguage.GetProp(Comp: TComponent; Prop: string): string;
var pi:PPropInfo;
begin
pi:=GetPropInfo(Comp.ClassInfo,Prop);
if pi<>nil then Result:=GetStrProp(Comp,pi)
else Result:='';
end;
procedure TLanguage.SetProp(comp: TComponent; const prop, value: string);
var pi:PPropInfo;
begin
if Value<>'' then
begin
pi:=GetPropInfo(Comp.ClassInfo,Prop);
if pi<>nil then SetStrProp(Comp,pi,Value);
end;
end;
function TranslateResource(Name,Value : AnsiString; Hash : Longint; arg:pointer) : String;
var s:string;
begin
s:=copy(Name,11,length(Name)); //remove ulanguage. from ulanguage.<resourcestring>
Result:=TInifile(arg).ReadString('Translation',s,'');
if Result='' then
begin
TInifile(arg).WriteString('Translation',s,Value);
Result:=Value;
end;
end;
procedure TLanguage.SetLanguage(const aValue: string);
var i,j,k : integer;
ParentComp,Comp : TComponent;
s : string;
IniFile : TIniFile;
begin
if (aValue<>FCurrentLanguage) and (aValue<>'') and (aValue<>' ') then
begin
Inifile:=TIniFile.Create(ExtractFilePath(Application.ExeName)+aValue);
try
FCurrentLanguage:=aValue;
FAuthor:=Inifile.ReadString('Header','Author','');
FVersion:=Inifile.ReadInteger('Header','Version',0);
FDate:=Inifile.ReadString('Header','Date','');
for i:=0 to Application.ComponentCount-1 do
begin
ParentComp:=Application.Components[i];
if HasProperty(ParentComp,'Caption') then
begin
s:=Inifile.ReadString('Translation',ParentComp.Name+'.Caption','');
if s='' then
begin
s:=GetProp(ParentComp,'Caption');
Inifile.WriteString('Translation',ParentComp.Name+'.Caption',s);
end;
SetProp(ParentComp,'Caption',s);
end;
for j:=0 to ParentComp.ComponentCount-1 do
begin
Comp:=ParentComp.Components[j];
if HasProperty(Comp,'Caption') and
not (getProp(Comp,'Caption')='-') and
not (getProp(Comp,'Caption')='') then
begin
s:=Inifile.ReadString('Translation',Comp.Name+'.Caption', '');
if s='' then
begin
s:=GetProp(Comp,'Caption');
Inifile.WriteString('Translation',Comp.Name+'.Caption', s);
end;
SetProp(Comp,'Caption',s);
end;
if HasProperty(Comp,'Hint') and (GetProp(Comp,'Hint')<>'') then
begin
s:=Inifile.ReadString('Translation',Comp.Name+'.Hint','');
if s='' then
begin
s:=GetProp(Comp,'Hint');
Inifile.WriteString('Translation',Comp.Name+'.Hint',s);
end;
SetProp(Comp,'Hint',s);
end;
if (Comp is TRadioGroup) then
for k:=0 to TRadioGroup(Comp).Items.Count-1 do
begin
s:=Inifile.ReadString('Translation',Comp.Name+'.Items['+IntToStr(k)+']','');
if s='' then
begin
s:=TRadioGroup(Comp).Items[k];
Inifile.WriteString('Translation',Comp.Name+'.Items['+IntToStr(k)+']',s);
end;
(Comp as TRadioGroup).Items[k]:=s;
end;
if (Comp is TCheckListBox) then
for k:=0 to TCheckListBox(Comp).Items.Count-1 do
begin
s:=Inifile.ReadString('Translation',Comp.Name+'.Items['+IntToStr(k)+']','');
if s='' then
begin
s:=TCheckListBox(Comp).Items[k];
Inifile.WriteString('Translation',Comp.Name+'.Items['+IntToStr(k)+']',s);
end;
(Comp as TCheckListBox).Items[k]:=s;
end;
if (Comp is TStringGrid) then
for k:=0 to TStringGrid(Comp).Columns.Count-1 do
begin
s:=Inifile.ReadString('Translation',Comp.Name+'.Column['+IntToStr(k)+']','');
if s='' then
begin
s:=TStringGrid(Comp).Columns[k].Title.Caption;
Inifile.WriteString('Translation',Comp.Name+'.Column['+IntToStr(k)+']',s);
end;
(Comp as TStringGrid).Columns[k].Title.Caption:=s;
end;
end; //for j
end;//for i
//translate resourcestrings
SetUnitResourceStrings('ulanguage',@TranslateResource,IniFile);
finally
IniFile.Free;
end;
end;
end;
function TLanguage.GetInfo(index: byte): string;
begin
case index of
0 : Result:=FAuthor;
1 : Result:=FVersion;
2 : Result:=FDate;
else Result:='';
end;
end;
initialization
Language:=TLanguage.Create;
finalization
Language.Free;
end.
- corpsman
- Lazarusforum e. V.
- Beiträge: 1641
- Registriert: Sa 28. Feb 2009, 08:54
- OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
- CPU-Target: 64Bit
- Wohnort: Stuttgart
- Kontaktdaten:
Re: [erledigt] i18n
hi scotty,
Ich habe mich auch gerade an den po Files versucht, und festgestellt, dass es irgendwie nicht so das ist, was ich will.
Dein Ansatz ist Einfach, wie genial, und gefällt mir sehr ( Mal abgesehen von einigen kleinen Bugs [ unterstützung von Mehrzeielstrings, ungültiger Typ von Version ], welche ich geschwind korrigiert habe ).
Wie siehts denn Lizenztechnisch aus, darf ich die Unit in einer Kommerziellen Anwendung verwenden ? Oder darf man das immer mit Source der hier veröffentlicht wird ?
Eine Frage bleibt auch noch offen, wie kriegen wir die Standard sprache des Systemes raus, ? der Po Ansatz kann das ja anscheinend..
Hier noch die reparierte Version
Ich habe mich auch gerade an den po Files versucht, und festgestellt, dass es irgendwie nicht so das ist, was ich will.
Dein Ansatz ist Einfach, wie genial, und gefällt mir sehr ( Mal abgesehen von einigen kleinen Bugs [ unterstützung von Mehrzeielstrings, ungültiger Typ von Version ], welche ich geschwind korrigiert habe ).
Wie siehts denn Lizenztechnisch aus, darf ich die Unit in einer Kommerziellen Anwendung verwenden ? Oder darf man das immer mit Source der hier veröffentlicht wird ?
Eine Frage bleibt auch noch offen, wie kriegen wir die Standard sprache des Systemes raus, ? der Po Ansatz kann das ja anscheinend..
Hier noch die reparierte Version
Code: Alles auswählen
Unit ulanguage;
{$MODE objfpc}{$H+}
Interface
Uses
Classes, SysUtils, Forms, Inifiles, ExtCtrls, Typinfo, CheckLst, Grids, Buttons;
Type
TLanguage = Class
private
FCurrentLanguage: String;
FAuthor, FDate: String;
FVersion: integer;
Function GetInfo(index: byte): String;
Procedure SetLanguage(Const aValue: String);
Function HasProperty(comp: TComponent; prop: String): boolean;
Function GetProp(Comp: TComponent; Prop: String): String;
Procedure SetProp(comp: TComponent; Const prop, value: String);
public
Constructor Create;
Destructor Destroy; override;
Property CurrentLanguage: String read FCurrentLanguage write SetLanguage;
Property Info[index: byte]: String read GetInfo;
End;
Var
Language: TLanguage;
Resourcestring
//alle resourcestrings kommen hier hin
Implementation
(*
* Decodes a single line string to a multiline string
*)
Function decode(value: String): String;
Var
i: integer;
Begin
i := 1;
While i <= length(value) Do Begin
If value[i] = '~' Then Begin
Case value[i + 1] Of
'~': Begin // Es passiert nicht weil der Doppelt index einfach gelöscht wird
End;
'3': value[i] := #13;
'0': value[i] := #10;
End;
delete(value, i + 1, 1);
End;
inc(i);
End;
result := value;
End;
(*
* Encodes a multiline string to a single line string
*)
Function encode(value: String): String;
Var
i: integer;
Begin
i := 1;
While i <= length(value) Do Begin
// Das Steuerzeichentoken wird verdoppelt
If value[i] = '~' Then Begin
value := copy(value, 1, i) + '~' + copy(value, i + 1, length(value));
inc(i);
End;
// CR in ~3
If value[i] = #13 Then Begin
value := copy(value, 1, i - 1) + '~3' + copy(value, i + 1, length(value));
End;
// RT in ~0
If value[i] = #10 Then Begin
value := copy(value, 1, i - 1) + '~0' + copy(value, i + 1, length(value));
End;
inc(i);
End;
result := value;
End;
Constructor TLanguage.Create;
Begin
Inherited Create;
FCurrentLanguage := '';
End;
Destructor TLanguage.Destroy;
Begin
Inherited Destroy;
End;
Function TLanguage.HasProperty(comp: TComponent; prop: String): boolean;
Begin
Result := (GetPropInfo(Comp.classInfo, Prop) <> Nil) And (Comp.Name <> '');
End;
Function TLanguage.GetProp(Comp: TComponent; Prop: String): String;
Var
pi: PPropInfo;
Begin
pi := GetPropInfo(Comp.ClassInfo, Prop);
If pi <> Nil Then
Result := GetStrProp(Comp, pi)
Else
Result := '';
End;
Procedure TLanguage.SetProp(comp: TComponent; Const prop, value: String);
Var
pi: PPropInfo;
Begin
If Value <> '' Then Begin
pi := GetPropInfo(Comp.ClassInfo, Prop);
If pi <> Nil Then SetStrProp(Comp, pi, Value);
End;
End;
Function TranslateResource(Name, Value: AnsiString; Hash: Longint; arg: pointer): String;
Var
s: String;
Begin
s := copy(Name, 11, length(Name)); //remove ulanguage. from ulanguage.<resourcestring>
Result := decode(TInifile(arg).ReadString('Translation', s, ''));
If Result = '' Then Begin
TInifile(arg).WriteString('Translation', s, encode(Value));
Result := Value;
End;
End;
Procedure TLanguage.SetLanguage(Const aValue: String);
Var
i, j, k: integer;
ParentComp, Comp: TComponent;
s: String;
IniFile: TIniFile;
Begin
If (aValue <> FCurrentLanguage) And (aValue <> '') And (aValue <> ' ') Then Begin
Inifile := TIniFile.Create(ExtractFilePath(Application.ExeName) + aValue);
Try
FCurrentLanguage := aValue;
FAuthor := decode(Inifile.ReadString('Header', 'Author', ''));
FVersion := Inifile.ReadInteger('Header', 'Version', 0);
FDate := decode(Inifile.ReadString('Header', 'Date', ''));
For i := 0 To Application.ComponentCount - 1 Do Begin
ParentComp := Application.Components[i];
If HasProperty(ParentComp, 'Caption') Then Begin
s := decode(Inifile.ReadString('Translation', ParentComp.Name + '.Caption', ''));
If s = '' Then Begin
s := GetProp(ParentComp, 'Caption');
Inifile.WriteString('Translation', ParentComp.Name + '.Caption', encode(s));
End;
SetProp(ParentComp, 'Caption', s);
End;
For j := 0 To ParentComp.ComponentCount - 1 Do Begin
Comp := ParentComp.Components[j];
If HasProperty(Comp, 'Caption') And
Not (getProp(Comp, 'Caption') = '-') And
Not (getProp(Comp, 'Caption') = '') Then Begin
s := decode(Inifile.ReadString('Translation', Comp.Name + '.Caption', ''));
If s = '' Then Begin
s := GetProp(Comp, 'Caption');
Inifile.WriteString('Translation', Comp.Name + '.Caption', encode(s));
End;
SetProp(Comp, 'Caption', s);
End;
If HasProperty(Comp, 'Hint') And (GetProp(Comp, 'Hint') <> '') Then Begin
s := decode(Inifile.ReadString('Translation', Comp.Name + '.Hint', ''));
If s = '' Then Begin
s := GetProp(Comp, 'Hint');
Inifile.WriteString('Translation', Comp.Name + '.Hint', encode(s));
End;
SetProp(Comp, 'Hint', s);
End;
If (Comp Is TRadioGroup) Then
For k := 0 To TRadioGroup(Comp).Items.Count - 1 Do Begin
s := decode(Inifile.ReadString('Translation', Comp.Name + '.Items[' + IntToStr(k) + ']', ''));
If s = '' Then Begin
s := TRadioGroup(Comp).Items[k];
Inifile.WriteString('Translation', Comp.Name + '.Items[' + IntToStr(k) + ']', encode(s));
End;
(Comp As TRadioGroup).Items[k] := s;
End;
If (Comp Is TCheckListBox) Then
For k := 0 To TCheckListBox(Comp).Items.Count - 1 Do Begin
s := decode(Inifile.ReadString('Translation', Comp.Name + '.Items[' + IntToStr(k) + ']', ''));
If s = '' Then Begin
s := TCheckListBox(Comp).Items[k];
Inifile.WriteString('Translation', Comp.Name + '.Items[' + IntToStr(k) + ']', encode(s));
End;
(Comp As TCheckListBox).Items[k] := s;
End;
If (Comp Is TStringGrid) Then
For k := 0 To TStringGrid(Comp).Columns.Count - 1 Do Begin
s := decode(Inifile.ReadString('Translation', Comp.Name + '.Column[' + IntToStr(k) + ']', ''));
If s = '' Then Begin
s := TStringGrid(Comp).Columns[k].Title.Caption;
Inifile.WriteString('Translation', Comp.Name + '.Column[' + IntToStr(k) + ']', encode(s));
End;
(Comp As TStringGrid).Columns[k].Title.Caption := s;
End;
End; //for j
End; //for i
//translate resourcestrings
SetUnitResourceStrings('ulanguage', @TranslateResource, IniFile);
Finally
IniFile.Free;
End;
End;
End;
Function TLanguage.GetInfo(index: byte): String;
Begin
Case index Of
0: Result := FAuthor;
1: Result := inttostr(FVersion);
2: Result := FDate;
Else
Result := '';
End;
End;
Initialization
Language := TLanguage.Create;
Finalization
Language.Free;
End.
--
Just try it
Just try it
-
- Beiträge: 768
- Registriert: Mo 4. Mai 2009, 13:24
- OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
- CPU-Target: x86_64-linux-qt/gtk2
- Kontaktdaten:
Re: [erledigt] i18n
Danke. Mehrzeilig geht per \\n (der aktuelle Code liegt auf Sourceforge oder Lazforge).corpsman hat geschrieben:Dein Ansatz ist einfach, wie genial, und gefällt mir sehr ( Mal abgesehen von einigen kleinen Bugs [ unterstützung von Mehrzeielstrings, ungültiger Typ von Version ], welche ich geschwind korrigiert habe ).
Code: Alles auswählen
Result:=StringReplace(TInifile(arg).ReadString('Translation',s,''),'\\n', LineBreak, [rfIgnoreCase, rfReplaceAll]);
Einfach ist meine Variante aber hoffentlich schon

PS: Vielleicht nimmst du besser UTF8Copy.
- corpsman
- Lazarusforum e. V.
- Beiträge: 1641
- Registriert: Sa 28. Feb 2009, 08:54
- OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
- CPU-Target: 64Bit
- Wohnort: Stuttgart
- Kontaktdaten:
Re: [erledigt] i18n
*g*
ja habe deine Version mittlerweile auch schon ganz schön aufgebort.
- Auslagern der Ressourcenstrings in Include files, zur Unterstützung meiner Library Philosophie
Einfügen einiger Konstanten und ein Versuch das ganze zu kommentieren ...
Einen Editor muss ich sowieso schreiben, wenn ich ihn fertig hab, kann ich das Ding eigentlich auch hier posten ...
Die Option des "Finden von veralteten Einträgen" ist eigentlich auch keine Dumme Idee. In Klab hatte ich auch so was eingebaut. Mal schaun was sich da machen läst.
ja habe deine Version mittlerweile auch schon ganz schön aufgebort.
- Auslagern der Ressourcenstrings in Include files, zur Unterstützung meiner Library Philosophie
Code: Alles auswählen
uses lclproc;
Function Get_System_Default_Language(): String;
Var
lang, def: String;
Begin
lang := '';
def := '';
LCLGetLanguageIDs(lang, def);
result := lang;
End;
Einen Editor muss ich sowieso schreiben, wenn ich ihn fertig hab, kann ich das Ding eigentlich auch hier posten ...
Die Option des "Finden von veralteten Einträgen" ist eigentlich auch keine Dumme Idee. In Klab hatte ich auch so was eingebaut. Mal schaun was sich da machen läst.
--
Just try it
Just try it
- corpsman
- Lazarusforum e. V.
- Beiträge: 1641
- Registriert: Sa 28. Feb 2009, 08:54
- OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
- CPU-Target: 64Bit
- Wohnort: Stuttgart
- Kontaktdaten:
Re: [erledigt] i18n
So meine Anpassungen sind fertig, und hier verfügbar.
Neben dem Editor, ist auch das automatische löschen von nicht mehr benötigten einträgen enthalten.
Neben dem Editor, ist auch das automatische löschen von nicht mehr benötigten einträgen enthalten.
--
Just try it
Just try it