Lokalisierung von WPF Anwendungen: Die Lokalisierungs-API

Sunday, December 27, 2009 5:00:00 PM (W. Europe Standard Time, UTC+01:00)

Dies ist der zweite Teil einer Artikelserie zu Lokalisierung von WPF Anwendungen. Nach einem allgemeinen Überblick über die Lokalisierung in Teil 1 erkläre ich in diesem Artikel, wie man WPF Anwendungen mit der WPF Lokalisierungsapi lokalisieren kann.

Satellitenassemblies

Soll eine Anwendung in mehrere Sprachen übersetzt werden, erzeugt man für jede Sprache eine Satellitenassembly, die alle zu übersetzenden Resourcen (Texte, Bilder usw.) in der jeweiligen Zielsprache enthält.

image

In WPF wird anders als in Windows Forms oder ASP.NET nicht mehr .resx als Resourcenformat für die Lokalisierung verwendet. Stattdessen werden komplette Formulare bzw. Steuerelemente in übersetzter Form in den Satellitenassemblies der jeweiligen Sprachen gespeichert.

Lokalisierung über .baml Dateien

WPF Formulare und Steuerelemente werden über .xaml Dateien beschrieben. Diese werden beim Übersetzen der Anwendung in ein binäres Format (.baml) übersetzt. Die .baml Dateien werden als Resourcen in den Assemblies der Anwendung gespeichert. So wird z.B. aus einer MainWindow.xaml eine MainWindow.baml, die dann z.B. in der Assembly Anwendung.exe als Resource gespeichert wird.

Wenn nun ein Formular bzw. Steuerelement in einer weiteren Sprache angeboten werden soll, muss die dazugehörige .baml Datei übersetzt als Resource in einer Satellitenassembly der Zielsprache vorliegen.

image

Um eine WPF Anwendung zu übersetzen müssen wir also die .baml Dateien der Anwendung übersetzen und in Satellitenassemblies speichern.

Außerdem benötigen alle Elemente in unseren .xaml Dateien, die übersetzt werden sollen, eine (pro Datei) eindeutige Bezeichnung in Form eines x:Uid-Attributs (mehr dazu in einem späteren Artikel).

Leider gibt es dafür keine Unterstützung in Visual Studio oder Blend. Auch in Visual Studio 2010 wird sich daran nichts ändern.

Die WPF Lokalisierungs-API

Stattdessen gibt es eine API, über die lokalisierte BAML Dateien erzeugt werden können. Diese API besteht im Kern aus einer Klasse, der Klasse BamlLocalizer.

BamlLocalizer

image

Die Klasse BamlLocalizer kann aus einer .baml Datei Resourcen extrahieren (ExtractResources-Methode) und später mit übersetzten Resourcen eine lokalisierte .baml Datei erzeugen (UpdateBaml-Methode).

Zunächst benötigen wir Zugriff auf die .baml Datei. Wir haben ja bisher nur die .xaml Dateien, für die BamlLocalizer-Klasse benötigen wir aber die binäre Form der .xaml Dateien im BAML Format.

Für den Zugriff auf die .baml Dateien bieten sich mindestens zwei Wege an: wir könnten die .baml Dateien aus dem fertig übersetzten Assemblies laden, oder die Dateien verwenden, die als “Zwischenprodukt” bei der Übersetzung unserer Anwendung anfallen.

Die erste Variante wird von Microsoft empfohlen und in Beispielen für die Lokalisierungs-API verwendet. Persönlich finde ich diesen Weg jedoch umständlicher, in meinen eigenen Lokalisierungstools verwende ich deshalb die .baml Dateien, die sowieso als “Abfallprodukt” bei der Übersetzung unseres Projekts erzeugt werden (diese Dateien befinden sich normalerweise “irgendwo” im Unterverzeichnis “obj” des WPF-Projekts).

Um eine .baml Datei übersetzen zu können, benötigen wir Zugriff auf die in der .baml Datei enthaltenen Resourcen (also Texte und sonstige Eigenschaften von Objekten, die wir übersetzen bzw. anpassen möchten). Diese Resourcen erhalten wir, indem wir die .baml Datei als Stream an den BamlLocalizer übergeben und die ExtractResources-Methode aufrufen.

