Click here to Skip to main content
Click here to Skip to main content

Localizing your Windows Phone 7 application (enums and finished project)

, 27 Aug 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Give language support to your Windows Phone 7 applications.

Introduction

While creating my first Windows Phone 7 application named InputStudio, I went into the Globalization and Localization issues we all face if we want to support atleast the standard/minimum languages as English, Spanish, Italian, French, and German.

Depending on the audience you want to reach (only the native country or the whole world) with your application, and of course how disciplined you are while coding, or even based on your preferences, you may support not only your native language but also others you want to reach since the very beginning of coding.

This simple article and code brings you one of many ways that exist to localize not only your application enumerated data types but also helps you localize your application when you have already finished your project in your native language (neutral language).

Background

Localization is the customization of applications for a given culture or locale. Globalization is the design and development of applications that support localized user interfaces and regional data for users in multiple cultures.

This article is the third part of the series: Journey of a Windows Phone 7 application (from ideas to deployment).

Main page in English
Enumerated data types in English
english.JPG english-enum-list.JPG
Main page in German
Enumerated data types in German
german.JPG german-enum-list.JPG

Code explanation

The demo application was created from the MVVM Light Windows Phone 7 template within MS Visual Studio and contains two projects: MvvmLocalized and the useful XAMLQueryLib.

Enumerated data types localization

Let's start by adding a resource wrapper class for the string resources binding.

  1      //Wraps access to the strongly typed resource classes so that you can bind
  2      // control properties to resource strings in XAML
  3      public sealed class ResourceWrapper {
  4          private static ApplicationStrings 
                     applicationStrings = new applicationStrings();
  5  
  6          public ApplicationStrings Strings {
  7              get {
  8                  return applicationStrings;
  9              }
 10          }
 11      }

As you can see in the code below, the MainViewModel class is quite simple, just to show you how to create the actual list of enumerations based on the current device (phone) language.

  1      // This class contains properties that the main View can data bind to.
  2      public class MainViewModel : ViewModelBase {
  3          // Initializes a new instance of the MainViewModel class.
  4          public MainViewModel() {
  5              if (IsInDesignMode) {
  6                  // Code runs in Blend --> create design time data.
  7              }
  8              else {
  9                  // Code runs "for real"
 10              }
 11          }
 12  
 13          private List <eDataType> _dataTypes = new List<eDataType>();
 14          public List<eDataType> DataTypes {
 15              get {
 16                  if (_dataTypes.Count.Equals(0))
 17                      _dataTypes = 
                          EnumsGeneric<eDataType>.EnumAsList(eDataType.Undefined);
 18  
 19                  return _dataTypes;
 20              }
 21          }
 22      }

Now add the MainViewModel class from the ViewModelLocator with the key "Locator" as the data context and the ResourcesWrapper class with the key "StringsLocator" for accessing our localized language strings in the application resources.

  1      <application.resources>
  2          <vm:viewmodellocator x:key="Locator" d:isdatasource="True"/>
  3          <local:resourcewrapper x:key="StringsLocator"/>        
  4       </application.resources>

Let's now see the Language attribute so we can decorate our enumerated data types with attributes. This will be the base to localize our enumerated data types. The Ordinal member is used to sort the enums by value.

  1      [Flags]
  2      public enum eLanguage
  3      {
  4          enUS = 1,
  5          frFR = 2,
  6          deDE = 4,
  7          itIT = 8,
  8          esES = 16
  9      }
 10  
 11      [AttributeUsage(AttributeTargets.All)]
 12      public class LanguageAttribute : Attribute
 13      {
 14          public eLanguage Language;
 15          public int Ordinal;
 16  
 17          public LanguageAttribute(eLanguage language, int ordinal)
 18          {
 19              this.Language = language;
 20              this.Ordinal = ordinal;
 21          }
 22      }

