| Comments

In my working with Windows Store apps, I’ve become increasingly fond/aware of the advantages for app localization.  There are a lot of resources out there for you to localize your app using a good-better-best approach as well.  I’ve previously written about localizing a Windows Store app using some of these methods and what the WinRT platform supports to make this easier in most cases.

Now that you’ve localized your app, you may be faced with the question of how you might want to respond to language choice changes by the user.  Remember that the Windows Store app model is that it honors the user’s language choice preferences matching that with the available languages the app indicates it supports.  This may not always map to the user’s installed OS language.  For instance I can have an English OS install but prefer German in my apps and set my language preference to: de-DE, en-US.  If a Windows Store app was localized in German, then I would see that version instead of English because my preferences say so, regardless of my OS install.

Language switching problem

Now, I’m not as proficient in German as I’d like to be, so there may be times where I need to flip back to English to understand certain areas of an app.  I can do this easily by going to the languages panel and switching my order of preference:

But when I go back to my app, it is still in German…until I terminate/restart the app.  Let’s say I started the app and it honors my language preference:

Now I go and change back to English and click the button to take me to page 2.  It is still in German even though my language preference list is now: en-US; de-DE.

The XAML framework doesn’t automatically re-evaluate the resource cache in response to these changes.  This is something the developer has to manage and luckily there are some easy steps you can do to make this experience better for your users.

We should point out that this is likely a rare case that a user of your app is constantly switching languages and switching back to your app, but having your app support this is a delighter for your users when it does happen.

Solution: listening to qualifier updates

There are APIs to the rescue here!  When you switch your language preferences, the system actually is aware of it, however the context that your app already had at launched has been cached…and thus it will still be delivering the original context-aware resources.  Good for us is that the APIs can let us know when this happens and we can respond.

Since most apps have only a single Window object and since this change is likely rare, we should manage this call and not put it on every Page load, for example, but rather higher up in the App because it will likely be rare that it happens.  In our activation code path we can listen for the resource context to change

   1: protected override void OnLaunched(LaunchActivatedEventArgs args)
   2: {
   3:     ...
   4:     ResourceManager.Current.DefaultContext.QualifierValues.MapChanged += QualifierValues_MapChanged;
   5:     ...
   6: }

In this snippet we are using the default ResourceContext and listening for when its qualifier map changes.  The qualifier map is the set of context for resources like language, but also scale, phonetics (for Japanese), high contrast, etc.  Once that map changes we can force a refresh of the cache essentially:

   1: void QualifierValues_MapChanged(IObservableMap<string, string> sender, IMapChangedEventArgs<string> @event)
   2: {
   3:     ResourceManager.Current.DefaultContext.Reset();
   4: }

This simple flow basically says “hey, when a qualifier has changed, reset the context map so that I get the new data” and our app in subsequent calls to the resources will get the updated resources.

Here’s a video demonstrating the completed before/after approach.

There is a big caveat to this approach.  The change doesn’t automatically affect your current view.  So your page has rendered and is in German.  If I go and change it to English, then go back to my page…it isn’t automatically in English.  Subsequent view loads (and even going back to this page) will get the new resources, but already-rendered ones do not.  The developer can, of course, implement way more logic to refresh the view, traverse the tree, etc. to manage this, but that experience is up to the app developer to determine what is most appropriate for the app itself.

This is a little subtle but helpful tip in enabling your localized apps to be flexible to these types of changes by users when that happens.

Hope this helps!

| Comments

Being more involved in the engineering process of a product I had the chance to participate in the design of various features instead of just the ones that I’m responsible for delivering.  One of those areas was the way we would enable developers to produce localized applications.  Before this process I have to be honest and as an app developer never really paid much attention to providing localized versions of any app that I wrote.  I had absolutely no good reason for coming to that decision, just never bothered.  In helping to design and understand localization a bit more I can say that it is easy to do this in your app and there shouldn’t be any excuse for any app developer to create their application in a way to make it localized at any given moment.  Note I didn’t say localized immediately, but rather I mean to create your app in a way that would make it easy to provide a localized version in a fairly quick turnaround.

As I do some app development for Windows 8 I wanted to share my thoughts around localization and what it really means to me to provide a ‘world-ready’ application.  The three areas of focus are 1) how the platforms support a localized app, 2) what technical tools you have to automate localization 3) getting culture-correct localization and 4) testing your localized app.