image

BAML Resourcen – Das BamlLocalizationDictionary

Von der ExtractResources-Methode erhalten wir ein BamlLocalizationDictionary-Objekt, das alle Resourcen der .baml Datei enthält.

image

Jede Resource besteht aus einem Schlüssel und dazugehörigen Werten.

Der Schlüssel ist vom Typ BamlLocalizableResourceKey. Er besteht aus dem Namen der Eigenschaft, die in dem Eintrag gespeichert ist (PropertyName, z.B. “Text”), der Klasse, zu der diese Eigenschaft gehört (ClassName, z.B. “TextBlock”), dem Namen der Assembly, in der diese Klasse gespeichert ist (AssemblyName) und einer eindeutigen Bezeichnung (Uid). Die Eigenschaften des Schlüssels sind nicht veränderbar.

Zu jedem Schlüssel sind verschiedene Werte in Form eines BamlLocalizableResource-Objekts gespeichert. Die Wichtigste Eigenschaft dieses Objekts ist die Eigenschaft Content, über die der Wert der Eigenschaft gespeichert wird (z.B. “Hello World” als Wert für die Text-Eigenschaft des TextBlock-Objekts). Das ist der Wert, den wir übersetzen möchten. In den weiteren Eigenschaften können wir Informationen für den Übersetzer hinterlegen, der unsere Resourcen für uns übersetzen soll. In der Eigenschaft Comments können wir z.B. Kommentare und Hinweise für den Übersetzer speichern. Modifiable legt fest, ob der Übersetzer überhaupt diese Eigenschaft übersetzen darf. Mit Readable können wir festlegen, ob eine Eigenschaft für den Übersetzer sichtbar ist oder nicht. Über LocalizationCategory werden Resourcen in verschiedene Kategorien einteilen, z.B. “Text”, “Label”, “Button” usw..

Um die Einträge im BamlLocalizationDictionary übersetzen zu können, müssen wir die Inhalte des Dictionaries in irgendeiner Form speichern, z.B. in eine Datei oder in eine Datenbank. Dafür gibt es keine Klassen in der WPF Lokalisierungs-API, wir müssen also eigenen Code schreiben. Es gibt lediglich ein Beispiel dafür in der MSDN (locbaml), zu dem ich später noch etwas schreiben werde.

image

Diese Datei können wir nun selbst übersetzen oder an einen Übersetzer weitergeben, der die Datei für uns übersetzt. Problematisch ist dabei allerdings, dass so sehr leicht ungültige Dateien erstellt werden können (z.B. wenn bei einer Eigenschaft wie “Width” ein Text statt einer Zahl eingegeben wird, ein Teil des Schlüssels geändert wird, oder einfach nur ein Komma falsch gesetzt wrid).

image

Aus der übersetzten Datei müssen wir nun wieder ein BamlLocalizationDictionary erzeugen. Dafür ist wieder eigener Code erforderlich.

image

Wir haben nun ein BamlLocalizationDictionary mit den übersetzten Resourcen. Dieses können wir an die UpdateBaml-Methode der BamlLocalizer-Klasse übergeben, um eine lokalisierte .baml Datei zu erzeugen.

image

Lokalisierte Satellitenassemblies erzeugen

Als Ergebnis erhalten wir eine lokalisierte .baml Datei. Diese müssen wir nun noch in einer Satellitenassembly speichern. Dafür gibt es wieder mindestens zwei Alternativen.

Microsofts locbaml-Beispiel erzeugt die Satellitenassembly selbst komplett neu. Das Beispiel berücksichtigt allerdings keine Resourcen aus anderen Quellen (z.B. .resx), so dass eine so erzeugte Satellitenassembly die Satellitenassembly mit den .resx-Resourcen einfach überschreibt. Außerdem kann locbaml keine signierten Satellitenassemblies erzeugen.

