Methode | Besonderheiten | Vorteile | Nachteile |
Ressourcen-Dlls | Es erfolgt eine Trennung von Formularen und der zugehörigen Logik. Die Formulare werden separat übersetzt. | Anpassungen an die Größe der Komponenten und das Umschalten der Sprache im laufenden Programm ist möglich. Es gibt eine Unterstützung durch Delphi. | Nach Änderungen ist die Übersetzung des Programms (der Dll's) notwendig. |
String-Tabellen | String-Tabellen werden in Textform erstellt und als Ressource-Datei (*.rc) in ein Delphi-Projekt eingebunden. | Über spezielle Tools (Ressource Workshop) können die Einträge bearbeitet werden, ohne das das Programm neu übersetzt werden muss | Die Änderung ist nur durch Personen möglich, die solch ein Tool besitzen. |
Separate Dateien | Separate Sprach-Dateien können unterschiedlich aufgebaut sein, z.B. als Ini-Datei mit Abschnitten in denen Name-Wert-Paare eingefügt werden oder als einfache Textdateien. | Diese Dateien liegen separat zum Programm vor und können durch die Endanwender bearbeitet werden. Auf diese Weise können Programme schnell in eine neue Sprache übersetzt werden. Die Sprachumschaltung kann mit oder ohne Neustart des Programms erfolgen. | Durch die Möglichkeit der Änderung der Dateien durch die Anwender kann nicht verhindert werden, dass fehlerhafte/unsinnige Übersetzungen durchgeführt werden. |
String-Konstanten | Über Konstanten oder String-Arrays werden Zeichenketten für mehrere Sprachen definiert. | Es sind keine speziellen Kenntnisse notwendig. | Der Zugriff auf die Konstanten und Zuweisung der Werte an die Komponenten sowie die Wartung der Konstanten ist sehr umständlich. |
Getrennte Übersetzung für jede Sprache | Für jede Sprache wird eine eigene Programmversion erstellt. | Das Programm kann optimal für eine Sprache konfiguriert werden. | Installationen, Übersetzungen usw. sind für jede Sprachversion getrennt notwendig. Es ist gegebenenfalls mehr Verwaltungsaufwand notwendig. |
Worauf ist bei einer Übersetzung zu achten:
Download Beispiel - Example1.zip
Für die Erstellung einer mehrsprachigen Anwendung über String-Tabellen ist die folgende Vorgehensweise möglich (es gibt verschiedene Möglichkeiten der Erstellung einer String-Tabelle):
Komponente | Eigenschaft | Wert |
![]() |
Formular | Name | frmMain | |
Caption | SprachWandler | ||
Button | Name | btnGerman | |
Caption | Deutsch | ||
Button | Name | btnEnglish | |
Caption | Englisch |
Fügen Sie den folgenden Text in die Datei LanFile.rc ein:
STRINGTABLE BEGIN 1, "Sprachwandler" 2, "Deutsch" 3, "Englisch" 1001, "LanguageChanger" 1002, "German" 1003, "English" END
Über STRINGTABLE BEGIN und END wird der Inhalt der String-Tabelle eingeschlossen. Die Einträge der String-Tabelle haben die Form: Bezeichner, Wert. Die Werte mit dem Index 1-3 stellen die deutschen Übersetzungen dar, die Werte mit dem Index 1001-1003 die englischen mit einem Offset von 1000. Wenn eine String-Tabelle in eine *.exe- oder *.dll-Datei eingebunden wird, befindet sie sich in einem sogenannten Ressourcenbereich. Dieser kann mit bestimmten Tools direkt in der *.exe-Datei bearbeitet werden.
Definieren Sie je eine Konstante für die Offsets der Übersetzungen in der String-Tabelle. Der Funktion GetStringFromRessource übergeben Sie die ID in der String-Tabelle und den Offset. Sie liefert den String aus der Tabelle zurück. Liefert die Funktion LoadString einen Wert größer 0 konnte der String gefunden werden. Um das Ganze effizienter zu machen, können Sie die Speicherbereitstellung/-freigabe auslagern (z. B. in die Ereignisse FormCreate / FormDestroy).
const GERMAN = 0; // Offset deutsche Version ENGLISH = 1000; // Offset englische Version function GetStringFromRessource(ID, Offset: Integer): string; var p: PChar; begin p := StrAlloc(256); try if LoadString(hInstance, ID + Offset, p, 255) <> 0 then Result := p else Result := ''; finally StrDispose(p); end; end;
Erstellen Sie eine Methode zum Umschalten der Sprache. Dieser wird der Offset der einzustellenden Sprache übergeben. Die Initialisierung der Komponenten ist in diesem Beispiel recht umständlich gelöst, da für jede Eigenschaft einer Komponente ein eigener Aufruf notwendig ist.
procedure TfrmMain.InitForm(Offset: Integer); begin frmMain.Caption := GetStringFromRessource(1, Offset); btnGerman.Caption := GetStringFromRessource(2, Offset); btnEnglish.Caption := GetStringFromRessource(3, Offset); end;
Eine andere Möglichkeit, wie Sie auch in der zweiten Variante genauer beschrieben wird ist die Iteration über alle Formulare und deren Komponenten innerhalb einer einzigen Schleife. Die Komponenten werden über Ihren Typ identifiziert. Der String in der String-Tabelle wird über den Wert der Eigenschaft Tag der Komponente gefunden.
// Die Elemente der Eigenschaft Components einer Anwendung sind Formulare var i, j: Integer; c: TComponent; begin // dies geht nur für automatisch erstellten Formulare for i := 0 to Application.ComponentCount - 1 do begin // Iteration über alle Komponenten eines Formulars for j := 0 to Application.Components[i].ComponentCount - 1 do begin c := Application.Components[i].Components[j]; if c is TButton then (c as TButton).Caption := GetStringFromRessource((c as TButton).Tag, Offset); ... ... end; end; end;
Beim Klicken auf den Button DEUTSCH, wird die folgende Ereignisbehandlung ausgeführt:
procedure TfrmMain.btnGermanClick(Sender: TObject); begin InitForm(GERMAN); end;
Download Beispiel - Example2.zip
Es wird im folgenden die eine Übersetzungsvariante vorgestellt, die mit separaten Sprach-Dateien arbeitet. Diese können vom Endanwender bearbeitet werden, so dass einfach Anpassungen oder Übersetzungen in eine neue Sprache möglich sind. Die Dateien werden innerhalb des Programms über ein Menü oder eine Auswahlbox zur Auswahl der Sprache angeboten...\Anwendungsverzeichnis | Verzeichnis des Hauptprogramms |
..\Anwendungsverzeichnis\Sprache | Verzeichnis der Sprachdateien |
var StrLst: array[1..3] of string = ('Sie müssen einen Listeneintrag markieren', 'Willkommen im Kurs', 'Information'); const SELECTLISTENTRY = 1; WELCOMEMSG = 2; MSGBXINFO = 3;Binden Sie diese Unit in alle Formulare ein, die Zeichenketten daraus benötigen. Greifen Sie auf die Zeichenketten über die folgende Anweisung zu:
XXX := StrLst[MSGBXINFO];
WriteComponentProperties(LgnWriteMode:
|
Schreibt die Namen, Eigenschaften und deren Werte
in eine Datei. Der Modus legt fest, ob die Datei neu erstellt oder die Daten
angefügt werden. |
WriteInternalStrings(var InternalStrings: |
über diese Methode schreiben Sie das Array von Strings in die Sprachdatei. Als erstes übergeben Sie den Namen des Arrays, dann den Offset mit dem diese durchnummeriert werden sollen und den Modus (in der Regel DFLngAppend - anfügen). |
procedure TDFInitLng.WriteComponentProperties(LgnWriteMode: TLgnWriteMode); var FormsIdx, j, k: Integer; FormName: string; C: TComponent; C2: TComponent; ItemCount: Integer; ItemString: string; begin ... // iterieren über alle Komponenten der Anwendung - in der Regel die Formulare // aus diesem Grund sollen die Formulare auch in die automatische Erstellung // aufgenommen werden, damit sie hier geparst werden können for FormsIdx := 0 to Application.ComponentCount - 1 do begin // prüfen, ob es sich wirklich um ein Formular handelt if Application.Components[FormsIdx] is TForm then begin // für den einfacheren Zugriff ... C2 := Application.Components[FormsIdx]; FormName := C2.Name; // der Formularname wird als erster Eintrag eines neuen Abschnitts eingefügt // [frmMain] // frmMain=CaptionText ini.WriteString(FormName, FormName, (C2 as TForm).Caption); // jetzt wird über alle Komponenten des Formulars iteriert for j := 0 to Application.Components[FormsIdx].ComponentCount - 1 do begin // für den einfacheren Zugriff ... C := Application.Components[FormsIdx].Components[j]; // Klassennamen sind Case-Sensitive, achten Sie auf die korrekte Schreibweise // der folgende Abschnitt muss für jede Komponente einfügt werden // ist die Komponente vom Typ TButton, dann ... if C.ClassName = 'TButton' then begin // schreibe Wert der Eigenschaft Caption ini.WriteString(FormName, C.Name, (C as TButton).Caption); // Um mehrere Eigenschaften einer Komponente zu erfassen, setzt sich der // Eintrag in der Sprachdatei aus dem Komponentennamen und dem // Eigenschaftsnamen zusammen if (c as TButton).Hint <> '' then ini.WriteString(FormName, C.Name + 'Hint', (C as TButton).Hint); // nächster Durchlauf continue; end; ... // Sollen alle Komponentenklassen in die Sprachdatei geschrieben werden, die // nicht verarbeitet wurden, setzen Sie die Eigenschaft WriteLog von // DFInitLng auf true if WriteLog then ini.WriteString(FormName, C.ClassName, 'LOG: Component not parsed yet'); ...Das Auswerten der Komponenteneigenschaften geht sicherlich auch einfacher. Der Vorteil dieser Variante ist, dass Sie die Ausgabe der Eigenschaften jeder Komponente individuell anpassen können. Routinen, die einfach auf den Typ einer Eigenschaft prüfen (if Komponente hat Eigenschaft Caption) müssen für jeden Typ aufgerufen werden.
with DFInitLng1 do begin FileName := ExtractFilePath(ParamStr(0)) + 'Deutsch.lng'; // Dateiname ItemSeperator := ';'; // Separator für Items in Listen MessageHeader := 'OtherStrings'; // überschrift des Abschnitts für die Strings WriteLog := True; // Log für nicht geparste Komponenten schreiben WriteComponentProperties(DFLngCreate); // Komponenteneigenschaften schreiben WriteInternalStrings(StrLst, 6000, DFLngAppend); // Strings anhängen end;
[frmMain] frmMain=Standardsprachversion btnViewListEntry=Zeige Listeneintrag lbMainItems= btnLanguage=Sprache einstellen TMainMenu=LOG: Component not parsed yet miFile=&Datei miFileSelectLanguage=Sprache einstellen miFileDT=- miFileExit=Beenden miHelp=&Hilfe miHelpInfo=Info ... TDFInitLng=LOG: Component not parsed yet [frmOptions] frmOptions=Sprache auswählen lblSelectLanguage=Wählen Sie eine Sprache aus ... cbLanguageDTItems= btnLanguage=Schließen [OtherStrings] 6000=Sie müssen einen Listeneintrag markieren 6001=Willkommen im Kurs 6002=Information
Download Beispiel - Example3.zip
Zum Einbinden der Sprach-dateien und Integration der Sprachumschaltung sind folgende Schritte notwendig:[frmMain]
|
[frmMain]
|
var MainPath: string; mi: TMenuItem; vSL: TStringList; Idx: Integer; begin // Anwendungsverzeichnis bestimmen MainPath := ExtractFilePath(Application.ExeName); if MainPath[Length(MainPath)] = '\' then Delete(MainPath, Length(MainPath), 1); // Verzeichnis der Sprach-Dateien einstellen Lng.LanguageFilesDirectory := MainPath + '\Sprache'; vSL := TStringList.Create; try // Spachdateien ermitteln Lng.GetLanguages(vSL); vSL.Sort; // jeweils einen Menüpunkt pro Sprache erzeugen // der Text entspricht dem Dateinamen ohne Endung for Idx := 0 to vSL.Count - 1 do begin mi := TMenuItem.Create(self); // Der Menüpunkt benötigt einen Namen, damit die Sprachumschaltung funktioniert. // Es wird je über alle Komponenten iteriert und dabei der Name abgefragt. mi.Name := 'XXXXX' + IntToStr(Idx); mi.Caption := vSL[Idx]; mi.OnClick := OnLangChange; miFileSelectLanguage.Add(mi); end; // mind. 1 Menüpunkt eingefügt, dann Menüpunkt Sprache aktivieren if vSL.Count > 0 then miFileSelectLanguage.Enabled := true; finally vSL.Free; end;In der Formularklasse fügen Sie im private-Bereich die folgende Prozedur ein (betätigen Sie danach Strg+C), um den Prozedurrumpf zu erzeugen.
procedure OnLangChange(Sender: TObject);Setzen Sie die Eigenschaft Enabled des Menüpunkts, unter dem die Sprachen eingegliedert werden auf false. Es kann ja sein, das keine Sprachdateien vorhanden sind.
vSL := TStringList.Create; try Lng.GetLanguages(vSL); vSL.Sort; lbMain.Items.Assign(vSL); finally vSL.Free; end;Im Ereignis OnClick bzw. OnChange können Sie dann die Sprachumschaltung implementieren.
procedure TDFUseLng.GetLanguages(values: TStrings); var SrcRec: TSearchRec; found: boolean; begin found := false; values.Clear; if DirectoryExists(FLanguageFilesDirectory) then begin if FindFirst(FLanguageFilesDirectory + '\*.lng', faAnyFile, SrcRec) = 0 then repeat found := true; values.Add(Copy(SrcRec.Name, 1, Length(SrcRec.Name) - 4)); until FindNext(SrcRec) <> 0; if found then FindClose(SrcRec); end; end;
Lan := Lng.GetActiveLanguage(HKEY_CURRENT_USER, '\Software\FirmenName\Programmname\', 'LanFile'); if Lan <> '' then begin // später zum Initialisieren des Formulars und der Strings der Anwendung // lng.InitForm(landir + '\' + lan, self); // lng.FillMsgArray(landir + '\' + lan, Msgs, 7000); end; function TDFUseLng.GetActiveLanguage(RootKey: dword; Key, value: string): string; var vReg: TRegistry; begin Result := ''; vReg := TRegistry.Create; try vReg.RootKey := RootKey; if vReg.KeyExists(Key) then if vReg.OpenKey(Key, false) then begin if vReg.ValueExists(value) then Result := vReg.ReadString(value); vReg.CloseKey(); end; finally vReg.Free; end; end;
Lan: string; // aktuelle Sprache (Dateiname) LanDir: string; // Sprache-Dateiverzeichnis // Ereignis OnShow Lan := Lng.GetActiveLanguage(HKEY_CURRENT_USER, '\Software\Frischa\Example3\', 'LanFile'); LanDir := MainPath + '\Sprache';
procedure TfrmMain.OnLangChange(Sender: TObject); begin if (Sender as TMenuItem).Caption + '.lng' <> lan then begin lan := (Sender as TMenuItem).Caption + '.lng'; lng.InitForm(landir + '\' + lan, self); lng.FillMsgArray(landir + '\' + lan, StrLst, 6000); lng.SetActiveLanguage(HKEY_CURRENT_USER, '\Software\Frischa\DFCRC\', 'LanFile', lan); end; end;Ereignis OnClick der ListBox
if lbMain.ItemIndex <> -1 then if lbMain.Items[lbMain.ItemIndex] + '.lng' <> lan then begin Lan := lbMain.Items[lbMain.ItemIndex] + '.lng'; Lng.InitForm(Landir + '\' + Lan, self); Lng.FillMsgArray(landir + '\' + lan, StrLst, 6000); Lng.SetActiveLanguage(HKEY_CURRENT_USER, '\Software\Frischa\Example3\', 'LanFile', lan); end;Bei Programmstart wird jetzt die aktuelle Sprache ermmittelt und das Hauptformular initialisiert. Diese Initialisierung müssen Sie bei allen anderen Formularen durchführen (ausser diese werden alle automatisch erzeugt - was aber tunlichst zu vermeiden ist).
LanDir := MainPath + '\Sprache'; Lng.LanguageFilesDirectory := MainPath + '\Sprache'; Lan := Lng.GetActiveLanguage(HKEY_CURRENT_USER, '\Software\Frischa\Example3\', 'LanFile'); if Lan <> '' then begin lng.InitForm(landir + '\' + lan, self); lng.FillMsgArray(landir + '\' + lan, StrLst, 6000); end;Fertig.