Ein Echtzeit-Sprachumschalter zur UI-Lokalisierung in WPF (Teil 2)de

Teil 2 der Serie zur WPF-Lokalisierung: Hier beginnen wir mit klärenden Worten zur Terminologie sowie konzeptuellen Vorüberlegungen. Außerdem wird die Schnittstelle zur Datenhaltung der (textbasierten) Übersetzungen vorgestellt und auf einfachste Weise implementiert.

Der erste Teil dieser Serie beschäftigte sich mit den Anforderungen an einen Echzeit-Sprachumschalter für WPF Anwendungen. Im zweiten Teil wollen wir mit einer zentralen Schnittstelle und den ersten Implementierungen beginnen. Zuerst aber müssen zentrale Begriffe und Konzepte eingeführt werden.

Vorüberlegungen I: Terminologie

Zunächst sollen die wichtigsten Begriffe erläutert werden. Da der Source Code ausschließlich in Englisch geschrieben ist, halte ich mich an direkte deutsche Entsprechungen und ergänze die englischen Begriffe in Klammern.

  • Übersetzt wird nicht bloß in eine Sprache (schwieriger Begriff!), sondern immer in eine mehr oder weniger konkrete Kultur (Culture), welche durch eine Sprachfamilie oder -variante (Dialekt) und (optional) durch einen Ort definiert ist. Beispiel: en-US für amerikanisches Englisch (United States), en-GB für britisches Englisch (United Kingdom).
  • Als Zielkultur (Target Culture) bezeichnen wir das Ziel einer Übersetzung, die aus einer Quellkultur heraus stattfindet.
  • Als aktive Zielkultur (Active Target Culture) bezeichnen wir die Zielkultur, in welche die Benutzeroberfläche zu einer gegebenen Zeit lokalisiert werden soll.
  • Als verfügbare Zielkulturen (Available Target Cultures) bezeichnen wir die Liste aller Kulturen, zu denen Übersetzungen vorliegen, d.h. in welche die Benutzeroberfläche lokalisiert werden kann.
  • Als Index bezeichnen wir ein Wort (aus der Quellkultur), welches in eine Zielkultur übersetzt werden kann. Alle verfügbaren Zielkulturen sollten optimalerweise eine Übersetzung aller indizierten Wörter aufweisen.

Vorüberlegungen II: Konzepte und Aufgaben

Zunächst erarbeiten wir ein Grobkonzept, das uns beim Klassenentwurf helfen wird. Den SOLID-Prinzipien folgend, bilden wir drei zentrale Aufgaben in separaten Klassen ab:

  1. Die Datenhaltung zum Auslesen der Übersetzungen in verschiedenen Zielkulturen.
  2. Die Anbindung an die Benutzeroberfläche via INotifyPropertyChanged. Die Implementierung dieses Interfaces ist notwendig, um eine Bindungsquelle für die Benutzeroberfläche herzustellen. Optimalerweise werden auch noch Helfermethoden zur Verfügung gestellt, welche die UI über einen Wechsel der aktiven Zielsprache informiert.
  3. Die Aufgabe der Lokalisierung i.e.S., welche zu einer gerade ausgewählten Zielkultur (aktive Zielkultur) die Übersetzung eines gewünschten Wortes (Index) anbietet. Hier wird an die Aufgaben in 1 und 2 delegiert.

Aufgabe 1: Die Sprachdaten

Um eine UI zu lokalisieren, benötigen wir strukturierte Übersetzungen in Form von Texten, Grafiken, Icons etc. Beginnen wir also mit der Implementierung der Datenquelle. Diese muss in der Lage sein, uns ihre verfügbaren Zielkulturen sowie deren indizierten Übersetzungen mitzuteilen. Da wir von der konkreten Speicherung der Daten (Datenbank, XML-Datei  o.ä.) abstrahieren wollen, definieren wir ein zentrales Interface:

Neben einer Auflistung aller verfügbaren Zielkulturen (Zeile 3) bietet die Schnittstelle den Zugriff auf die Übersetzungen zu einer bestimmten Zielkultur (Zeile 4). Eine simple Beispiel-Implementierung folgt, welche zwei Begriffe Hello World und Bye ins Deutsche und Spanische übersetzt:

Die Klasse TestTranslations dient uns zunächst als Provisorium, um erste Daten an die UI anbinden zu können, welche wir fest im Code verdrahten (Zeilen 9 bis 25). Wir werden die Klasse später durch eine gescheitere Implementierung ersetzen. Da unser Localizer allein über die obige Schnittstelle ILocalizerTranslations mit der Datenhaltung interagiert, können wir die konkreten Implementierungen beliebig austauschen.

Zeile 4 zeigt ein verschachteltes Dictionary, welches über den indizierten Typ CultureInfo die Übersetzungen einer verfügbaren Zielkultur anbietet. Wir beschränken uns zunächst auf den Datentyp String (einfacher Text), der als Übersetzung angeboten werden soll. Da die Echtzeit-Umschaltung der aktiven Zielsprache in möglichst kurzer Zeit erfolgen soll, ist es wichtig, dass der Zugriff auf die gesuchten Übersetzungen möglichst unmittelbar erfolgt. Zu diesem Zweck verwenden wir den Datentyp Dictionary, welcher dank seines Hashings einen schnellen Zugriff bietet.

Im Konstruktor der Klasse (Zeilen 10 bis 29) bringen wir die verfügbaren Übersetzungen unter, auf welche später per Datenbindung zugegriffen wird. Die erste Aufgabe soll damit erledigt sein.

Aufgabe 2: Die Datenbindung

Die WPF ist bekannt für ihre äußert flexible Datenbindung. Diese wollen wir uns zu Nutze machen. Das heißt, um die UI zu lokalisieren, binden wir diese an ein Objekt, welches die obigen Sprachdaten weiterreicht und die UI informiert, sobald die aktive Zielsprache geändert wurde. Ein solcher Typ muss die Schnittstelle INotifyPropertyChanged implementieren, damit er als Bindungsquelle in Frage kommt.

Um die Datenbindung möglichst bequem zu gestalten, werden wir außerdem mehrere Hilfsmethoden schreiben. Es bietet sich daher an, all diese Teilaufgaben in eine separate Basisklasse auszulagern, von welcher die spezialisierteren Bindungsquellen dann erben können. Eine solche Basisklasse entwickeln wir im Folgenden:

Im Kern enthält diese Klasse die typische WPF Datenbindungslogik: sie nimmt Datenänderungen auf beliebigen Properties via SetPropertyValue<T> entgegen und benachrichtigt registrierte Clients (via RaisePropertyChanged<T>) über das Event PropertyChanged. Zusätzlich wird geprüft, ob Daten tatsächlich geändert wurden, um unnötige Benachrichtigungen an die Clients zu verhindern. Wir werden im folgenden Teil sehen, wie die Klasse konkret verwendet wird.

Der nächste Teil dieser Serie folgt in Kürze. Er wird näher auf die Aufgaben 2 bis 3 eingehen. Hier gehts zum dritten Teil.

Source Code auf GitHub

Der Source Code für die erste Iteration (Tag v0.1) ist auf GitHub verfügbar.

Related Posts

European Citizens’ Initiative Against TTIP and CETA