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: 7192
- 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
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;
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: 7192
- 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: 7192
- 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;
Edit01: Zwei Hinweise hinzugefügt.
-
- 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: 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/Tran ... 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 ... plications), 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:
d) Vierte, etwas umständlichere Variante: Wir definieren eine Konstante:Code: Alles auswählen
MeinAbbruchButton.Caption := 'Abbrechen';
und weisen sie im Quelltext zu:Code: Alles auswählen
const MeineFroheBotschaft = 'Abbrechen';
e) Die fünfte Variante ist eine Abwandlung der vorigen: Anstelle einer "normalen" Konstante deklarieren wir:Code: Alles auswählen
MeinAbbruchButton.Caption := MeineFroheBotschaft;
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
resourcestring MeineFroheBotschaft = 'Abbrechen';
bleibt die gleiche.Code: Alles auswählen
MeinAbbruchButton.Caption := MeineFroheBotschaft;
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...
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!";
Code: Alles auswählen
#: ichmagnichtmehr.msg_warumfunktioniertdasnicht
msgid "Hiiilfe!"
msgstr ""
Code: Alles auswählen
msgstr "Aiuuuto!"
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.
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
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
Code: Alles auswählen
initialization
{$I lclstrconsts.de.lrs}
{$I lclstrconsts.it.lrs}
{$I MyProject.en.lrs}
{$I MyProject.it.lrs}
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.
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?

Grundlegend, aber momentan (Mai 2014) überarbeitungsbedürftig:
http://wiki.lazarus.freepascal.org/Tran ... rograms/de
Ein sehr schönes Tutorial, das einen alternativen "Trampelpfad" (via DefaultTranslator) beschreitet:
http://wiki.lazarus.freepascal.org/Step ... plications
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: 3177
- 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
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: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).
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: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)?
Die betrachte ich als meine persönlichen Feinde, gleich hinter Zahnärzten und Finanzbeamten...Socke hat geschrieben:Ein paar Rechtschreibfehler, die ich auf die schnelle aber nicht mehr wiederfinde.

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)?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?
Gruß Rüdiger
-
- Beiträge: 586
- 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
Siehe hier: http://wiki.lazarus.freepascal.org/Laza ... DE_ChangesSocke 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)?
-
- 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;
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;
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;
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;