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