In meinen eigenen Lokalisierungstools nutze ich stattdessen die MSBuild-Skripte aus, die Visual Studio zur Kompilierung von WPF-Projekten verwendet. Die lokaliiserten .baml Dateien werden so von MSBuild genauso behandelt wie evtl. vorahndene andere Resourcen im Projekt (.resx usw.), und von den WPF-MSBuild-Skripten selbst in die Satellitenassemblies geschrieben. Dadurch gibt es keine Probleme mit anderen Resourcen, und die Satellitenassemblies werden “ganz normal” signiert, falls das so in den Projekteigenschaften konfiguriert wurde. Eventuell schreibe ich später dazu einen eigenen Artikel, da ich diesen Ansatz bisher nirgendwo sonst gesehen habe.

Fazit

Lokalisierung über die WPF Lokalisierungs-API ist umständlich. Fertige Tools gibt es nicht, es gibt lediglich ein Beispielprogramm für die API (dazu später mehr in einem eigenen Artikel).

Vorteil dieses Ansatzes ist, dass am Xaml-Code selbst keine Änderungen erforderlich sind, mal abgesehen von zusätzlichen x:Uid Attributen. Somit gibt es keine Probleme bei der Darstellung mit anderen Tools (Blend oder Visual Studio).

In den nächsten Teilen dieser Serie werde ich eventuell noch Alternativen zur WPF Lokalisierungs-API vorstellen. Außerdem möchte ich die Verwendung des locbaml-Beispiels erklären und zeigen, wie sich das locbaml-Beispiel anpassen und erweitern lässt.

Technorati Tags: ,
Kick it on dotnet-kicks.de

Lokalisierung von WPF Anwendungen: Einführung

Tuesday, November 03, 2009 3:31:03 PM (W. Europe Standard Time, UTC+01:00)

Anwendungen sollen oft in verschiedenen Märkten angeboten werden. Dazu müssen sie verschiedene Sprachen unterstützen, d.h. sie müssen lokalisierbar sein. Resourcen wie Texte usw. müssen also sprachabhängig austauschbar sein.

Rückblick: Lokalisierung in Windows Forms

Betrachten wir zunächst die Lokalisierung von Windows Forms Anwendungen: Windows Forms Formulare sind direkt über den Designer lokalisierbar. Dafür gibt es die Eigenschaften Localizable und Language.

image

Stellt man Localizable auf true, kann man über Language die Sprache des Formulars wechseln und das Formular direkt im Designer in der gewählten Sprache bearbeiten.

Zu jeder Sprache, die man auf diese Weise erstellt, erzeugt Visual Studio eine Ressourcendatei, in der die Texte und sonstige Formular-Eigenschaften der Sprache gespeichert werden.

image

Ressourcendateien sind außerdem direkt in Visual Studio bearbeitbar.

image

Für jede Sprache erzeugt Visual Studio eine sogenannte Satellitenassembly mit den Ressourcen der jeweiligen Sprache. Zur Anwendung Beispiel.exe gibt es also dann z.B. Unterordner de-DE\Beispiel.resources.dll und en-US\Beispiel.resources.dll. Abhängig von der aktuell im Thread eingestellten Sprache lädt die Anwendung automatisch die Ressourcen aus der passenden Satellitenassembly.

Lokalisierung in WPF

Man sollte meinen, in WPF als Nachfolgetechnologie zu Windows Forms müsste die Lokalisierung mindestens genauso gut funktionieren, idealerweise sollte alles noch einfacher gehen.

Das ist jedoch nicht so. Eigenschaften wie Localizable und Language fehlen im Designer.

image

Tatsächlich gibt es überhaupt keine eingebauten Lokalisierungswerkzeuge in Visual Studio oder Blend. Stattdessen gibt es eine API, mit der man solche Tools zunächst selbst schreiben soll. Zu dieser API gibt es außerdem ein Beispiel-Tool, locbaml, das aber wirklich nur ein Beispiel ist und deshalb in der Praxis nicht brauchbar ist.

In den nächsten Wochen werde ich über einige Ansätze zur WPF Lokalisierung schreiben.

Technorati Tags: ,
Kick it on dotnet-kicks.de
Page 1 of 1 in the WPF category