Mehrsprachigkeit, Internationalisierung, Übersetzungen
Mehrsprachige, internationale Websites sind für viele Unternehmen ein Muss. Sie helfen, neue Märkte zu erschließen und zielen darauf, den Absatzmarkt zu erweitern, um so Umsatz und damit hoffentlich den Gewinn zu steigern. Bei der Ausgabe fremdsprachlicher Texte ist dabei einiges zu beachten.
In einer derartigen Web-Applikation fand sich folgendes Konstrukt:
= sLang("Auswahl") + " " + sLang("endgültig") + " " + sLang("löschen") + "?';
Der Aufruf der Funktion sLang
ermittelt anhand des angegeben Schlüssels
die in der Datenbank befindliche Übersetzung.
Die Anwendung ist insofern schon gut gemacht, weil die Übersetzungen
in der Datenbank jederzeit leicht geändert werden können, und nicht im Programmcode verdrahtet sind.
Die Pflege der Übersetzungs-Phrasen kann über ein
CMS
erfolgen.
Alternativ können die Phrasen für eine Übersetzung z.B. in eine Excel-Tabelle
oder eine XML-Datenstruktur exportiert werden.
Diese Datei wird vom Übersetzungsbüro übersetzt zurückgeliefert
und ins System zurück importiert.
Im Ergebnis erscheint beim Aufruf des obigen Konstrukts eine Rückfrage an den Anwender
mit folgendem Text:
Auswahl endgültig Löschen?
oder im englischen:
Selection finally Delete?
Hier zeigen sich schon die gravierende Probleme dieser Art von "Übersetzung":
Eine Einzelwort-Übersetzung ist keine ausreichende Lösung,
da hierbei die Groß-/Kleinschreibung nicht korrekt Beachtung finden kann
("Löschen" erscheint großgeschrieben).
Zudem sind in anderen Sprachen die Wortfolgen oft völlig anders.
Geschweige denn, dass es Sprachen gibt, die von rechts nach links geschrieben werden.
Spätestens dort führt dieses Verfahren zu absurden Ergebnissen.
Generell muss daher für jeden individuellen Satz ein eigener Übersetzungs-Eintrag angelegt werden. Es kommt sogar vor, das für denselben deutschen Satz trotzdem zwei verschiedenen Schlüssel angelegt werden müssen, da die Übersetzung in anderen Sprachen je nach Kontext leicht anders lauten muss.
Das zweite Problem ist nicht sofort ersichtlich:
Die Satzzeichen gehören immer mit in den zu übersetzenden Teil.
Im spanischen z. B. wird bei einer Frage ein
umgekehrtes Fragezeichen dem Satz vorangestellt,
im
französischen dagegen muss zwischen dem Satzende und manchen
Satzzeichen ein Leerzeichen stehen,
im
deutschen aber folgt das Satzzeichen direkt dem letzten Buchstaben.
Die Verwendung von Platzhaltern
Wenn Variablen in einer Phrase vorkommen,
legt man diese als Platzhalter in der Übersetzung an.
Der Platzhalter wird vom Programm gegen den konkreten, aktuellen Wert ersetzt.
Keinesfalls sollte man versuchen, die Werte per String-Konkatenation im Programmcode anzuhängen.
Ein Beispiel, wie man es nicht machen sollte:
= lnCount.ToString("n0") + " " + sLang("Elemente löschen") + "?";
Primäres Problem hierbei ist, dass die Position des Platzhalters im zugehörigen Satz
je nach Sprache völlig unterschiedlich sein kann.
Im deutschen steht die Anzahl zu löschender Elemente vorne, im spanischen
aber mitten im Satz.
Beispiel für eine sprachspezifische Verwendung eines Platzhalters:
Key "deleteNumber": deutsch: "{0} Datensätze löschen?" englisch: "Delete {0} items?" spanisch: "¿Extinguir {0} elementos?" französisch: "Effaçage {0} éléments ?" (Leerzeichen   vor dem Fragezeichen)
Ausgabe per string.Format
Die Codierung der Ausgabe sieht hierbei im .NET-Template-Quelltext so aus:
= string.Format(sLang("deleteNumber"), lnCount);
Vorteil einer Lösung mit string.Format
:
- Der Wert kann im Textstring mehrfach benutzt werden.
- Ein Datum oder eine Zahl wird automatisch sprachspezifisch ausgegeben
(d.h. abhängig von der aktuell aktiven
CultureInfo
). - Man kann die Anzeige individuell formatieren durch Änderung des Platzhalters innerhalb der Phrase, ohne jegliche Code-Anpassung.
- Es kann an jeder verwendeten Stelle und jeder Sprachversion anders formatiert ausgegeben werden.
Beispiele für eine Formatierung mittels string.Format
:
alexonasp.net/samples/stringformatting/
Nachteil:
Es wird nur eine Position angegeben,
im Phrasenplatzhalter ist nicht immer leicht zu erkennen,
welche Semantik der Inhalt hat.
Ausgabe mit .Replace
und XML-Platzhaltern
Die Alternative ist eine Ausgabe per .Replace
mit XML-Platzhaltern.
Vorteil:
Platzhalter können aussagekräftige Namen haben, Beispiel:
= sLang("Finally delete <NumberOfItems/> items?")
.Replace("<NumberOfItems/>", lnCount.ToString());
Nachteil:
Es sind verschiedene Schreibweisen für einen XML-Platzhalter möglich,
daher ist bei jeder Nutzung ein XML-Parsing der Phrase notwendig.
Ein einfaches .Replace
reicht nicht aus,
wenn man wirkliche alle denkbaren XML-Schreibweisen zulassen will.
Beispiel verschiedener Schreibweisen:
"<NumberOfItems/>" "<NumberOfItems />" "<NumberOfItems />"
Die Ersetzung kann per XML-DOM-Manipulationen erfolgen,
allerdings ist dieses relativ prozessorintensiv
im Vergleich zu einem einfachem .Replace
-Aufruf.
Bei hochfrequentierten Websites ist daher die Verwendung von Caching-Verfahren anzuraten,
sofern der auszugebende Ausdruck - mindestens einige Zeit - konstant ist.
Schon Caching-Dauern von wenigen Sekunden können hilfreich sein,
um die Last auf einem Server merklich zu mindern.
Kombination
Optimal hinsichtlich der Gestaltungsmöglichkeiten
ist eine Kombination von XML-Platzhaltern mit Formatangaben:
"Finally delete <NumberOfItems format="{0:n}" forceCulture="en-US" />
items?"
Diese Variante vereint die Vorteile der vorgestellten beiden Varianten.
Der notwendige Code ist zwar relativ komplex, aber auch sehr variabel in den
Anpassungsmöglichkeiten.
Im Default wird immer die CultureInfo
des
aktuellen Benutzers oder der aktuellen Sprachversion verwendet.
Ein zusätzliches Attribut kann dazu dienen,
bei der Ausgabe eine bestimmte CultureInfo
zu verwenden,
unabhängig vom aktuellen Default.
Fazit
Mehrsprachigkeit und Internationalisierung
erfordern etwas mehr Aufwand, wenn man es gleich richtig machen möchte.
Bevor man aber nationale Besucher mit Kauderwelsch
und unprofessionellen Übersetzungen abschreckt,
kann es sich rechnen, hier sofort die richtige Lösung zu suchen.
Die Strategie sollte sein: “Think globally, act locally”