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 im Extremfall hilfreich sein, 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”