Platform support for localization

The first thing you have to understand is how the platform you are targeting enables support for your app to easily be localized.  This may be in how you organize your content, how you name certain things, and how you write code.  For most platforms there is a pretty predictable method of doing this.  I would argue not all platforms make this extremely intuitive, but also state that inferring how (and what) you want to localize your app isn’t something a platform can predict well for every app.  It is important for the app developer to plan for a global market and fully understand the models before writing a line of code to understand how to make the app localized more easily later in the process.

For Windows 8, a new resource model is introduced for WinRT that is common across the entire platform regardless of the UI framework choice.  The areas you should understand are Windows.ApplicationModel.Resources and Windows.ApplicationModel.Resources.Core.  These two areas are where most Windows 8 developers will spend most of their time with regard to localization of UI strings (and other content).  For most, the GetString() method is probably where you’ll spend your time when using code.  String values are indexed during the build process of your application.  Your strings will either reside in a RESJSON (for HTML/JS) or RESW (for XAML) file.  Both of these are essentially name/value collections for your strings.  An example of the process of naming and managing your string resources can be found here: How to manage string resources.

NOTE: Yes the Consumer Preview version of that documentation shows only Javascript right now, but will be updated.  The naming conventions are the same.

In XAML we created a new way to do XAML UI localization in your markup, by moving some functionality into the platform for you.  Previously in platforms like Silverlight people used techniques like data binding to accomplish the mapping from the RESX-generated class to the UI.  Because the indexing of the string resources changed a bit (i.e., currently no strongly-typed resource class representation), we wanted to have the platform do some more heavy lifting for you.  We already load the index file (resources.pri) automatically during load of the app, so we’re aware of the resources and all the context the app may have (i.e., what locale, screen resolution, etc.).  Because of this we enabled a markup mechanism in XAML for you to provide easy string localization.  Let’s use a simple example of a TextBlock that you might have a static string:

   1: <TextBlock x:Uid="MyTextBlock" FontSize="24.667" Text="Design Placeholder" />

Notice the x:Uid value here.  This now maps back to the key in your RESW file.  Anything with that starting key will have properties merged into it.  So we can have a key in our RESW for “Text” using the key name MyTextBlock.Text with a value of “Hello World” and the runtime will do the replacement for you.  This applies to properties other than text (i.e. width) as well as attached properties.  While we think this is a great new method, it may not work for everyone’s situations and you still have control over retrieving strings using the Windows.ApplicationModel.Resources WinRT APIs.  You can read more about this method with some examples here: QuickStart: Make your Metro style app world ready and an SDK Sample here: Application Resources.

Tools to help with localizing content

Once you have understood what your platform provides and you have taken the effort to prepare your app for easy localization, the next step is to actually use tools/process to perform the localization.  I’ve come across a few tools that I think are helpful for XAML developers who are ‘small shops’ and don’t have an existing process for app localization.

The easiest thing for a developer to do is to use machine translation using automated services.  There are a few tools out there that I think can be helpful for this method.  It is important to note we are talking about machine translation here…I will get to that point later on in the post.

RESX Translator with Bing

Since the RESW format is identical with RESW (restriction is that RESW only supports string values currently), you can use a lot of existing RESX tools.  There is a RESX Translator with Bing project on CodePlex that was done by Microsoft UK Consulting Services.  This allows you to select a file and source language and translate to any language that Microsoft Translator supports. 

RESX Translator with Bing

Since the current project supports (and only loads) .resx files, I had submitted some patch files to quickly make it support the .resw file extension.  The contributor hasn’t accepted these patches, but you can find them on the patches section for the project.

Multilingual App Toolkit

Released with the Visual Studio 11 Beta, the Multilingual App Toolkit from Microsoft provides an integrated method for generating resources for your app.  One of the benefits of this app is that it produces some standards-based files that are used to send to localizers and can be used in other tools.  This includes the TPX and XLIFF file formats.  Once installed the tool is integrated within visual studio and your flow would be something like the following.

First you’d have your app and an initial RESW file containing your default language (in my case en-US) resources.  Once I have that I can right-click on the project and choose Add translation languages and when doing so, launches:

Add translation language dialog

which I can select the languages I would prefer.  Notice the translator icon next to some of them indicating that these could be machine-translated.  Once you select the languages you want, you’ll see a folder within your app that contains the configuration information:

Multilingual resources folder

Now if you open the XLF files you’ll basically see some XML that contains configuration information but initially won’t see any of your string values.  You need to do a build to populate those files.  After building you’ll see the XLF files regenerated with the keys to be localized:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   3:   <file datatype="xml" source-language="en-US" target-language="de-DE" original="LocTesting_Multilingual.prilog.xml" tool-id="MAT" product-name="LocTesting" product-version="Version 1" build-num="1.0.0.0">
   4:     <header>
   5:       <tool tool-id="MAT" tool-name="Multilingual App Toolkit" tool-version="1.0" tool-company="Microsoft" />
   6:     </header>
   7:     <body>
   8:       <group id="Resources">
   9:         <trans-unit id="Resources\SomeTextBlock\Text" translate="yes" xml:space="preserve">
  10:           <source>Good morning!  Have a nice day!</source>
  11:           <target state="new">Good morning!  Have a nice day!</target>
  12:         </trans-unit>
  13:         <trans-unit id="Resources\SomeButton\Content" translate="yes" xml:space="preserve">
  14:           <source>Click to Save</source>
  15:           <target state="new">Click to Save</target>
  16:         </trans-unit>
  17:       </group>
  18:     </body>
  19:   </file>
  20: </xliff>

Now you send those off for translation by right-clicking on the XLF file and choosing Send for translation which will give you a dialog to choose if you want to mail them or save to a folder and which format (TPX/XLIFF).  At this point your translation process can begin.  If you don’t have a translation process where you would normally send the XLIFF files, you can use the MAT Localization Editor that is installed with the toolkit.  Launching this tool and opening your XLIFF file will allow you (or the person doing translations) to edit the files with the correct value.  The MAT editor also has the Microsoft Translator capability as well.  Here’s an example of the above German file in the editor with one value translated by the Microsoft Translator:

MAT Localization Editor

Once you have the edited XLIFF file you import that back into your project.  Notice there are some other workflow-related properties in the XLIFF (i.e., Signed off, ready for review, etc.) that you can enforce.  Right-click on the XLF file in your project and choose Import translation and the XLIFF file that was modified.  Once imported, when I build next the signed-off items will be indexed within my app’s resource lookup (resources.pri) and packaged.  You will not see any new RESW files generated as using this process does the indexing during the build process with the XLF file and a build task.

Amanuens

Another tool that I’ve come across is Amanuens, which is more of a service/tool than anything that integrates into Visual Studio or runs on your machine.  This service acts as a broker to translators.  You basically create a project and then upload files to be localized.  After you login and create a project (specifying the type as Metro as they now support Windows 8 application file types) then you upload your “master” RESW/RESJSON file.  After doing that you can assign a translation and this is the core of their service.  You can assign a file for translation to a person that is a user on the system as you may have a German speaking friend who might help you out.  Or you can assign to an agency and get a quote:

Amanuens agency service

This way you can send it off and wait.  Either way when the translation comes back you just download the file, put it in your project in the appropriate folder and rebuild your app.  Amanuens is a pay service with a free option, but if you have an app and want to manage the workflow of that process, it seems like a pretty cool service.  They also have automatic source control synchronization that you can opt-in for so as your translations come in, they are automatically in your app via a repo sync.

Culture-correct localization

While all the above tools are awesome and fast to get you started, to build a really targeted localized app it is highly recommended you do more than machine translation, or at a minimum verify that machine translation.  Many times, especially for east Asian languages, the machine translation can be grammatically incorrect or sometimes culturally insensitive as most do dictionary-based translation rather than really understanding any context.

I recall a time in high school where a classmate went to Germany for exchange.  Having taken 6 semesters of German she felt pretty confident in her skills.  Little did she know her hoch Deutsch would prove to be a problem.  Upon arrival she went to a café with some friends and proclaimed she was hot.  However, her dictionary translation was bad and she effectively said “I’m horny” which immediately caught the attention of all the men in the room.  So it is key to understand context and culture when translating!

For this services like Amanuens really help out in both the flow and identifying a person to do the translation.  If you don’t know anyone who speaks Swedish, but want to target that market, using a service could help you get more culturally-correct translations for your app over just machine translation.  This, of course, does not come without a cost, but usually it is minimal and is a one-time translation.

