Themenvorschläge WissensDB

Hilfreiche Artikel und Tutorials
Antworten
monta
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

Beitrag von monta »

Um den Wildwuchs von Fragen u.a. in dieser Kategorie zu vermeiden dürfen keine neuen Themen erstellt werden, damit es hier so aufgeräumt wie möglich bleibt.

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.
Johannes

pluto
Lazarusforum e. V.
Beiträge: 7178
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Themenvorschläge WissensDB

Beitrag von pluto »

Ich habe hier eine kleine Beispiel Funktion erstellt, wie man, die Erweiterung von einem SaveDialog Automatisch an den Dateinamen anhängen kann:

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.

Scotty
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

Beitrag von Scotty »

Manchmal kann es nützlich sein, dass ein Hint Graphiken enthält, d.h. Methoden von TCanvas bekommt. OnShowHint eines Controls ist definiert als procedure(Sender: TObject; HintInfo: PHintInfo); wobei HintInfo^ leicht genutzt werden kann, um zum Beispiel Umbrüche in lange Hints einzufügen. Zugriff auf den Canvas hat man damit aber nicht.
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;

pluto
Lazarusforum e. V.
Beiträge: 7178
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Themenvorschläge WissensDB

Beitrag von pluto »

Und hier gleich mein zweiter Vorschlag: "Array of Const verwenden"
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;

pluto
Lazarusforum e. V.
Beiträge: 7178
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Themenvorschläge WissensDB

Beitrag von pluto »

Titel: Dem HTMLPanel ein Stream übergeben
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.

pluto
Lazarusforum e. V.
Beiträge: 7178
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Themenvorschläge WissensDB

Beitrag von pluto »

Ich bin dafür, dass die Tipps und Trickst für die WissenDB, andere sind, als in den übrigen(Anderen Foren wie z.b. dp, df). Da es sonst kein wirklichen Sinn machen würde(Aus meiner Sicht).

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.

diogenes
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

Beitrag von diogenes »

Im Folgenden eine kleine, feine Prozediur, mit der man einen ganzen Menübaum enablen/disablen kann. Ich laß es mal unkommentiert, ist hoffentlich klar genug.

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;

ruewa
Beiträge: 153
Registriert: Sa 12. Apr 2014, 14:43

Re: Themenvorschläge WissensDB

Beitrag von ruewa »

Lokalisation: Wie man seiner Anwendung Deutsch und Italienisch beibringt.

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)
Zuletzt geändert von ruewa am Di 13. Mai 2014, 16:13, insgesamt 5-mal geändert.

Socke
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

Beitrag von Socke »

Eine super Zusammenfassung.

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?

ruewa
Beiträge: 153
Registriert: Sa 12. Apr 2014, 14:43

Re: Themenvorschläge WissensDB

Beitrag von ruewa »

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... :shock: 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

martin_frb
Beiträge: 571
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

Beitrag von martin_frb »

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

jc99
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

Beitrag von jc99 »

Codierung (ANSI, UTF8, UNICODE) von Text weitestgehend automatisch erraten und Dateien ggf. wieder in der Orginalcodierung speichern.
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

Antworten