It is now time to define our enumerated data types:

  1       public enum eDataType {
  2          [Language(eLanguage.enUS, 0)]
  3          Undefined,
  4          [Language(eLanguage.enUS | eLanguage.frFR | 
                       eLanguage.deDE | eLanguage.itIT, 1)]
  5          Boolean,
  6          [Language(eLanguage.enUS, 2)]
  7          DateTime,
  8          [Language(eLanguage.enUS, 3)]
  9          Decimal,
 10          [Language(eLanguage.enUS | eLanguage.deDE, 4)]
 11          Integer,
 12          [Language(eLanguage.enUS | eLanguage.deDE, 5)]
 13          ListOf,
 14          [Language(eLanguage.enUS | eLanguage.frFR | 
                       eLanguage.deDE | eLanguage.esES, 6)]
 15  
 16  
 17          String,
 18          [Language(eLanguage.enUS | eLanguage.frFR | 
                       eLanguage.itIT | eLanguage.esES, 7)]
 19          Url,
 20          [Language(eLanguage.enUS | eLanguage.frFR | eLanguage.deDE, 8)]
 21          Image,
 22  
 23          [Language(eLanguage.frFR, 0)]
 24          Indéfini,
 25          [Language(eLanguage.frFR, 1)]
 26          DateHeure,
 27          [Language(eLanguage.frFR, 2)]
 28          Décimal,
 29          [Language(eLanguage.frFR, 3)]
 30          Entier,
 31          [Language(eLanguage.frFR, 4)]
 32          Listedes,
 33  
 34          [Language(eLanguage.deDE, 0)]
 35          Undefiniert,
 36          [Language(eLanguage.deDE, 1)]
 37          DatumUhrzeit,
 38          [Language(eLanguage.deDE, 2)]
 39          Dezimal,
 40          [Language(eLanguage.deDE, 3)]
 41          URL,
 42  
 43          [Language(eLanguage.itIT, 0)]
 44          Indefinito,
 45          [Language(eLanguage.itIT, 1)]
 46          DataOra,
 47          [Language(eLanguage.itIT, 2)]
 48          Decimale,
 49          [Language(eLanguage.itIT, 3)]
 50          Intero,
 51          [Language(eLanguage.itIT, 4)]
 52          Elencodi,
 53          [Language(eLanguage.itIT, 5)]
 54          Stringa,
 55          [Language(eLanguage.itIT, 6)]
 56          Immagine,
 57  
 58          [Language(eLanguage.esES, 0)]
 59          Indefinido,
 60          [Language(eLanguage.esES, 1)]
 61          Boleano,
 62          [Language(eLanguage.esES, 2)]
 63          FechaHora,
 64          [Language(eLanguage.esES, 3)]
 65          Decimales,
 66          [Language(eLanguage.esES, 4)]
 67          Entero,
 68          [Language(eLanguage.esES, 5)]
 69          Listade,
 70          [Language(eLanguage.esES, 6)]
 71          Imagen,
 72      }

All of the 'magic' in the enumerated data types creation is because of the Language attribute and the current culture name taken from the current thread.

  1       public class EnumsGeneric<t> {
  2          public static List<t> EnumAsList(T t) {
  3              List<t> items = new List<t>();
  4  
  5              var fields = from field in t.GetType().GetFields()
  6                   let attribute = 
                      field.GetAttributes<languageattribute>(true).FirstOrDefault()
  7                   where attribute != null && 
                      attribute.Language.ToString().StartsWith(
                      Thread.CurrentThread.CurrentCulture.Name.Substring(0,2))
  8                   orderby attribute.Ordinal
  9                   select field;
 10              foreach (FieldInfo fi in fields)
 11                  items.Add((T)fi.GetValue(t));
 12  
 13              return items;
 14          }
 15  }

This is all the necessary code for the enumerated data types creation.

Gathering text strings from all of your pages for localization

This code was created based on the assumption that you have already finished your project and all of your text strings were written in your native (neutral) language. Now we need to localize all of these strings to support at least the standard languages (mentioned above).

I've created a base class for all pages in the project (even the main page). This class will attach the Load event to all of your pages and will handle the saving of your text strings into the IsolatedStorage files for being emailed later to you (when loading the Main Page).

