Dieser Artikel ist Tag #29 der Serie 31 Tage Mango von Jeff Blankenburg.
Der Originalartikel befindet sich hier: Day #29: Globalization.
Dieser Artikel wurde in der Originalserie von Gastautor Matt Eland geschrieben. Bei Twitter kann Matt unter @integerman erreicht werden.
Globalisierung und Lokalisierung
Es herrscht oft etwas Verwirrung bei der Diskussion von Globalisierung und Lokalisierung. Beide haben damit zu tun, Inhalte für alle Regionen der Welt passend zu präsentieren. Der Unterschied ist, dass Globalisierung sich mit der Formatierung von Elementen, also z.B. Zeit, Datum, Währung und Zahlen, beschäftigt, während es bei Lokalisierung um die Anzeige der Inhalte in der Sprache des Anwenders geht. In diesem Artikel werden wir uns mit beiden Aspekten beschäftigen, um Anwendungen zu bauen, die für ein möglichst breites Publikum attraktiv sind.
Wir werden im Laufe dieses Artikels eine Beispielanwendung bauen, die sowohl Globalisierung als auch Lokalisierung unterstützt. Diese Anwendung wird eine Email-Nachricht generieren, mit der Sie Ihrer Verabredung über Ihre Verspätung Bescheid geben können.
Lokalisierung einrichten
Nachdem wir ein neues C# Windows Phone Projekt erstellt haben, müssen wir ein paar Konfigurationseinstellungen zur Unterstützung der Lokalisierung vornehmen.
Eine Neutral Language für das Assembly festlegen
Da wir unsere Anwendung für verschiedene Sprachen anbieten wollen, müssen wir festlegen, welche Sprache die standardmäßig verwendete ist. Hierzu gehen wir in den Eigenschaftsdialog des Projekts und Klicken auf „Assembly Information…“. Dort können wir die Neutral Language des Assemblies festlegen — diese Sprache wird verwendet, wenn für die Sprache des Anwenders keine spezifischen Ressourcen gefunden werden. In unserem Beispiel setzen wir die Neutral Language auf English (United States).
Unterstützte Sprachen angeben
Als nächstes müssen wir dem Projekt mitteilen, welche Sprachen wir unterstützen wollen. Man kann diese Projektinformation derzeit leider nicht direkt im Visual Studio bearbeiten. Wir können aber leicht die Projektdatei editieren. Speichern Sie alles, damit alle Änderungen an der .csproj Datei gesichert sind, bevor Sie sie bearbeiten. Dann gehen Sie in den Ordner des Projekts auf der Festplatte, indem Sie z.B. mit rechts auf das Projekt klicken und „Open folder in Windows Explorer“ wählen. Wählen Sie die .csproj Datei Ihrer Anwendung (nicht mit der Datei .csproj.user verwechseln!) und öffnen Sie diese Datei in einem Texteditor.
Suchen Sie nach dem Element <SupportedCultures></SupportedCultures> und fügen Sie dort die Culture Codes hinzu, die Sie unterstützen möchten. Trennen Sie die einzelnen Codes durch ein Semikolon. Die Neutral Culture des Assemblies kommt nicht in diese Liste. Wir geben hier nur die über die Standardsprache hinausgehenden, zusätzlichen Culture Codes an. Hier finden Sie eine Liste der von den verschiedenen Windows Phone Versionen unterstützten Sprachen: http://msdn.microsoft.com/en-us/library/hh202918(v=VS.92).aspx. In unserem Beispiel werden wir Spanisch, vereinfachtes Chinesisch und Französisch zusätzlich zu unserer Standardsprache English (United States) untestützen. Unser Knoten SupportedCultures sieht damit so aus:
<SupportedCultures>es-ES;zh-CN;fr-FR</SupportedCultures>
Nach den Änderungen speichern Sie die Datei .csproj und wechseln zurück ins Visual Studio. Klicken Sie auf „Reload“ wenn das Visual Studio die Änderung an der Projektdatei feststellt.
Eine Basis-Ressourcendatei erstellen
Jetzt, wo wir unsere Standardsprache und die weiteren unterstützten Sprachen definiert haben, können wir anfangen, unsere sprachspezifischen Ressourcen zu definieren. Wir beginnen, indem wir eine Datei für unsere Standardsprache hinzufügen. Später werden wir weitere Ressourcen für die anderen Sprachen hinzufügen. Damit unsere Anwendung vollständig lokalisiert ist, sollte jeder für den Anwender sichtbare String in diesen Dateien enthalten sein und nicht hartkodiert im XAML oder einer Quellcode-Datei.
Wir fügen eine Ressourcendatei zum Projekt hinzu, welche unsere Strings in der Standardsprache enthalten wird. Rechts-Klicken Sie auf das Projekt und wählen Sie „Add > New Item“. Dort wählen wir Resource File. Diese Datei kann prinzipiell irgendwie heißen — in unserem Beispiel nennen wir sie Strings.
Nach dem Hinzufügen der Datei wechselt das Visual Studio sofort in den Ressourceneditor für diese Ressource. Der Editor besteht aus einer Tabelle mit drei Spalten: Name, Wert und Kommentar. Name ist der eindeutige Schlüssel zur Identifikation. Dieser wird als Name der Eigenschaft zum Zugriff auf die Ressource in der generierten code-behind Datei verwendet. Der Wert ist der sprachspezifische Wert der Ressource. Dies ist also der für den Benutzer sichtbare Text. Das Feld Kommentar werden wir in unserer Anwendung nicht benötigen. Es ist aber praktisch, um die Bedeutung der Ressource und z.B. den Ort der Verwendung zu notieren. Diese Information kann bei der Übersetzung in andere Sprachen sehr wertvoll sein. Oben rechts im Ressourceneditor sehen Sie ein Auswahlfeld zur Steuerung der Sichtbarkeit der Ressourcen. Standardmäßig ist die Sichtbarkeit auf internal eingestellt. Wir ändern dies auf public, damit wir im XAML direkt an die Ressourcen binden können.
Hier ist unser Beispiel, bei dem bereits die nötigen Strings eingetragen wurden und der Access Modifier auf public umgestellt wurde.
Sprachspezifische Ressourcendateien erstellen
Nachdem wir die Ressourcen für die Standardsprache abgeschlossen sind, können wir uns daran machen, weitere Ressourcen für die unterstützten Sprachen anzulegen. Wir fangen mit den spanischen Ressourcen an. Halten Sie Strg gedrückt und klicken und ziehen Sie Strings.resx im Solution Explorer um eine Kopie der Datei dem Projekt hinzuzufügen. Benennen Sie die Datei „Kopie von Strings.resx“ um in „Strings.es-ES.resx“ (es-ES ist der Culture Code für Spanisch). Die neue Datei muss genau so heißen wie die alte plus dem Culture Code für die Sprache, für die die Ressource gilt. Wenn der Dateiname nicht stimmt, wird die Ressource für die vorgesehene Sprache nicht verwendet. Nachdem Sie die Datei umbenannt haben, öffnen Sie die Datei Strings.es-ES.resx und passen Sie die Spalte Value für jeden String an. Eine gute Ausgangsbasis für die Übersetzung ist Bing Translator. Vor einer Veröffentlichung sollten Sie dennoch die Übersetzungsergebnisse von einer der Sprache mächtigen Person prüfen lassen. Die Werte in der Spalte Name müssen zwischen den verschiedenen Ressourcendateien übereinstimmen, damit die passenden Übersetzungen gefunden werden.
Wenn Sie die Übersetzung abgeschlossen haben, wiederholen Sie den Vorgang für jede weitere unterstützte Sprache. Achten Sie bei allen Ressourcendateien darauf, dass die Sichtbarkeit auf public steht, dass die Namen der einzelnen Ressourcen unverändert bleiben und dass die Dateinamen den entsprechenden Culture Code beinhalten. Weiterhin sollten Sie wissen, dass der Ressourceneditor nicht alle Zeichen fremder Sprachen korrekt darstellen kann (wenn Sie diese z.B. vom Bing Translator kopieren). Die Zeichen sollten im Emulator oder auf einem tatsächlichen Gerät aber vernünftig dargestellt werden.
Die nicht-lokalisierte Benutzerschnittstelle bauen
Jetzt, wo wir eine Reihe lokalisierter Strings haben, beginnen wir mit dem Bau der Benutzerschnittstelle, die diese Strings verwenden wird. Unsere Beispielanwendung wird einige Felder, eine Application Bar und einen Standard-Titel haben. Da wir Datumswerte zur Demonstration der Globalisierung verwenden wollen, referenzieren wir das Silerlight Toolkit for Windows Phone und verwenden daraus das TimePicker Control. Da wir das Silverlight Toolkit for Windows Phone in der Serie schon öfter verwendet haben, spare ich mir in diesem Artikel eine Beschreibung des Downloads, der Installation und Referenzierung des Silverlight Toolkits for Windows Phone. Es ist kostenlos verfügbar und online gibt es ausführliche Hilfe.
Unsere nicht-lokalisierte MainPage.xaml sieht so aus:
<phone:PhoneApplicationPage x:Class="PhoneApp1.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="PortraitOrLandscape" Orientation="Portrait" shell:SystemTray.IsVisible="True"> <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsMenuEnabled="False"> <shell:ApplicationBarIconButton IconUri="/icons/appbar.feature.email.rest.png" IsEnabled="True" Text="send" Click="HandleSendClick" /> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar> <!--LayoutRoot is the root grid where all page content is placed--> <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="I'm Running Late" Style="{StaticResource PhoneTextNormalStyle}" /> <TextBlock x:Name="PageTitle" Text="Send Message" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" TextWrapping="Wrap" /> </StackPanel> <ScrollViewer Margin="12,0,12,0" Grid.Row="1"> <StackPanel x:Name="ContentPanel"> <TextBlock TextWrapping="Wrap" Text="To" Style="{StaticResource PhoneTextSubtleStyle}" /> <TextBox x:Name="txtTo" TextWrapping="Wrap" InputScope="EmailUserName" /> <HyperlinkButton Content="Choose a contact" HorizontalContentAlignment="Left" Foreground="{StaticResource PhoneAccentBrush}" Click="HandleChooseContactClick" Margin="{StaticResource PhoneVerticalMargin}" /> <TextBlock TextWrapping="Wrap" Text="Subject" Style="{StaticResource PhoneTextSubtleStyle}" /> <TextBox x:Name="txtSubject" TextWrapping="Wrap" Text="I'm Running Late" InputScope="Text" /> <CheckBox x:Name="checkIncludeReason" Content="Include a reason" /> <TextBox x:Name="txtReason" TextWrapping="Wrap" Text="Traffic" InputScope="Text" IsEnabled="{Binding IsChecked, ElementName=checkIncludeReason}" /> <CheckBox x:Name="checkIncludeETA" Content="I should arrive by" /> <Controls:TimePicker x:Name="timeArrival" IsEnabled="{Binding IsChecked, ElementName=checkIncludeETA}" Margin="0,-12,0,0" /> <CheckBox x:Name="checkIncludeDiagnosticData" Content="Include extra data" /> </StackPanel> </ScrollViewer> </Grid> </phone:PhoneApplicationPage>
Wie Sie leicht sehen, enthält dieses XAML eine Reihe hartkodierter Strings. Genau das wollen wir für unsere lokalisierte Anwendung nicht. Wir müssen die Benutzerschnittstelle dazu bringen, unsere übersetzten Ressourcen-Strings zu verwenden.
Ressourcen-Strings im XAML verwenden
Der Ressorceneditor generiert automatisch Klassen zum Zugriff auf die Ressourcen. Unglücklicherweise können wir diese nicht ohne weiteres im XAML binden, da die generierte Klasse Strings einen internal Konstruktor und statische Eigenschaften hat. Wir bauen also ein kleines Wrapper-Objekt, welches die Ressourcen so zur Verfügung stellt, dass wir daran binden können.
Fügen Sie dem Projekt eine neue C#-Datei hinzu und nennen Sie diese StringProvider.cs. Fügen Sie dort den folgenden Code ein:
namespace PhoneApp1 { public class StringProvider { private readonly Strings _resources = new Strings(); public Strings Resources { get { return _resources; } } } }
Gehen Sie jetzt in die Datei App.xaml und fügen Sie der Anwendung die neu erstellte Ressource hinzu. Hierzu müssen Sie noch ein xmlns Namespace-Alias für den Default-Namespace der Anwendung hinzufügen. Die Ressource wird in der ganzen Anwendung zur Verfügung stehen und uns den einfachen Zugriff auf die übersetzten Strings ermöglichen. Am Ende sollte unsere App.xaml etwa wie folgt aussehen:
<Application x:Class="PhoneApp1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:local="clr-namespace:PhoneApp1"> <!--Application Resources--> <Application.Resources> <local:StringProvider x:Key="Strings" /> </Application.Resources> <Application.ApplicationLifetimeObjects> <!--Required object that handles lifetime events for the application--> <shell:PhoneApplicationService Launching="Application_Launching" Closing="Application_Closing" Activated="Application_Activated" Deactivated="Application_Deactivated"/> </Application.ApplicationLifetimeObjects> </Application>
Mit dem StringProvider als Ressource können wir in der Main Page an dieses Objekt binden. In unserem Fall binden wir an eine Untereigenschaft der Eigenschaft Resource, die dem Namen der Ressource in unserer Ressourcentabelle entspricht. Den anwendungsweit zugreifbaren StringProvider verwenden wir als Binding Source. Aus dem PageTitle TextBlock unseres Beispiels wird daher:
<TextBlock x:Name="PageTitle" Text="{Binding Resources.PageTitleSendMessage, Source={StaticResource Strings}}" TextWrapping="Wrap" />
Sowohl Blend als auch das Visual Studio sollten das Binding erkennen und den String der neutralen Sprache im Designer anzeigen (also den aus der Datei Strings.resx). Hierzu muss das Projekt seit dem Hinzufügen der Ressource erstellt worden sein.
Wenn Sie eine lokalisierte Anwendung bauen, bedenken Sie, dass übersetzte Texte länger sein können als in der neutralen Sprache. Aus diesem Grund sollten Sie z.B. an geeigneten Stellen TextWrapping=„Wrap“ setzen und flexible Layouts, wie z.B. ScrollViewers und StackPanels verwenden. Diese können sich an umgebrochene Texte anpassen.
Eine weitere Möglichkeit zum Umgang mit langen Texten ist das Setzen von SupportedOrientations=„PortraitOrLandscape“ für das Seitenelement bei geeigneten Seiten.
Weiterhin sollten Sie beachten, dass nicht alle Sprachen alle Fonts unterstützen. Um auf Nummer sicher zu gehen, sollten Sie auf spezielle Fonts verzichten und sich auf die eingebauten Styles verlassen. Für mehr Information siehe hier: http://msdn.microsoft.com/en-us/library/hh202920(v=VS.92).aspx.
Lokalisierte Strings im Code-Behind verwenden
Dieser Ansatz funktioniert für die meisten Strings. Die Metro Designrichtlinien sehen aber beispielsweise vor, dass der Anwendungstitel ganz oben auf der Seite in Großbuchstaben geschrieben ist. In unserem String in der Ressourcendatei sind nur die Anfangsbuchstaben groß geschrieben. Wir könnten natürlich einen neue Ressource für den Anwendungstitel definieren oder einen ValueConverter zur Umwandlung in Großbuchstaben schreiben. Stattdessen setzen wir den Titel einfach im Code-behind. Damit können wir demonstrieren, wie man die lokalisierten Ressourcen aus dem Code zugreifen kann. Wir fügen im Konstruktur der MainPage die folgende Zeile ein:
ApplicationTitle.Text = Strings.AppTitle.ToUpper(CultureInfo.CurrentCulture);
Application Bars lokalisieren
Elemente der Application Bar unterstützen zur Zeit kein Binding, da sie eigentlich gar keine Silverlight-Elemente sind. Wir müssen hier also die Texte für die ApplicationBarIconButton Elemente händisch im Code-Behind setzen, indem wir die automatisch generierten Eigenschaften für unsere Ressourcennamen unserer Strings Ressource wie folgt verwenden:
public MainPage() { InitializeComponent(); // Ensure that the app title uses all caps ApplicationTitle.Text = Strings.AppTitle.ToUpper(CultureInfo.CurrentCulture); // Specify the text explicitly on the app bar using our resource string. var button = (ApplicationBarIconButton)ApplicationBar.Buttons[0]; button.Text = Strings.ButtonSend; // By default, we're going to be 15 minutes later than now timeArrival.Value = DateTime.Now.AddMinutes(15); }
Wir haben jetzt eine lokalisierte Anwendung. Ein Test der Anwendung im Emulator mit französischen Spracheinstellungen ergibt das folgende:
Globalisierung unterstützen
Wir haben jetzt eine lokalisierte Anwendung — wenden wir uns der Globalisierung zu. Globalisierung hat damit zu tun, die lokalen Einstellungen des Anwenders zu respektieren. Wir müssen also an geeigneten Stellen IFormatProvider für die Formatierung von Strings angeben. Zum Glück bietet uns .NET mit CultureInfo.CurrentCulture einen einfachen Zugriff auf die aktuellen Einstellungen. Diese können wir verwenden, um die Strings für die Benutzeroberfläche zu formatieren. Bei Vergleichen, Serialisierung oder anderen Operationen, die nichts mit dem Benutzerinterface zu tun haben, sollten Sie die CultureInfo.InvariantCulture verwenden, damit Ihre Anwendung sich bei allen lokalen Einstellungen gleich verhält.
Anders als bei der Lokalisierung müssen wir nicht explizit angeben, welche Spracheinstellungen wir unterstützen wollen. Wir übergeben einfach die aktuellen Einstellungen an das .NET Framework und dieses übernimmt die meiste Arbeit der sprachspezifischen Formatierung. Trotzdem ist es empfehlenswert, an den betreffenden Stellen passende Format Strings zu übergeben und die CultureInfo.CurrentCulture in Form eines IFormatProvider zu übergeben.
Der Code zur Generierung einer Email beispielsweise macht expliziten Gebrauch der CurrentCulture und von Format Strings:
private void HandleSendClick(object sender, EventArgs e) { // Build the E-Mail body from the user's selections var body = new StringBuilder(); body.AppendLine(Strings.EmailHeader); // Include reason if applicable var culture = CultureInfo.CurrentCulture; if (checkIncludeReason.IsChecked == true) { body.AppendLine(); body.AppendLine(string.Format(culture, "{0}: {1}", Strings.EmailReason, txtReason.Text)); } // Include eta if applicable if (checkIncludeETA.IsChecked == true) { body.AppendLine(); // Since we've specified our ValueFormatString for the Time Picker, we can just rely on the ValueString here. body.AppendLine(string.Format(culture, "{0}: {1}", Strings.CheckShouldArriveBy, timeArrival.ValueString)); } // Include extra globalization examples if applicable if (checkIncludeDiagnosticData.IsChecked == true) { body.AppendLine(); // this is the standardized culture name such as en-US or zh-CH body.AppendLine(culture.Name); body.AppendLine(string.Format(culture, "pi: {0}", Math.PI)); body.AppendLine(string.Format(culture, "number: {0}", -1)); body.AppendLine(string.Format(culture, "currency: {0:c}", 4200.00)); body.AppendLine(string.Format(culture, "date: {0:D}", DateTime.Today)); body.AppendLine(string.Format(culture, "time: {0:t}", DateTime.Now)); } // Now that we have our message body, do something with it. What we do depends on what we're running on. if (Microsoft.Devices.Environment.DeviceType == DeviceType.Emulator) { // The emulator doesn't currently support sending E-Mails so we'll just output the text to a message box MessageBox.Show(body.ToString()); } else { // Compose the E-Mail and show it to the user to preview before sending var task = new EmailComposeTask {Subject = txtSubject.Text, To = txtTo.Text, Body = body.ToString()}; task.Show(); } }
Da die Globalisierung unabhängig von der Lokalisierung ist, wird unsere App auch bei Spracheinstellungen, für die wir keine lokalisierten Ressourcen definiert haben, die korrekt globalisierten Werte ausgeben. Im Beispiel unten werden die deutschen Spracheinstellungen verwendet:
Format Strings in XAML
Manchmal wollen Sie vielleicht einen Format String im XAML als Parameter, als ValueConverter oder als Eigenschaft eines eingebauten Controls definieren. In unserem Beispiel setzen wir den Format String unseres TimePicker Controls auf das kurze Zeitformat der aktuellen Spracheinstellungen („t“). Hierzu stellen wir dem Format String ein Paar von Klammern vorne an:
<Controls:TimePicker x:Name="timeArrival" ValueStringFormat="{}{0:t}" />
Man kann das auch im Code-Behind machen. Entweder so:
var info = CultureInfo.CurrentCulture.DateTimeFormat; timeArrival.ValueStringFormat = "{0:" + info.ShortTimePattern + "}";
oder etwas kompakter:
timeArrival.ValueStringFormat = "{0:t}";
Verschiedene Spracheinstellungen testen
Wir haben jetzt eine voll funktionsfähige Anwendung gebaut, die Globalisierung und Lokalisierung unterstützt. Sie fragen sich vielleicht, wie man die verschiedenen Spracheinstellungen am besten testet. In Ihrem Gerät werden Sie möglicherweise die Anzeigesprache gar nicht verändern können. Glücklicherweise bietet der Emulator diese Möglichkeit.
Gehen Sie im Emulator auf die App für Einstellungen und dort in den Bereich Region & Sprache um die Spracheinstellungen zu verändern.
Auf dem Bildschirm Region & Sprache können Sie die Anzeigesprache umstellen, indem Sie die Sprache aus der Auswahlbox wählen und dann oben auf das Link „Tippen Sie hier, um die Änderungen zu übernehmen und das Handy neu zu starten“. Dadurch wird der Emulator neu gebootet und wird mit den gewählten Spracheinstellungen neu gestartet. Es kann vielleicht etwas verwirrend sein, die Einstellungen in einer Ihnen unbekannten Sprache vorzunehmen. Merken Sie sich deshalb das Aussehen und die Anordnung der Eingabefelder, bevor Sie die Sprache umstellen. Sobald der Emulator neu gebootet wurde, können Sie Ihre Anwendung im Emulator starten, um das Verhalten mit den neuen Spracheinstellungen zu testen.
Veröffentlichung
Wenn Sie eine Anwendung veröffentlichen, die verschiedene Sprachen unterstützt, stellen Sie sicher, dass Sie die betreffenden Märkte zur Veröffentlichung Ihrer App auswählen. Sie können natürlich auch gleich „Worldwide Distribution“ wählen.
Zusammenfassung
Sie wissen jetzt, wie Sie eine komplette Anwendung für den globalen Markt entwickeln können.
Um ein Windows Phone Projekt mit dem ganzen Code dieses Artikels herunterzuladen, drücken Sie den Download Code Button:
Morgen wenden wir uns wieder den Daten zu. Wir werden uns anschauen, wie man eine lokale Datenbank in der App verwenden kann.
Bis dahin!
Kommentare sind geschlossen.