Themenvorschläge WissensDB
-
- Lazarusforum e. V.
- Beiträge: 2809
- Registriert: Sa 9. Sep 2006, 18:05
- OS, Lazarus, FPC: Linux (L trunk FPC trunk)
- CPU-Target: 64Bit
- Wohnort: Dresden
- Kontaktdaten:
Themenvorschläge WissensDB
Für alle, die einen Artikel beisteuern wollen ... diesen bitte in diesem Thema verfassen, ich verschiebe dann den fertigen Artikel in die entsprechende Kategorie. Ansonsten ist natürlich jeder Artikel gern gesehen.
Artikel können natürlich weiterhin wie in den anderen Foren auch Kommentiert werden.
-
- Lazarusforum e. V.
- Beiträge: 7180
- Registriert: So 19. Nov 2006, 12:06
- OS, Lazarus, FPC: Linux Mint 19.3
- CPU-Target: AMD
- Wohnort: Oldenburg(Oldenburg)
Re: Themenvorschläge WissensDB
Code: Alles auswählen
function GetFilterIndex(const aIndex:Integer; aFilter:String):String;
var
x,z,x1,z2:Integer;
str:String;
begin
z:=0; z2:=0;
for x:=1 to Length(aFilter) do begin
if aFilter[x] = '|' then begin
inc(z);
if (odd(z) and (z >= 1)) then begin
x1:=posex('|',aFilter,x+1);
str:=copy(aFilter,x+1,abs(x1-x-1));
x1:=Pos(';',str);
inc(z2);
if x1 > 0 then str:=copy(str,1,x1-1);
end;
if z2 >= aIndex+1 then break;
end;
end; // for x
if str = AllFilesMask then
result:=''
else begin
x1:=Pos('*',str);
if x1 > 0 then
result:=copy(str,2,Length(str))
else
result:=str;
end;
end; // GetFilterIndex
Der Aufruf erfolgt hier so:
Code: Alles auswählen
var
ext:String;
begin
if SaveDialog1.Execute then begin
ext:=GetFilterIndex(SaveDialog1.FilterIndex-1,SaveDialog1.Filter);
if pos('.',SaveDialog1.FileName) = 0 then
ShowMessage(SaveDialog1.FileName+ext)
else
ShowMessage(SaveDialog1.FileName)
end;
So wird die Ausgewählte Endung, in einem SaveDialog Automatisch zum Dateinamen hinzugefügt. Wenn diese Vergessen wird. Wegen POSEXT muss die Unit "strutils" eingebunden werden.
Ich habe keine Fertige Möglichkeit gefunden. Daher habe ich mir diese Funktion erstellt. Verbesserungs Vorschläge lese ich mir gerne durch, Produktive Kritik ebenfalls.
Monta: Jetzt weißt du warum ich eine Code-Lib-Forum haben wollte *G*. Ich hätte noch einige Beispiele, die hier gut rein Passen würde.
-
- 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: Themenvorschläge WissensDB
Um so flexibel wie möglich zu sein, habe ich eine Klasse von THintWindow abgeleitet, die ein Bitmap enthält. Kommt eine Paint-Botschaft, wird dieses Bitmap auf den Canvas des Hints gezeichnet. Alles andere bleibt inherited.
Man kann sicher einiges eleganter lösen, ich bin für Vorschläge offen. Andererseits habe ich keine Lösung ergoogeln können und vielleicht hilft es ja jemandem.
Code: Alles auswählen
unit uspecialhint;
{$mode objfpc}
interface
uses
Classes, SysUtils, Forms, Graphics, Messages, LMessages;
type
{ TSpecialHintWindow }
TSpecialHintWindow = class(THintWindow)
private
FBitmap: TBitmap;
protected
procedure LMPaint(var msg: TMessage); message LM_PAINT;
public
constructor Create(AOwner: TComponent);
destructor Destroy;
procedure ShowWindow;
property Bitmap:TBitmap read FBitmap write FBitmap;
end;
implementation
{ TSpecialHintWindow }
procedure TSpecialHintWindow.LMPaint(var msg: TMessage);
begin
Canvas.Draw(0,0,FBitmap);
end;
constructor TSpecialHintWindow.Create(AOwner: TComponent);
begin
inherited Create(aOwner);
AutoHide:=true;
FBitmap:=TBitmap.Create;
with FBitmap do
begin
Width:=300; //max size, shrink before paint
Height:=300;
Canvas.Brush.Color:=clInfoBk;
Canvas.Brush.Style:=bsSolid;
Canvas.Pen.Color:=clInfoText;
Canvas.FillRect(0,0,Width,Height);
end;
end;
destructor TSpecialHintWindow.Destroy;
begin
FBitmap.Free;
inherited;
end;
procedure TSpecialHintWindow.ShowWindow;
begin
ActivateHint(Bounds(Left,Top,Width,Height),'');
end;
end.
{--------------- }
procedure TfmMain.Form1ShowHint(Sender: TObject; HintInfo: PHintInfo);
var
png : TPortableNetworkGraphic;
h : TSpecialHintWindow;
begin
h:=TSpecialHintWindow.Create(self);
h.Bitmap.Canvas.TextOut(5,5,'Hello World');
png:=TPortableNetworkGraphic.Create;
try
png.LoadFromLazarusResource('flag_de');
h.Bitmap.Canvas.Draw(5,25,png);
finally
png.Free;
end;
p:=Mouse.CursorPos;
h.SetBounds(p.x,p.y,100,100);
h.ShowWindow;
end;
-
- Lazarusforum e. V.
- Beiträge: 7180
- Registriert: So 19. Nov 2006, 12:06
- OS, Lazarus, FPC: Linux Mint 19.3
- CPU-Target: AMD
- Wohnort: Oldenburg(Oldenburg)
Re: Themenvorschläge WissensDB
Dient nur als Beispiel. Kann für andere Aufgaben relativ einfach angepasst werden:
Code: Alles auswählen
function myFormat(const Fmt : string; const args : array of const):string;
var
x,c,p1,p2,i:Integer;
ch:Char;
str1, str2, str3:String;
begin
c:=Length(args)-1; p1:=Pos('%',fmt); x:=p1+1; i:=0;
str1:=Copy(Fmt, 1, p1-1); str2:=str1;
while i <= c do begin
ch:=Fmt[x];
case ch of
'V': begin
str2:=str2+String(args[i].VAnsiString);
inc(i);
end;
'N': begin
str2:=str2+String(args[i].VAnsiString);
inc(i);
end;
'A': begin
str2:=str2+IntTostr(args[i].VInteger);
inc(i);
end;
end; // case
// inc(i);
p2:=PosEx('%',FMT, x)+1;
str1:=Copy(Fmt, x+1, abs(p2-x)-2);
x:=p2;
str2:=str2+str1;
end; // while x
result:=str2;
end; // myFormat
{ TForm1 }
// Anwendung
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
writeln(myFormat('Mein Vornamename ist %V, Mein Nachname ist: %N, Ich bin %A Jahre alt ',[Edit2.Text, Edit3.Text, SpinEdit1.Value]));
end;
-
- Lazarusforum e. V.
- Beiträge: 7180
- Registriert: So 19. Nov 2006, 12:06
- OS, Lazarus, FPC: Linux Mint 19.3
- CPU-Target: AMD
- Wohnort: Oldenburg(Oldenburg)
Re: Themenvorschläge WissensDB
Code:
Code: Alles auswählen
var
html:TIpHtml;
ms:TMemoryStream;
begin
ms:=TMemoryStream.Create;
SynMemo1.Lines.SaveToStream(ms);
ms.Position:=0; // Von entscheidender Bedeutung, sonst geht es nicht(Vielleicht ein BUG in der LCL bzw. von TStream bzw. von TStringList.
html:=TIpHtml.Create;
html.LoadFromStream(ms);
IpHtmlPanel1.SetHtml(html);
ms.Free;
Ich denke, der Code kann leicht angepasst werden oder daraus kann leicht eine allgemeine Funktion erstellt werden. Vielleicht sogar als extra Erweiterung für das HTML Panel.
Edit01: Zwei Hinweise hinzugefügt.
-
- Lazarusforum e. V.
- Beiträge: 7180
- Registriert: So 19. Nov 2006, 12:06
- OS, Lazarus, FPC: Linux Mint 19.3
- CPU-Target: AMD
- Wohnort: Oldenburg(Oldenburg)
Re: Themenvorschläge WissensDB
Ich denke auch, die Tipps sollten einer Gewissen Qualität haben, jedoch sollten sie nicht Copy & Past Fertig sein. Jeder sollte sie ohne Problem anpassen können und sich vorher Gedanken machen ob der Code wirklich hilft oder nicht.
-
- Beiträge: 200
- Registriert: So 11. Jul 2010, 18:39
- OS, Lazarus, FPC: Linux
- CPU-Target: 64 Bit
- Wohnort: Wien
- Kontaktdaten:
Re: Themenvorschläge WissensDB
Code: Alles auswählen
procedure MassEnable( MenuItem: TMenuItem; Enabled: Boolean);
var
I: Integer;
begin
if MenuItem.Count <> 0
then begin
MenuItem.Enabled := True;
for I := 0 to MenuItem.Count - 1 do
MassEnable( MenuItem.Items[ I], Enabled)
end
else MenuItem.Enabled := Enabled
end;
Re: Themenvorschläge WissensDB
Eigentlich stolpert jeder irgendwann über das Problem. Da hat man ein kleines Programm geschrieben, das einen selbstgestrickten Dialog enthält. Darauf hat man zwei BitBtns plaziert, denen man im Objektinspektor die Eigenschaft Kind = bkOk und bkCancel zugewiesen hat. So verkündet nun der eine Button sein fröhliches "Ok", der andere ein grimmiges "Abbrechen". Aber leider nur im Entwurfsmodus - denn startet man sein Programm, steht da plötzlich ein schnödes "Cancel", wo doch bis gerade eben noch "Abbrechen" stand.
Leider ist das Problem nicht ganz trivial zu lösen. Die Lazarus-IDE bietet (bislang) keinen Automatismus, der die Einstellungen sozusagen "per Mausklick" für einen erledigen würde. Und bei genauerem Hinsehen entpuppt sich die Sache auch als etwas vertrackter, jedenfalls sofern man Wert darauf legt, dass sich unser Button ein paar Hundert Kilometer weiter südlich auch eines wohlklingenden "Cancella!" zu bemächtigen vermag. So kommt man also nicht umhin, sich mit dem Mechanismus der Lokalisierung zumindest in groben Umrissen auseinanderzusetzen.
Ich möchte im Folgenden darstellen, welche Schritte man unternehmen muss, um
a) seinen LCL-Komponenten Deutsch beizubringen (d.h. dem Button das vermaledeite "Cancel" auszutreiben), und
b) seinem gesamten Programm ein elegantes "Cambiamento di modalità" zu verpassen (anstatt unsere italienischen Freunde über dem Wort "Modusänderung" verzweifeln zu lassen).
Dabei versuche ich nicht, alle Feinheiten und Eventualitäten zu erfassen, sondern vielmehr eine Art "Trampelpfad" zu weisen, der zu einer möglichst einfachen Lösung für die grundlegende Problematik führt. Dinge wie sprachspezifische Varianten der Datumsformatierung oder dergleichen blende ich dabei bewußt aus - an dieser Stelle sei stattdessen auf den Eintrag in der Lazarus-Wiki verwiesen: http://wiki.lazarus.freepascal.org/Translations_/_i18n_/_localizations_for_programs/de#.C3.9Cbersetzung_von_Formularen.2C_Datenmodulen_und_Frames
Soweit Versions- bzw. Betriebssystem-spezifische Menüpunkte und Befehle angesprochen werden, beziehe ich mich auf das deutschsprachige Lazarus 1.2.0 unter Linux.
Eine weitere Vorbemerkung: Viele Wege führen bekanntlich nach Rom. Es gibt noch andere Trampelpfade, vielleicht sogar einfachere, z.B. über die Lazarus-Units DefaultTranslator (siehe http://wiki.lazarus.freepascal.org/Step-by-step_instructions_for_creating_multi-language_applications), oder via GNU GetText. Das Problem dabei ist, dass Vieles davon nicht oder nur rudimentär dokumentiert ist und dieses Wenige, etwa Wiki-Einträge, dann oftmals auch schon in Teilen überholt ist und etwa Dinge beschreibt, die es längst nicht mehr gibt (z.B. .rst-Dateien). Dieser Text ist überhaupt erst dadurch entstanden, daß mir der oben angeführte Wiki-Eintrag auf der Suche nach der hier beschriebenen Lösung zwar zu einem Drittel weitergeholfen, mich aber zu zwei Dritteln erstmal verwirrt und in die Irre geführt hat. Ich hoffe, den Lesern passiert mit diesem Text nicht dasselbe!
Ohnehin sollten sich Leser, die in Texten wie diesen nach Lösungen für ihre speziellen Probleme suchen, immer bewußt sein, dass sich Lazarus rasant weiterentwickelt. Davon profitieren wir ja, aber das hat auch seinen Preis: Manches von dem, was im Folgenden beschrieben ist, wird in Vorläuferversionen nicht unmodifiert umsetzbar sein, und umgekehrt ist jetzt schon absehbar, dass Teile dieses Textes mit Einführung der kommenden Version 1.4 überarbeitungsbedürftig sein werden, da diese u.a. ein konsistenteres Ressourcen-Management mitbringen wird. Wenn sie also z.B. an Wiki-Einträgen auflaufen, die Dinge beschreiben, die Sie in Ihrem System nicht finden können (mir ging es so mit den .rst-Dateien), oder die so wie beschrieben erstmal nicht funktionieren, verzweifeln Sie nicht! Haben Sie Nachsicht, wenn nicht jedes Tutorial auf dem neuesten Stand ist und versuchen Sie, wenigstens die dahinterstehende Logik mit Gewinn aufzunehmen, die ändert sich nämlich in aller Regel weniger schnell als etwa die Position eines bestimmten Compilerschalters in der Menüstruktur von Lazarus...
Zurück zum Thema Lokalisierung:
1) Was kann bzw. soll eigentlich übersetzt werden?
Zunächst einmal natürlich der ärgerliche Cancel-Button. Um ihm einen bestimmten Text zuzuweisen, haben wir vier, im speziellen Fall eines TBitBtn sogar fünf verschiedene Möglichkeiten:
- a) BitBtn-spezifisch ist die Variante, ihm im Objektinspektor über die Eigenschaft Kind einen Wert, z.B. bkCancel, zuzuweisen. Was insofern besonders bequem ist, als damit auch schon sein Verhalten (z.B. dessen ModalResult-Rückgabe) festgelegt ist - und eben auch die Aufschrift "Abbrechen".
b) Ein normaler Button verfügt nicht über die Kind-Eigenschaft. Aber auch sein Text läßt sich im Objektinspektor mithilfe der Eigenschaft Caption festlegen - das ist die zweite Variante.
c) Dritte Möglichkeit: Man weist ihm die gewünschten Aufschrift im Quelltext, z.B. in der OnCreate-Methode der übergeordneten Form, zu:
Code: Alles auswählen
MeinAbbruchButton.Caption := 'Abbrechen';
d) Vierte, etwas umständlichere Variante: Wir definieren eine Konstante:
Code: Alles auswählen
const
MeineFroheBotschaft = 'Abbrechen';
und weisen sie im Quelltext zu:
Code: Alles auswählen
MeinAbbruchButton.Caption := MeineFroheBotschaft;
e) Die fünfte Variante ist eine Abwandlung der vorigen: Anstelle einer "normalen" Konstante deklarieren wir:
Code: Alles auswählen
resourcestring
MeineFroheBotschaft = 'Abbrechen';
Recourcestrings sind im Grunde spezialisierte (String-) Konstanten, sie verhalten sich auch weitgehend gleich, nur dass sie zusätzlich übersetzungstauglich sind. Die Zuweisung:
Code: Alles auswählen
MeinAbbruchButton.Caption := MeineFroheBotschaft;
bleibt die gleiche.
Was passiert nun, wenn wir das Programm kompilieren? Die Methoden b) bis e) funktionieren wie erwartet, lediglich die erste Version macht hartnäckig Ärger und versteift sich auf das unerwünschte "Cancel". Es kommt noch doller: Man kann auch Variante a) austricksen, indem man im Objektinspektor die Caption editiert (z.B. nach "Ab-Brechen"). Kompiliert man dann das Programm und nimmt die Änderung anschließend wieder zurück, zeigt auch der BitBtn aus Variante a) für alle Zeiten sein schönstes "Abbrechen"... Aber klar: Das ist nun wirklich Krampf!
Ist das also ein Bug? Mitnichten! Um das zu verstehen, sollten wir erst noch die weitergehende Aufgabe betrachten, unser Programm ins Italienische zu übersetzen. Angenommen, wir haben alle weiter unten beschriebenen programmtechnischen Voraussetzungen erfüllt, ist Variante a) nun plötzlich überhaupt kein Thema mehr: Der BitBtn zeigt ohne jedes weitere Zutun sein schönstes "Cancello" vor. Auch die Varianten b) und e) funktionieren bestens, vorausgesetzt, wir haben entsprechende Übersetzungen in einer .po-Datei bereitgestellt und eingebunden. Doch die Varianten c) und d) stellen sich nun quer und bleiben stur bei ihren deutschen Texten. Dem könnte man nur beikommen, indem man den Quelltext mit bedingten Zuweisungen vollmüllt:
Code: Alles auswählen
if Language := 'Deutsch' then MeinAbbruchButton.Caption := 'Abbrechen' else
if Language := 'Italiano' then MeinAbbruchButton.Caption := 'Cancello' else
if...
... ad infinitum, und das für jede Buttonart, jede Dialogüberschrift, jede Fehlermeldung etc. der LCL einzeln...
Genau das will man ja nicht, auch das wäre Krampf.
Warum also bereitet uns die Variante a) soviel Kopfzerbrechen? Nun, gerade aufgrund der Fähigkeit des BitBtn, sich in allen Spachen darstellen zu können! Die LCL-Komponenten wurden auf Englisch geschrieben, "Cancel" ist der Originaltext des Abbruch-Buttons. Um ihn in der deutschen Fassung anzeigen zu können, muss die IDE die entsprechende deutschsprachige Übersetzungsdatei (es handelt sich in dem Fall um die "lclstrconsts.de.po") eingebunden haben (das hat sie auch) - und genau dasselbe muss unser Programm eben auch tun, um diese Buttons auf Deutsch beschriften zu können. Wir müssen unserem Programm also explizit sagen, welche Sprache es verwenden soll: Der Button "spinnt" nur deshalb, weil wir das bislang versäumt haben. Und warum funktioniert der oben beschriebene schmutzige Trick? Weil wir die Bindung zwischen dem Button und seinem Originaltext zerstört haben, als wir die Caption unnötig überschrieben haben. Damit haben wir letztlich die leistungsfähige Variante a) in die informationsärmere Variante b) umgewandelt - und uns so aufs Ganze gesehen unnötige Zusatzarbeit für den Fall der Übertragung ins Italienische aufgehalst (wir müssen nun die Übersetzung von "Abbrechen" nach "Cancella" selbst besorgen, anstatt uns der Vorarbeit der Lazarus-Übersetzer zu bedienen)...
Halten wir fest: Übersetzungstauglich sind alle veröffentlichten String-Eigenschaften von Komponenten (also alle Strings, die im Objektinspektor editiert werden können - die Varianten a und b), sowie alle Textschnipsel, die als Resourcestrings deklariert wurden (Variante e). Nicht geeignet sind hingegen direkte Stringzuweisungen im Quelltext (c) oder normale String-Konstanten (d).
2) Die .po-Dateien
Beherzigt man diese Konvention (die "Guten" ins Property- und Resourcestring-Töpfchen, die "Schlechten" ins Quelltext- bzw. const-Kröpfchen), nimmt einem die Lazarus-IDE den nächsten Schritt ab: Das programmweite Einsammeln der zu übersetzenden Textschnipsel in einer generischen po-Datei. Man muß Lazarus lediglich sagen, dass man das haben will, indem man im Menü Projekt/Projekteinstellungen/i18n zwei Häkchen setzt: "i18n einschalten" und ".po Datei beim Speichern einer lfm Datei erstellen/aktualisieren". Gegebenenfalls kann man auch ein Zielverzeichnis angeben, verzichtet man darauf, landet die po-Datei einfach im Projektverzeichnis. (Nebenbei: Man kann im Projektinspekor auch einzelne Units gezielt davon ausnehmen: Einfach rechte Maustaste auf die betreffende Unit und "i18n für lfm deaktivieren" anklicken).
Wobei es sich empfielt, damit zu warten, bis man alles beisammen hat. Man kann sich in der po-Datei nämlich auch eine Menge Schrott einfangen, z.B. irgendwann deklarierte, aber später wieder gelöschte Resourcestrings.
Diese von der IDE automatisch erzeugte po-Datei, meinetwegen "MyProject.po" ist generisch, d.h. wir müssen sie erst zu einer sprachspezifischen Datei machen, indem wir sie mit Übersetzungen füllen und als spezifische Sprachdatei abspeichern (z.B. als "MyProject.it.po"). Angenommen, unser Programm namens "MyProject" enthält eine Unit "IchMagNichtMehr.pas", in der wir den String
Code: Alles auswählen
resourcestring
msg_WarumFunktioniertDasNicht = 'Hiiilfe!";
deklariert haben. Dann finden wir in "MyProject.po" diese Eintragsgruppe:
Code: Alles auswählen
#: ichmagnichtmehr.msg_warumfunktioniertdasnicht
msgid "Hiiilfe!"
msgstr ""
Die ganze Datei besteht (abgesehen von einem Header) aus lauter solchen Gruppen. Wir müssen nun einfach die Leerstrings mit Übersetzungen füllen, also etwa
Code: Alles auswählen
msgstr "Aiuuuto!"
und die Datei dann unter dem Namen "MyProject.it.po" abspeichern. (Es gibt eine Reihe von Hilfsprogrammen, um po-Dateien komfortabler zu editieren, z.B. PoEdit, aber im Grunde tut es auch ein simpler Texteditor).
Was die LCL-Komponenten, darunter unseren störrischen Cancel-BitBtn, angeht, so haben uns die Lazarus-Entwickler die meiste Arbeit bereits abgenommen und fertig übersetzte po-Dateien bereitgestellt - im Lazarus-Verzeichnis unter /lcl/languages (unter Debian Linux z.B. in /usr/share/lazarus/1.2.0/lcl/languages/). Die Datei, die wir brauchen, heißt "lclstrconsts.de.po" (oder eben "lclstrconsts.it.po" für unsere italienische Programmvariante). Die müssen wir nur noch einbinden - damit ist der Cancel-Button schon mal erschlagen. Selbst übersetzen müssen wir lediglich das, was wir in unserem Programm an Texten hinzugefügt haben, also z.B. jenes "Aiuuuto!" in "MyProg.it.po".
3) po-Dateien ins eigene Programm einbinden
Da gibt es zwei Verfahren, ein einfaches umständliches, und ein umständlich einfaches.
a) Das zunächst einfachere Verfahren besteht darin, die po-Datei(en) zur Laufzeit, sinnvollerweise beim Programmstart, einzubinden. Dazu genügt im einfachsten Fall (wenn man schon genau weiß, welche Sprache man haben will) eine einzige Programmzeile:
Code: Alles auswählen
Unit MyMainForm;
uses
Translations;
(...)
initialization
Translations.TranslateUnitResourceStrings('LCLStrConsts',
'/Path/To/Lazarus/lcl/languages/lclstrconsts.%s.po','de','de'); { oder <'fi', 'fi'> für finnisch...}
end.
Wem es besser gefällt, der kann die Zeile natürlich auch in die OnCreate-Prozedur der Form packen. Und wer sich die Option für mehrere Sprachen offenhalten will, muss anstelle des ", 'de', 'de'" entsprechende Variablen vorab bereitstellen.
Aber diese denkbar einfache Lösung hat einen entscheidenden Haken: Unser Programm muss (unter allen Umständen!) die "lclstrconsts.de.po" auch finden können - sie braucht sie bei jedem Programmstart. Auf dem eigenen Rechner ist das kein Problem, aber wenn wir das Programm weitergeben, müssen wir entsprechende Vorsorge treffen. Und wir müssen alle infragekommenden po-Dateien, also die englische, die französische, italienische usw. mitliefern. So wird das einfache Verfahren also doch schnell wieder ziemlich umständlich.
Immerhin: wen das nicht weiter stört, der hat an dieser Stelle ausgesorgt (und braucht nicht mehr weiterlesen)...
b) Die andere Möglichkeit besteht nun darin, die Übersetzungen fest in das kompilierte Programm (als Ressourcen) einzubinden. Damit entfällt die Notwendigkeit, alle denkbaren po-Dateien mitliefern und sich um deren späteren Aufenthaltsort kümmern zu müssen. Andererseits muss man dafür aber etwas mehr Aufwand treiben.
Das liegt daran, dass die po-Files als reine Textdateien nur einen Zwischenschritt darstellen, eine Schnittstelle zum Programmierer bzw. Übersetzer. Lazarus braucht die Ressource aber in anderer Form, als Lazarus Resource File (*.lrs). Sie muss daher erst noch konvertiert werden (die oben verwendete Laufzeit-Routine Translations.TranslateUnitResourceStrings erledigt das stillschweigend für uns, aber leider ist sie für diesen Fall nicht geeignet, da nun die Daten beim Kompilieren schon fertig konvertiert vorliegen müssen). Also müssen wir selbst nochmal Hand anlegen.
Eine Nebenbemerkung zum Thema Speicherbedarf: Vielen Programmierern widerstrebt es, unnötig Computer-Ressourcen zu binden. Und wer noch die unförmigen 360-kB-Disketten erlebt hat und sich an den Jubel erinnert, der ausbrach, als die ersten Festplatten mit unfaßbaren 10 MB Speicherplatz die 2000.-DM-Schallmauer unterschritten, der hat seinen Knacks für's Leben abbekommen... Aber betrachten wir die Dimensionen mal nüchtern: Schon eine ganz simple Lazarus-GUI-Anwendung bringt (mit Standard-Compilereinstellungen) in der Regel wenigstens 15 MB auf die Waage (okay: Echte Kerle fangen jetzt an zu basteln: http://wiki.freepascal.org/Size_Matters/de). Eine LCLStrConst.xx.lrs-Datei ist im Schnitt 50 kB groß. Selbst wenn wir sie in 20 Sprachen in unser Programm einbinden, haben wir es von 15 auf gerade mal 16 MB aufgeblasen (oder, lebensnäher, von 37 auf 38 MB). So beängstigend ist das dann auch wieder nicht...
4) Lazarus-Resource-Dateien (*.lrs) erstellen und einbinden
Wie schon gesagt besteht der nächste Schritt darin, die fertig übersetzten (sprachspezifischen) po-Dateien in lrs-Dateien umzuwandeln und diese in das eigene Programm einzupflegen. Dazu stellt Lazarus im Verzeichnis Pfad/zu/Lazarus/tools ein Kommandozeilenprogramm namens "lazres" zur Verfügung. Es wird folgendermaßen aufgerufen:
Code: Alles auswählen
Pfad/zu/lazres Pfad/zum/Ziel_Projekt/lrs_Dateiname Pfad/zu/den/Quelldateien/po_Dateiname
(Wer Genaueres darüber wissen will, wird in der Wiki fündig: http://wiki.lazarus.freepascal.org/lazres/de)
Ich habe mir (unter Debian Linux) das Programm lazres kurzerhand nach /usr/bin/ kopiert und muss mich daher nicht mehr mit dem Pfad zu lazres herumschlagen.
Desweiteren empfielt es sich nachdrücklich, überall die gleichen Dateinamen beizubehalten (die unten bereitgestellte Unit "AppLanguage" setzt das sogar voraus). Das tut auch Lazarus, indem es für unser Projekt ("MyProject") die dazu passende "MyProject.po"-Datei erstellt hat. Daraus leiten wir dann unsere sprachspezifischen Übersetzungsdateien ab, die wir ebenfalls tunlichst "MyProject.<Sprachkürzel>.po" nennen sollten. Aus diesen generieren wir dann mit lazres die "MyProject.<Sprachkürzel>.lrs"-Dateien.
Was brauchen wir alles für unser Programm "MyProject", wenn wir es für Deutsch, Englisch und Italienisch fit machen wollen? Zunächst die "lclstrconsts" für alles, was die LCL bereitstellt (u.a. unseren Cancel-BitBtn). Da ist das Original englisch, wir brauchen also nur noch die "lclstrconsts.de.po" und die "lclstrconsts.it.po". Hinzu kommen die Anteile, die wir in unser Programm geschrieben haben. Angenommen, wir haben unser Programm im Original mit deutschen Texten, Überschriften etc. versehen, dann brauchen wir hierfür also noch die englischen und italienischen Übersetzungen "MyProject.en.po" und "MyProject.it.po". Das alles konvertieren wir z.B unter Debian Linux in der Shell:
Code: Alles auswählen
~$ lazres Pfad/zu/MyProject/lclstrconsts.de.lrs /usr/share/lazarus/1.2.0/lcl/languages/lclstrconsts.de.po
~$ lazres Pfad/zu/MyProject/lclstrconsts.it.lrs /usr/share/lazarus/1.2.0/lcl/languages/lclstrconsts.it.po
~$ lazres Pfad/zu/MyProject/MyProject.en.lrs Pfad/zu/MyProject/MyProject.en.po
~$ lazres Pfad/zu/MyProject/MyProject.it.lrs Pfad/zu/MyProject/MyProject.it.po
Damit befinden sich alle lrs-Dateien, die wir brauchen, im Projektverzeichnis, die müssen wir als nächstes in unser Programm einbinden. Das können wir an verschiedenen Stellen tun, im Initialisierungsteil einer Unit, der Mainform oder in der lpr-Projektdatei (von der ich persönlich allerdings tunlichst die Finger lasse):
Code: Alles auswählen
initialization
{$I lclstrconsts.de.lrs}
{$I lclstrconsts.it.lrs}
{$I MyProject.en.lrs}
{$I MyProject.it.lrs}
Damit sind alle Voraussetzungen geschaffen. Wir müssen unserem Programm jetzt nur noch beibringen, wie es zur Laufzeit die passende Sprache auswählen und verwenden kann, und zwar auf allen Rechnern zwischen Gateshead, Castrop-Rauxel und Civitaluparella...
5) Parlez-vous Francais? Habla espaniol? Ma no: Parlo Italiano, cara mia!
Eine typische mehrsprachige Anwendung fackelt nicht lange. Sie fragt beim Programmstart die Systemsprache des Computers ab, stellt ihre Sprachauswahl darauf ein und das war's dann auch schon.
Die FPC-/Lazarus-Bibliotheken stellen für diese Systemabfrage fertige Routinen bereit, an sich sollte ein Aufruf von
Code: Alles auswählen
uses
LazUTF8
var
LocalLanguage: String;
(...)
initialization
LazGetShortLanguageID(LocalLanguage);
end.
vollauf genügen. Diese Prozedur weist der Variablen LocalLanguage jene zweibuchstabigen Sprachkürzel (gemäß ISO 639-1) zu, die Lazarus auch zur Kennzeichnung seiner po.-Dateien verwendet, also "de", "it" etc. Die Formulierung "an sich sollte..." ist der Forderung geschuldet, dass diese Routine an sich plattformunabhängig sein sollte. Ich kann das nicht testen, deshalb sei an dieser Stelle auf die sehr viel kompliziertere Funktion verwiesen, die in der Wiki beschrieben wird: http://wiki.lazarus.freepascal.org/Translations_/_i18n_/_localizations_for_programs/de#Cross-Plattform-Methode.2C_um_Systemsprache_festzustellen
Welchem Stand der Lazarus-Entwicklung diese Routine entspricht, ist nicht erkennbar. Bis zum Beweis des Gegenteils gehe ich also davon aus, dass LazUTF8.LazGetShortLanguageID() plattformübergreifend das tut, was es tun soll - wenn es Probleme gibt, dann eben stattdessen die Wiki-Funktion. Die liefert übrigens ihre Ergebnisse im selben 2-Buchstaben-Format.
6) Drag, drop, plug, play and forget: Die Unit AppLanguage
Für mein aktuelles Projekt habe ich den hier beschriebenen Komplex in eine eigene Unit ausgelagert, die ich hiermit zur Verfügung stelle. Im Normalfall genügt es vollauf, diese Unit (etwa im Projektinspektor) Ihrem Projekt hinzuzufügen (damit gelangt es in den uses-Teil Ihrer Projekt.lpr). Ihre Units brauchen sie nicht zu sehen, denn bis die zum Leben erweckt werden, ist schon alle Arbeit getan und sind alle revanten Stingkonstanten auf die passende Übersetzung umgebogen (es sei denn, Ihr Programm sieht vor, zur Laufzeit Spracheinstellungen wechseln zu können, dann müssen sie sie natürlich in die entsprechende uses-Liste aufnehmen, um TranslateFromResource() aufrufen zu können).
Die Unit enthält im Wesentlichen den Mechanismus zur Abfrage der Systemsprache und zum Aktivieren der so ermittelten Sprachressource in Ihrem Programm. Sie ist keine "echte" Bibliotheks-Unit, denn Sie müssen sie im Initialisierungsteil ggfls. noch an Ihre Wünsche anpassen.
Code: Alles auswählen
{ AppLanguage.pas
Author: Ruediger Walter, with thanks to Bart Broersma
*****************************************************************************
* *
* This file is intended for use with the Lazarus Component Library (LCL), *
* under the same licence (LGPL) as the Lazarus Component Library. *
* *
* See the file COPYING.modifiedLGPL.txt, included in your Lazarus *
* distribution, for details about the copyright. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* *
****************************************************************************}
unit AppLanguage;
{$mode objfpc}{$H+}
interface
uses
Classes, LazUTF8, LResources, Translations;
type
TTranslateFromResourceResult = (trSuccess, trResourceNotFound, trTranslationError);
var
LocalLanguage,
StandardLanguage : String;
function TranslateFromResource(AResourceName, ALanguage,
AStandardLanguage: String): TTranslateFromResourceResult;
implementation
function TranslateFromResource(AResourceName, ALanguage,
AStandardLanguage: String): TTranslateFromResourceResult;
var
LRes : TLResource;
POFile : TPOFile = nil;
SStream : TStringStream = nil;
begin
Result := trResourceNotFound;
LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');
if LRes = nil then
begin
{ berücksichtigt Besonderheiten der Namensgebung von Lazarus-po-Dateien }
if (ALanguage = 'pt') or (ALanguage = 'zh') then
begin
if ALanguage = 'pt' then ALanguage := 'pt_BR' else ALanguage := 'zh_CN';
LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');
end;
{ wenn auch das nicht funktioniert hat, dann Rückzug zur Standardsprache }
if LRes = nil then
LRes := LazarusResources.Find(AResourceName + '.' + AStandardLanguage, 'PO');
end;
if LRes <> nil then
try
SStream := TStringStream.Create(LRes.Value);
POFile := TPoFile.Create(SStream, False);
{ Wichtig hierbei: Übergabe des Parameters Full als False, sonst fängt man sich
eine "TPOFile.Translate Inconsistency"-Exception ein, sobald ein Leerstring
als Übersetzung übergeben wird. Mit Full := false wird anstelle eines
fehlenden Übersetzungsstrings dann der Originalstring weiterverwendet. }
try
if TranslateUnitResourceStrings(AResourceName, POFile) then Result := trSuccess
else Result := trTranslationError;
except
Result := trTranslationError;
end;
finally
if Assigned(SStream) then SStream.Free;
if Assigned(POFile) then POFile.Free;
end;
end;
initialization
{ Resourcefiles einbinden }
{$I lclstrconsts.de.lrs}
// {$I lclstrconsts.it.lrs}
// {$I MyProject.en.lrs} { Nach Bedarf einzusetzen }
// {$I MyProject.it.lrs}
{ Sprache ermitteln }
LazGetShortLanguageID(LocalLanguage); { wo bin ich ? }
StandardLanguage := 'en'; { für den ungeplanten Fall }
if LocalLanguage = '' then LocalLanguage := StandardLanguage;
{ Für die Lazarus-Komponenten }
if LocalLanguage <> 'en' then { denn das Original ist ja schon englisch }
TranslateFromResource('lclstrconsts', LocalLanguage, StandardLanguage);
{ Für unsere Programm-Anteile }
// if LocalLanguage <> 'de' then { sofern das Original deutsch ist }
// TranslateFromResource('MyProject', LocalLanguage, StandardLanguage);
end.
7) Eine offene Frage
Ein Problem bleibt vorerst offen. Angenommen, unser Programm sieht vor, zur Laufzeit die Spracheinstellung ändern zu können. Wie kann man dann den Originalzustand wiederherstellen? Also z.B. wenn beim Programmstart die deutsche Version der "LCLStrConsts" geladen wurde und man nun wieder auf englisch zurückschalten möchte? Leider findet sich nirgendwo eine Dokumentation der Lazarus-Unit Translations, die diese Frage klären könnte.
Ein Workaround ist, für den Fall die generische "lclstrconsts.po" unverändert in eine "lclstrconsts.en.po" zu kopieren, diese in eine lrs-Datei umzuwandeln und in das Programm einzubinden (dabei braucht es den Zwischenschritt des Kopierens und Umbenennens der lclstrconsts.po, warum auch immer). Das funktioniert dann auch, ist aber natürlich äußerst unelegant, da man auf diese Weise dieselbe Ressource zweimal ins Programm lädt. Vielleicht kennt einer der Leser eine bessere Methode?
8) Siehe auch
Grundlegend, aber momentan (Mai 2014) überarbeitungsbedürftig:
http://wiki.lazarus.freepascal.org/Translations_/_i18n_/_localizations_for_programs/de
Ein sehr schönes Tutorial, das einen alternativen "Trampelpfad" (via DefaultTranslator) beschreitet:
http://wiki.lazarus.freepascal.org/Step-by-step_instructions_for_creating_multi-language_applications
9) Na endlich: Basta!
Vielen Dank für Ihre Geduld! Korrekturen, Hinweise, Anregungen, Kritik und Blumen sind selbstverständlich stets willkommen!
Rüdiger Walter (Mai 2014)
-
- Lazarusforum e. V.
- Beiträge: 3158
- Registriert: Di 22. Jul 2008, 19:27
- OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
- CPU-Target: 32bit x86 armhf
- Wohnort: Köln
- Kontaktdaten:
Re: Themenvorschläge WissensDB
Ein paar Hinweise und Fragen:
- Als typische Programmgröße würde ich von ca. 2 Megabyte bei kleinen Programmen ausgehen (die ewigen Debug-Symbole); Vielleicht kannst du noch einen Hinweis/Link dazu einbauen (http://wiki.freepascal.org/Size_Matters/de).
- Die Lazarus-Ressourcen (.lrs) sehe ich als veraltet an; gibt es eine Möglichkeit FPC Ressourcen zu verwenden (http://wiki.freepascal.org/Lazarus_Reso ... _resources)?
- Ein paar Rechtschreibfehler, die ich auf die schnelle aber nicht mehr wiederfinde.
- Abschnitt 2 (po-Dateien) (zur Not hilft dann: i18n ausschalten, neukompilieren, po-Datei löschen und i18n wieder einschalten).
-- Ist es nicht ausreichend, die po-Dateien zu löschen und das Programm neu zu übersetzen?
Re: Themenvorschläge WissensDB
Socke hat geschrieben:Als typische Programmgröße würde ich von ca. 2 Megabyte bei kleinen Programmen ausgehen (die ewigen Debug-Symbole); Vielleicht kannst du noch einen Hinweis/Link dazu einbauen (http://wiki.freepascal.org/Size_Matters/de).
Danke für den Hinweis, hab's eingebaut. Ich gebe zu, mich mit dem Thema nie wirklich beschäftigt zu haben, weil ich seit der Zeitenwende zum 386er-Prozessor nie wieder auch nur im Entferntesten an Hardware-Ressourcengrenzen gestoßen bin (vorher unentwegt, das schon).
Socke hat geschrieben:Die Lazarus-Ressourcen (.lrs) sehe ich als veraltet an; gibt es eine Möglichkeit FPC Ressourcen zu verwenden (http://wiki.freepascal.org/Lazarus_Reso ... _resources)?
Nicht daß ich wüßte. Kann schon sein, daß das die Zukunft der Lokalisierungsdaten sein wird, aber diese Alternative ist mir bei der Recherche über das Thema nicht untergekommen.
Socke hat geschrieben:Ein paar Rechtschreibfehler, die ich auf die schnelle aber nicht mehr wiederfinde.
Die betrachte ich als meine persönlichen Feinde, gleich hinter Zahnärzten und Finanzbeamten... Und bin daher für jeden Hinweis dankbar. Wobei ich allerdings der neuen Rechtschreibung mit stoischer Ignoranz begegne (Worten wie "Stopp", "Tipp" oder "Stofffffffetzen" kann ich nur kabarettistischen Nutzwert entnehmen). So habe ich zwar widerstrebend den Text mit "muss" und "dass" vollzupflastern versucht, aber dabei bestimmt die Hälfte übersehen. Ich kuck's nochmal systematisch durch.
Socke hat geschrieben:Abschnitt 2 (po-Dateien) (zur Not hilft dann: i18n ausschalten, neukompilieren, po-Datei löschen und i18n wieder einschalten).
-- Ist es nicht ausreichend, die po-Dateien zu löschen und das Programm neu zu übersetzen?
Das war auch mein erster Gedanke. Hab dann rumprobiert: Es ist offenbar ziemlich verworren. Soweit ich sehe, braucht es eine Änderung speziell der ressourcenrelevanten Teile, während die po-Datei gelöscht ist, um die Stringkadaver zu entfernen. Eine Änderung im Quelltext der Unit bzw. ein Neukompilieren alleine reicht nicht (vielleicht ist das auch versionsabhängig). Aber ich habe gerade festgestellt, daß auch meine Methode nicht immer funktioniert. Ich habs jetzt erst mal ganz raus genommen, bis ich es besser verstanden habe. Vielleicht hat jemand einen Tip(-pppp)?
Gruß Rüdiger
-
- Beiträge: 573
- Registriert: Mi 25. Mär 2009, 21:12
- OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
- CPU-Target: mostly 32 bit
Re: Themenvorschläge WissensDB
Socke hat geschrieben:[*]Die Lazarus-Ressourcen (.lrs) sehe ich als veraltet an; gibt es eine Möglichkeit FPC Ressourcen zu verwenden (http://wiki.freepascal.org/Lazarus_Reso ... _resources)?
Siehe hier: http://wiki.lazarus.freepascal.org/Laza ... DE_Changes
-
- Beiträge: 29
- Registriert: Fr 19. Mai 2017, 18:04
- OS, Lazarus, FPC: Win10x64, L 1.8rc3, FPC3.0.2
- CPU-Target: 64
- Wohnort: Nord-Baden
- Kontaktdaten:
Re: Themenvorschläge WissensDB
Benutzt:
Code: Alles auswählen
uses LConvEncoding;
TMyComponent enthält dann 2 private Felder, und die Proceduren zu Laden und Speichern:
Code: Alles auswählen
type TMyComponent=class
[...]
private
Flines:TStringList; // Enthält den Text Encoding bereinigt.
FFileEncoding:String; // Enthält den Encoding-String der Orginal-Datei
[...]
procedure LoadfromFile(Filename: STRING);
procedure LoadfromStream(Stream: TStream);
procedure SavetoFile(Filename: STRING);
procedure WriteToStream(Stream: TStream);
[...]
End;
Zum Laden benutzt man:
Code: Alles auswählen
procedure TMyComponent.LoadfromFile(Filename: STRING);
var s:string;
sf:TFileStream;
BEGIN
sf:=TFileStream.Create(Filename,fmOpenRead);
try
setlength(s,sf.Size);
sf.ReadBuffer(s[1],sf.Size);
FFileEncoding := GuessEncoding(s);
FLines.Text:=ConvertEncoding(s,FFileEncoding,EncodingUTF8);
finally
freeandnil(sf);
end;
END;
procedure TMyComponent.LoadfromStream(Stream: TStream);
var s:String;
BEGIN
setlength(s,Stream.Size);
Stream.ReadBuffer(s[1],Stream.Size);
FFileEncoding := GuessEncoding(s);
FLines.Text:=ConvertEncoding(s,FFileEncoding,EncodingUTF8);
END;
Zum Speichern:
Code: Alles auswählen
procedure TMyComponent.SavetoFile(Filename: STRING);
var s:string;
sf:TFileStream;
BEGIN
if fileexists(Filename) then
sf:=TFileStream.Create(Filename,fmOpenWrite)
else
sf:=TFileStream.Create(Filename,fmCreate);
try
s:=ConvertEncoding(FLines.Text,EncodingUTF8,FFileEncoding);
sf.WriteBuffer(s[1],Length(s));
finally
freeandnil(sf);
end;
END;
procedure TMyComponent.WriteToStream(Stream: TStream);
var s:String;
BEGIN
s:=ConvertEncoding(FLines.Text,EncodingUTF8,FFileEncoding);
Stream.WriteBuffer(s[1],Length(s));
END;