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_FramesSoweit 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-DateienBeherzigt 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 einbindenDa 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 einbindenWie 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 FrageEin 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 auchGrundlegend, aber momentan (Mai 2014) überarbeitungsbedürftig:
http://wiki.lazarus.freepascal.org/Translations_/_i18n_/_localizations_for_programs/deEin sehr schönes Tutorial, das einen alternativen "Trampelpfad" (via DefaultTranslator) beschreitet:
http://wiki.lazarus.freepascal.org/Step-by-step_instructions_for_creating_multi-language_applications9) 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)