One thing I’d love to see happen is somewhat of a co-op of software developers from around the world to offer translation help.  Working at Microsoft I had the chance to get correct translations of my Sudoku app for German, Japanese, Korean and French.  It was helpful to just send off the RESW file and get back the translation from a human who speaks the language and understands technology terms as well.  A service like Amanuens can help facilitate that co-op having a bank of users that are willing to donate their translation services for your project.  I think the struggle is that it is mostly English apps that need the help and hard for an English person to really reciprocate the value.  But if you are willing to help out other software developers perhaps we can start this co-op idea?

Testing your localized app

[!!_Śò ẃĥâт đõ Уôú ďó įƒ ўôú đõи'ť ћãνê тŕáйşłâţêď ţę×т ýęţ áйď ẅàήт тó тęšť ÿòµѓ ãрρ?_!!!!!!!!!!!!!!!!!!!!!!!!]

Could you read that?  Squint a little more.  It is what we call Pseudo Language.  Not a ‘real’ language but something that simulates a lot of different characteristics of localized language challenges.  Using the Multilingual Toolkit you can have all your strings translated to this Pseudo Language (sometimes called “ploc” within the walls of Microsoft) to test your app before your real language translations come in.  Using ploc helps you identify various artifacts that may exist in localizing an app.  Let’s take a simple example from above.  We had a button that said “Click to Save” in English and we translated it to German.  When rendered it would look like this respectively:

 

Notice that the German translation didn’t fit within the Button MaxWidth we had set because the translation of the text was longer than our English version.  Testing this is important for us to realize we need to change the constraint of the XAML sizes to accommodate our languages that we are targeting.

Historically testing localized versions of apps may have been challenging requiring you to install a language pack, try to navigate menus that you may not understand, or relying on others to verify for you.  In Windows 8, this process has become easier using the language list preferences.  The Building Windows 8 blog had a post called “Using the language you want” which talks about this concept in detail. 

NOTE: If using the Pseudo Language option and you want to test, when in the Language panel search for “qps-ploc” and you’ll see it under English (qps-ploc) as an option and then you can really test the boundaries of your app with the ploc strings!

Effectively as a developer I can go into the Language panel, add German, set it as my first language and run my app.  Now I’ll get my German resources first and I can visible verify things that need to be changed without installing any language pack on my machine…and I can quickly change back without any reboots.

Summary

Developing your application to be world-ready doesn’t have to be a huge undertaking and can turn out not to be difficult at all for your app.  While not every app is going to be the same and how you choose to architect your code may dictate how you get your resources, the concepts are still the same and starting with the mindset that your app may be localized at some point is the right thing to do.  Using RESW/RESJSON from the start and understanding the options you have for extracting those resources will make localization later very easy…in fact in some cases as easy as just putting in the updated RESW file and recompiling.

Start smart from the beginning and think global even if you aren’t ready for it just yet.  You never know when your German friend might help out translating for you and then you can instantly expand to that market within a day!

Hope this helps!

| Comments

I recently got a note about a nagging issue in using StringFormat in XAML binding expressions and how it doesn’t honor the current user’s culture settings.  This is true that there is an issue in that it doesn’t in WPF or Silverlight.  If you don’t know what I’m talking about, Silverlight introduced the ability to use StringFormat in data binding expressions (WPF has had this since 3.5 SP1) so you could do some formatting in-line in your binding.  Like this:

   1: <TextBlock Text="{Binding Path=CurrentDate, StringFormat=Current Timestamp is: \{0:G\}}" />

This would result in text that would be formatted directly using your string Formatter without the need for code-behind or any generic ValueConverter.  This is a very helpful feature for formatting UI values as well as in some cases replacing ValueConverters for simple tasks.

The problem is that StringFormat isn’t honoring the user’s culture settings.  Take for example this complete XAML:

   1: <StackPanel x:Name="FooContainer">
   2:  
   3:     <TextBlock x:Name="CultureInfo" />
   4:     <TextBlock x:Name="UICultureInfo" />
   5:  
   6:     <TextBlock Text="{Binding Path=CurrentDate, StringFormat=Current Timestamp is: \{0:G\}}" />
   7:  
   8:     <TextBlock x:Name="CostField" Text="{Binding Path=Cost, StringFormat=Cost is: \{0:c\}}" />
   9:  
  10:     <toolkit:GlobalCalendar  />
  11:  
  12: </StackPanel>

This is being bound to a simple object that exposes two properties for the purposes of demonstration: CurrentDate (DateTime) and Cost (double).  Using my standard US-English settings and regional preferences the output would be:

StringFormat with default culture

Now, let me tell my Silverlight app that I have a different culture information.  I can do this without having to force a language pack installation of sorts and completely change my machine.  Adding the culture/uiculture params to the <object> tag does the trick.  I’ll change it to “de-de” for German.  Here is the new output:

StringFormat with explicit culture

What?!  Even thought the settings recognize a different culture, StringFormat is not doing what I expect.  I would have expected a different date display for German settings (d.m.yyyy) and a different currency display instead of dollars.

Unfortunately this is an issue in StringFormat right now, but there is a simple workaround that if you are creating a localized app you can add to your code that shouldn’t affect your default language settings either.  In my constructor I add this line of code:

   1: this.Language = XmlLanguage.GetLanguage(Thread.CurrentThread.CurrentCulture.Name);

This tells the markup system to use the current culture settings as the UI language.  XmlLanguage is a part of the System.Windows.Markup namespace, so ensure you call that out explicitly or add a using statement.  Now refreshing my German settings sample I get:

StringFormat with explicit culture

as expected.  Changing (or removing the explicit setting of culture in my <object> tag) back to my default culture settings results in my US-English preferences being used and no need for me to change the XAML.

Hope this helps!

| Comments

One of the announcements that happened during the MIX10 conference was the availability of the V2 of the Microsoft Translator API.  This is the engine that powers the translation behind http://www.bing.com/translate and some other Bing-related properties as well.  A lot of research has gone into the engine from Microsoft Research and others.  Language translation isn’t an easy task especially taking into consideration cultural significance of words, etc.  I have heard that the most challenging in machine translation is to Asian languages.  I will admit to not speaking any of them, so I don’t know how well we are performing here – you’ll have to let that team know if they are doing well.

After reading the announcement and working on my translate plugin for the new Seesmic Desktop Platform, I noticed that there was a Speak API.  After reading I saw this literally translates text to a WAV file for platback.  Pretty cool I thought.  I wanted to play around with this in Silverlight so created a simple application to do so:

Microsoft Translator in Silverlight

The Speak translation isn’t available for all the Translator languages (currently 30 languages for text language translation) but does support seven (7) languages: English, German, Spanish, French, Italian, Portuguese, and Russian.  So how is Silverlight talking back to you?

The Translator API comes in 3 flavors: SOAP, HTTP and Ajax.  Now I could have used the SOAP version and used Add Service Reference but I felt for what I was doing this was overkill.  The SOAP API doesn’t return me back super-strongly typed objects, so I saw little value in doing that over the REST-based HTTP methods which I decided to use.  The code is relatively simple.  I first want to translate the text input into the selected language, then pass the translated text to the Speak API and play the results in a MediaElement.

First, we use the Translate method and a WebClient call to accomplish this:

   1: private void TranslateTextToAudio(object sender, RoutedEventArgs e)
   2: {
   3:     if (Languages.SelectedIndex < 0)
   4:     {
   5:         MessageBox.Show("Please select a language first...");
   6:         return;
   7:     }
   8:     WebClient client = new WebClient();
   9:     client.OpenReadCompleted += ((s, args) =>
  10:         {
  11:             if (args.Error == null)
  12:             {
  13:                 DataContractSerializer des = new DataContractSerializer(typeof(string));
  14:                 string responseText = des.ReadObject(args.Result) as string;
  15:                 SpeakIt(responseText);
  16:             }
  17:         });
  18:     client.OpenReadAsync(new Uri(string.Format(TRANSLATE_URI, _appId, HttpUtility.UrlEncode(TextToTranslate.Text), Languages.SelectedValue.ToString(), _currentLang)));
  19: }

Notice the DataContractSerializer use here.  The HTTP API returns serialized objects, so you’ll want to use this method here to deserialize to make it easier.

After we have the translated text, we pass that to the Speak API (notice the call to SpeakIt above) which returns a Stream that is an audio/wav format.   We know that Silverlight’s MediaElement cannot directly play lossless WAV format.  But luckily there is a MediaStreamSource API that enables us to essentially write our own decoders for audio/video.  One of the Silverlight team members, Gilles, created a WAV MediaStreamSource for us to use.  After having that and not worrying about decoding, I could create a new WaveMediaStreamSource with my result Stream and set that as the Source for the MediaElement – here is the resulting SpeakIt() method (TranslatedPlayback is the name of my MediaElement in the application):

   1: private void SpeakIt(string responseText)
   2: {
   3:     WebClient client = new WebClient();
   4:     client.OpenReadCompleted += ((s, args) =>
   5:     {
   6:         if (args.Error == null)
   7:         {
   8:             WaveMediaStreamSource mss = new WaveMediaStreamSource(args.Result);
   9:             TranslatedPlayback.SetSource(mss);
  10:         }
  11:     });
  12:     client.OpenReadAsync(new Uri(string.Format(SPEAK_URI, _appId, responseText, Languages.SelectedValue)));
  13: }

That’s it!  It’s pretty cool (bonus points to you in identifying the obvious pre-filled text being used in my sample).

I’ve almost completed my Seesmic plugin and using the Translator API has made it easier.  I’ve found that a simple wrapper to the HTTP-based methods is going to make things easier for people to use, so I’ve created a Translator Client for Silverlight that I’ll be releasing once I can complete the plugin (waiting on a few things).  This will make it easier for Silverlight developers to quickly consume the API for text and Speech translation.

The code (C#) for the above sample is here: MSTranslatorSilverlightSample.zip.  You will need a Translator Application ID key to run it yourself (put it in the App.xaml resources).  If you just want to see it running really quick, you can view it here: Speak Translator for Silverlight.  You will need Silverlight 4 RC to run the sample.

NOTE: The Translator API actually doesn’t need Silverlight 4, but I just created the app using that base.

Hope this helps!

| Comments

While I was at the Silverlight Atlanta Firestarter event I had a chance to meet some great people.  One of them was Sergey Barskiy.  Sergey was doing a session on deployment and while in the speaker area we were chatting about overall feedback on Silverlight.  One of the things he mentioned was what he thought was a bug in Visual Studio Tools for Silverlight.  It was around RESX files and the modifier setting (Internal, Public, etc.).  More on that later.  Sergey was using RESX files for localization.  While investigating the bug for him, I realized how many people might not know how to do some simple string localization/binding in their Silverlight applications.  It’s relatively simple and I thought I’d outline the steps.  I must admit that I’ve never had to develop a full-fledged internationalized application before, and I applaud those who have and have tackled both the obvious of the language localization challenges as well as cultural and display challenges with various technologies.

The Sample Application

For this experiement we’ll keep it simple and we’re talking about String localization.  We are going to work with a Silverlight 3 application in simple form which will have a TextBlock and a Button control which will have their respective Text and Content settings.  Here’s the English-US (en-US) version of the app:

Sample application image

If we were like most applications we’d be done…but this post will try not to be so US-centric :-).

Step 1: Adding default String resources

As a best practice for String resource localization, in your Silverlight application structure, organize your resource files accordingly.  We’re going to use the RESX file approach and let the framework do most of the work for us.  To that regard in my application I have created a folder called Resources and will be placing my RESX files there.  I’ll first add the default set of data, adding a new RESX file named Strings.resx.

When you add this you’ll notice you get a Strings.resx and a designer (cs or vb) file.  By default the Resource Designer will open and we’ll add two values: WelcomeMessage and ButtonMessage.  Our surface looks like this:

Visual Studio resource designer

Notice the Access Modifier section in the designer.  We need to set this to Public in order to use it in binding we will do later, so set it now.  We save this and step one is done.  Let’s test it out and use it.

Step 2: Binding our RESX files to our XAML

Now that we have a Strings.resx file (which is marked as Embedded Resource by default) in our Resources folder, we can use it as a resource in our XAML.  We need to do a few things to enable this.  First, we need to add an XML namespace to our XAML page.  I’ve chosen local for mine, but choose whatever.  We will have this namespace point to our Resources namespace in our application.  Mine looks something like this:

   1: xmlns:local="clr-namespace:StringLocalization.Resources"

Now I can use that assembly classes in my XAML.  I’ll add the Strings class to my UserControl’s Resources section giving it a key of LocStrings like this:

   1: <UserControl.Resources>
   2:     <local:Strings x:Key="LocStrings" />
   3: </UserControl.Resources>

And now I have a XAML resource I can bind to.  Let’s do that.

Step 3: Binding to the resx file data

My XAML has a TextBlock and a Button I want to bind to the string values.  Because I have a XAML resource this is simple and I just create a binding using the the XAML binding syntax to that resource.  here’s what my TextBlock and Button look like (relevant portions):

   1: <TextBlock Text="{Binding Source={StaticResource LocStrings}, Path=WelcomeMessage}" />
   2: <Button Content="{Binding Source={StaticResource LocStrings}, Path=ButtonMessage}" />

Notice that I’m using a Path that points to the String name in the RESX file.  Let’s run the application now and we should see what we’re looking for right?

WRONG.  Here’s the bug that Sergey was talking about.  It turns out that there does seem to be a bug in Visual Studio with regard to the modifier, and more specifically the PublicResXFileCodeGenerator custom tool that is used to generate the code.  It turns everything in the resource to public except the constructor.  Not having the constructor public (it is still marked internal) is what is causing our problem. 

NOTE: This is not a bug in Silverlight or in the Silverlight tools, but more widely in Visual Studio.  The same thing reproduces on any VS project type.  It’s been noted and is being tracked to address.  See this topic:

What we have to do as a workaround is to go into the Strings.designer.cs file manually and change the constructor from internal to public.  I will note that each time you open up the designer for that main resource file that it may get reset to internal, so remember that. 

After changing to public you can run the application and get the desired output.  There is actually a way to still use the resources and keep the constructor internal, but it involves using another class you have to create and instantiate…more on that later.

Step 4: Adding additional localized resources

Now that we have our base working, we simply need to provide the localized resources for our application.  I’m choosing to localize in Spanish (es-es), French (fr-FR), German (de-DE) and Japanese (ja-JP). 

I solicited some help on Twitter to get some “human” translation to double-check my translated text with Windows Live Translator.  To my surprise, Windows Live Translator actually did a great job.  I think with short sentences it is fine, but longer conversational text may lose context.

Thanks to: Ken Azuma, Othmane Rahmouni, Talya, Misael Ntirushwa, Hannes Preishuber, and others who jumped in to offer help for my little sample.

To do this we simply add more resource files into our project following a specific naming scheme.  Since we have Strings.resx when we add additional languages we’ll add them as Strings.<locale>.resx.  So adding German would be Strings.de-DE.resx and so forth.  It really is best to use the xx-xx locale settings versus just the two character (i.e., de) ones.  Note when you add these files to the project that your modifier section in the resource designer should say No Code Generation automatically.  If it doesn’t, choose that option.  We only need the code for our default language choice.  When I’m done my structure looks like this:

Project structure with localized string resx files

Obviously the contents of the file contains the localized string information.  Note that the string parameter names still have to match.  So even though I’m localizing the contents of WelcomeMessage, in the German resource the parameter is WelcomeMessage and not Wilkommen or something like that.

UPDATE: Forgot one critical step – oops.  You have to manually edit your **proj (csproj/vbproj) file for your Silverlight project to add the locales in the SupportedCultures node of the proj file.  This is a manual step (and sucks), but don’t forget it or nothing will work as you suspect.

Step 5: Testing it out.

Because we used our declarative binding in Steps 2-3, we don’t have to change our code.  We should test it out to make sure it works though.  There are two good ways I have found to test this out.  First, the Silverlight plugin supports forcing a UICulture and we could do it that way.  Let’s test German.  In our plugin instantiation on our hosting page we’ll add these two parameters to the <object> tag (relevant portions):

   1: <object ...>
   2:     ...
   3:     <param name="culture" value="de-de" />
   4:     <param name="uiculture" value="de-de" />
   5:     ...
   6: </object>

That tells the plugin to load with those cultures set.  You can change them without recompiling your application and the language will change.

A second option would be to actually change the display language of your Windows environment.  For some this may be a little frightening as your screen suddenly may change to a language you don’t understand natively.  I recommend if you go this route to keep a translation dictionary handy find your way back (you have to logoff/logon to change a display language)!

Either way when you run the application using either of these methods you should get what you expect.  Here’s my German output:

German localized sample application

Obviously it is easier to test using the <param> approach (changing display languages in Windows requires a logoff), but ultimately I recommend doing actual OS display language for verification.  If a user is on a language culture that you have not localized, it will use the default values provided in our initial Strings.resx file.

Another option for testing is that you can actually change the culture and UICulture values using the Thread namespace APIs in Silverlight.  Keep in mind though changing these values on the CurrentThread does *not* reload the default resource, so you’d have to do some additional code to get the resource to load using that new culture setting.

Caveats and cultural differences

One thing to note is that while your application may now have an easy ability to display localized string data with simple bindings, it may not always be appropriate.  Take for instance Japanese language translation (I’ll assume it is roughly translated correctly, but for the sake of this discussion this serves a purpose) of “Click here” for the button.  In English it fits fine in our lovely world of fixed width button sizes.  But look at the Japanese translation of the text:

ここをクリックしてください。

And here’s how it looks in the button:

Japanese localized sample output

Notice that we don’t see all of the characters?!  These are things that you’ll have to understand when things seem simple enough.  Sometimes translated strings will be longer/shorter than your intended design.  Designing around a localized approach will have to consider these in advance.  In fact, for some languages you may have an alternate placement of controls even to accommodate the culture.

As I noted that I used machine translation for my sample here, but I do want to stress that I think respecting cultural differences is important in customer facing applications.  Using something like Windows Live Translator seems simple enough and might work in simple instances, but I would recommend hiring true localization resources/people to help you differentiate the subtle differences in languages.

Public modifier workaround and dynamically setting the culture

As I mentioned above there is a workaround to the internal/public modifier bug.  It is easy enough to change of course, but you may want to look at this approach as well…and in some implementations this may fit within your model better.  The idea is to provide your own class implementation access to the Strings resource.  Here’s an example of what my custom class implementation might look like:

   1: public class CustomResources : INotifyPropertyChanged
   2: {
   3:     private static StringLocalization.Resources.Strings _strings = new StringLocalization.Resources.Strings();
   4:     public StringLocalization.Resources.Strings LocalizedStrings
   5:     {
   6:         get { return _strings; }
   7:         set { OnPropertyChanged("LocalizedStrings"); }
   8:     }
   9:  
  10:     #region INotifyPropertyChanged Members
  11:  
  12:     public event PropertyChangedEventHandler PropertyChanged;
  13:  
  14:     private void OnPropertyChanged(string propertyName)
  15:     {
  16:         if (PropertyChanged != null)
  17:         {
  18:             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  19:         }
  20:     }
  21:     #endregion
  22: }

And then my resource section would look like this:

   1: <UserControl.Resources>
   2:     <localCustom:CustomResources x:Key="CustomLocStrings" />
   3: </UserControl.Resources>

And my binding would look like:

   1: <TextBlock Text="{Binding Source={StaticResource CustomLocStrings}, Path=LocalizedStrings.WelcomeMessage}" />
   2: <Button Content="{Binding Source={StaticResource CustomLocStrings}, Path=LocalizedStrings.ButtonMessage}" />

Now that I have this in place like this I get around the internal modifier issue because I’m actually binding to an instance of my own class (which has a static instance to the Strings resource class).  Using this method I could dynamically change the culture on the fly as well by resetting the culture settings in the thread and resetting the resource.

   1: ComboBoxItem item = ChangeLog.SelectedItem as ComboBoxItem;
   2: Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(item.Content.ToString());
   3: Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(item.Content.ToString());
   4: ((CustomResources)this.Resources["CustomLocStrings"]).LocalizedStrings = new StringLocalization.Resources.Strings();

A bit of a workaround for most scenarios I think.  I think probably remembering to change the modifier may work best for most cases, but this custom instance class might actually fit better into some model implementations.

Summary

Hopefully you can see that for simple string resources the technical implementation is fairly simple.  The real challenge is to you, the developer, to ensure the cultural integrity of the message is being displayed appropriately.  Localization is not an overall easy task and I’m simplifying it to simple strings here.  As I stated above, I applaud those who have successfully implemented fully localized applications.  It can be as simple as a button label or as complex as alternate screen layouts for different cultures!

You can download the code for this post here: StringLocalization.zip

Hope this helps!


This work is licensed under a Creative Commons Attribution By license.