As this project has only one page (Main Page), you will need to quit the application and enter again to email the XML text strings.

Note that the text string files will be generated when you tap the page title.

  1    public class PhoneApplicationPageBase : PhoneApplicationPage {
  2        public PhoneApplicationPageBase()
  3              : base() {
  4              this.Loaded += new RoutedEventHandler(PhoneApplicationPageBase_Loaded);
  5        }
  6  
  7        void PhoneApplicationPageBase_Loaded(object sender, RoutedEventArgs e) {
  8              if (true /*model.IsInBuilStringResourcesMode*/)
  9                  this.ManipulationStarted += new EventHandler
 10                     <manipulationstartedeventargs>(
                        PhoneApplicationPageBase_ManipulationStarted);
 11        }
 12  
 13        void PhoneApplicationPageBase_ManipulationStarted(object sender, 
                                           ManipulationStartedEventArgs e) {
 14              if (e.OriginalSource is TextBlock && 
                       ((TextBlock)e.OriginalSource).RenderSize.Height.Equals(96))
 15                  BuildStringResources(this.GetType(), this);
 16        }
 17  
 18        public MainViewModel model { get { return this.DataContext as MainViewModel; } }
 19  
 20        public static void BuildStringResources(Type type, FrameworkElement container) {
 21          try {
 22            using (IsolatedStorageFile file = 
                 IsolatedStorageFile.GetUserStoreForApplication()) {
 23                string fileName = string.Format("{0}.xml", type.ToString());
 24                if (!file.FileExists(fileName)) {
 25                  StringBuilder sb = new StringBuilder();
 26                  ControlSet textBlocks = XamlQuery.ByType<textblock>(container);
 27                  textBlocks.ForEach(delegate(DependencyObject item) {
 28                    TextBlock tb = item as TextBlock;
 29                    //if(null == tb.GetBindingExpression(TextBlock.TextProperty))
 30                    sb.AppendFormat("<{1}>{0}", 
                       tb.Text, "string");
 31            });
 32  
 33            using (XmlWriter w = XmlWriter.Create(file.OpenFile(fileName, 
                   FileMode.Append, FileAccess.Write),
 34                new XmlWriterSettings() { Indent = true, OmitXmlDeclaration = true })) {
 35                  w.WriteRaw(string.Format("<{1}>{0}", 
                     sb.ToString(), "strings"));
 36                  }
 37  
 38                  MessageBox.Show(ApplicationStrings.ResourcesSaved, 
                                ApplicationStrings.Done, MessageBoxButton.OK);
 39                }
 40            }
 41          }
 42          catch (IsolatedStorageException e) {
 43            MessageBox.Show(e.StackTrace, e.Message, MessageBoxButton.OK);
 44          }
 45        }
 46    }

Once you have received the email with the the XML text strings, you can apply the XSLT transformation to generate the XML text you can add to ApplicationStrings.<language>; at the end of the day, these resource files are just XML files.

See the files in the Xml folder of the solution.

  • ApplicationStrings.xslt is the stylesheet transformation
  • input.xml is the actual file sample you will email yourself
  • output.xml is the transformed XML, you can copy/paste it into your resource files

Points of interest

If you have to do this gathering task for more than two projects, you should create a Visual Studio add-in to automate this process. This add-in should also use the Microsoft Translation API or the Simple Resx Editor to automatically translate your resource strings.

History

This is my first post at CodeProject, so it is an honor to be here, since I have always been following CodeProject articles.

Code update: When using CurrentCulture.Name, use only the first two characters to avoid problems with other subcultures (en, en-US, en-AU, etc.).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

EmitsorGrp
Software Developer (Senior) Emitsor Group
Mexico Mexico
Focused on creating MS Windows 8, Silverlight, Windows Phone 7 & 8 and the Web applications.
 
More than 20 years of experience creating software solutions.
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmvpKanasz Robert21-Sep-12 4:31 
Questionnice PinmemberCIDev16-Sep-11 3:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141220.1 | Last Updated 27 Aug 2011
Article Copyright 2011 by EmitsorGrp
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid