Adding multiple languages to a Windows Store app with Javascript and Angular

Leer este artículo en castellano aqui

Last thursday I could attend a debate organized by MSCoders Madrid user group, related with localization of apps in a web enfironment.

After the event I got interested in frontend localization with Javascript. As Karmacracy for Windows is a Javascript app, I decided to do some experimentation on localizing the user interface in the context of a Windows Store app, to be able to distribute it for other markets.

Detecting the system language

For a Windows Store app the first thing to do is to detect the sustem language. This can be archieved with the following piece of code:

var culture = Windows.System.UserProfile.GlobalizationPreferences.languages[0];
var language = culture.substr(0, 2);

The culture is the language that is currently being used for the UI, in culture format. For example for Spanish of Spain would be es-ES, for US English would be en-US, and for British English would be en-GB. To be able to change this order and deploy our app, we must go to the control panel:

Captura de pantalla 2014-09-15 00.29.43

In this example we only need the language, not the culture, so we are going to get a substring of the culture getting en, es, de, etc.

Adding angular-translate

Angular-translate is a module that allows us to define key-value pairs for each language and perform the translation according to the configuration. For installing it, if we don’t use a package manager such as bower, we must download the last package from github and add it to our project.

Once installed, we must add it to the configuration of our app, in the section we define external modules (in this case the module is pascalprechtr.translate).

var karmacracyApp = angular.module('karmacracyApp', [
    'ngRoute',
    'karmacracyControllers',
    'pascalprecht.translate'
]);

Finally, we configure the different languages by using the code we defined previously for getting the OS display language:

karmacracyApp.config(['$translateProvider', function ($translateProvider) {
    $translateProvider.translations('en', {
        'LOGIN': 'Log in',
        'LOGIN_USERNAME': 'User name',
        'LOGIN_PASSWORD': 'Password'
    });

    $translateProvider.translations('es', {
        'LOGIN': 'Inicia sesión',
        'LOGIN_USERNAME': 'Nombre de usuario',
        'LOGIN_PASSWORD': 'Contraseña'
    });

    var culture = Windows.System.UserProfile.GlobalizationPreferences.languages[0];
    var language = culture.substr(0, 2);

     $translateProvider.preferredLanguage(language).fallbackLanguage('en');
}]);

Also, in case the current language is not available, we set English by default

#Usage

For accessing our keys we don’t need any aditional configuration on our controllers, we just need to modify our views so they can access the localized information

<span class="loginMessage">{{ 'LOGIN' | translate }}</span>

If the language is set to English or other language not controlled, the result will be the following:

en

If the language is set to spanish, the result will be the following:

es

As it shows, is an easy way to localize our javascript apps for the Windows Store.

Recap and next steps

Angular-translate provides a simple way of localizing texts in our views and Windows provides the information we need for setting the current language of an app.

As an aditional step, we may try to localize winjs controls such as buttons, which work in a very different way. Angular-translate also supports loading the translations from a json files, an option that may be useful if we want to distribute those files for translation by 3rd parties.

Links

Agregando múltiples idiomas a una aplicación de Windows Store con Javascript y Angular

**Read this article in English here

El pasado jueves tuve la ocasión de asistir a una mesa redonda organizada por el grupo MSCoders relacionada con la localización de aplicaciones en entornos web.

Tras el evento, uno de los temas que más llamó mi atención fue la localización en la parte Javascript, y como tenía una aplicación ya en la Windows Store que usaba ese lenguaje, me pareció interesante intentar localizarla para poder ofrecerla en otros mercados.

Detectando el idioma del sistema

Para una aplicación Windows 8 el primer paso es detectar el idioma del sistema, y eso lo podemos hacer de la siguiente manera:

var culture = Windows.System.UserProfile.GlobalizationPreferences.languages[0];
var language = culture.substr(0, 2);

En este caso tomaremos la lista de lenguajes preferentes, y de esa lista obtendremos el primer elemento. Para poder cambiar el orden de los elementos y probar los diferentes lenguajes, podemos cambiarlo en el panel de control:

Captura de pantalla 2014-09-15 00.29.43

El resultado se muestra en formato cultura, es decir, español de España se mostraría como es-ES, e inglés de EEUU se mostraría como en-US. Para nuestro ejemplo inicial, solamente necesitamos el idioma, con lo cual hacemos un substring y obtenemos en o es respectivamente.

Agregando angular-translate

Angular-translate es un módulo de angular que nos permite definir de manera las claves y los valores para cada lenguaje, y realizar la traducción en función de la configuración. Para instalarlo, si no contamos con gestores de paquetes como bower, es tan sencillo como descargar el último paquete de github y agregarlo a nuestro proyecto.

Una vez instalado, tendremos que agregarlo a la configuración de nuestra app, en el apartado en el que definimos módulos externos en este caso pascalprecht.translate:

var karmacracyApp = angular.module('karmacracyApp', [
    'ngRoute',
    'karmacracyControllers',
    'pascalprecht.translate'
]);

Finalmente, configuramos los diferentes idiomas usando el código que hemos visto anteriormente para seleccionar un idioma acorde con el sistema operativo:

karmacracyApp.config(['$translateProvider', function ($translateProvider) {
    $translateProvider.translations('en', {
        'LOGIN': 'Log in',
        'LOGIN_USERNAME': 'User name',
        'LOGIN_PASSWORD': 'Password'
    });

    $translateProvider.translations('es', {
        'LOGIN': 'Inicia sesión',
        'LOGIN_USERNAME': 'Nombre de usuario',
        'LOGIN_PASSWORD': 'Contraseña'
    });

    var culture = Windows.System.UserProfile.GlobalizationPreferences.languages[0];
    var language = culture.substr(0, 2);

    $translateProvider.preferredLanguage(language).fallbackLanguage('en');
}]);

Además, en caso de que el sistema tenga un idioma no controlado, estableceremos el inglés por defecto.

Uso

Para acceder a nuestras claves no necesitamos realizar ninguna configuración adicional en nuestros controladores, solamente necesitamos modificar nuestras vistas para que accedan a la información localizada:

<span class="loginMessage">{{ 'LOGIN' | translate }}</span>

En caso de que tengamos el idioma establecido a inglés (o a cualquier idioma no controlado), se mostrará el siguiente resultado:

en

En caso de que el idioma esté establecido a español, el resultado será diferente:

es

Como se ve, es una manera sencilla de poder localizar nuestras aplicaciones en javsacript para Windows Store

Conclusiones y pasos adicionales

Angular-translate proporciona una manera sencilla de localizar textos en nuestras vistas, por otra parte, Windows proporciona, a través de su API, la información que necesitamos para saber en qué idioma mostrar una aplicación.

Un paso adicional sería localizar controles winjs como los botones, que funcionan de manera diferente. Por otra parte se podría sacar un fichero de traducciones fuera de la configuración en formato json, útil si queremos distribuir esos ficheros para su procesamiento.

Enlaces adicionales

Datos locales en aplicaciones para Windows Store: Serialización de objetos

En un artículo anterior comentaba una de las posibilidades de almacenamiento de datos en aplicaciones Metro, los diccionarios clave-valor. Esto permitía, recordemos almacenar datos simples de una manera fácil y sencilla, aunque en ocasiones, querremos almacenar datos un poco más complejos. En este artículo se verá cómo almacenar objetos complejos sin tener que recurrir a una base de datos.

Pese a que la API de WinRT incluye todos los componentes necesarios para guardar los datos, hay bibliotecas de clases que nos facilitan mucho la vida. Concretamente esta, Generic Object Storage Helper for WinRT nos permite almacenar objetos y obtenerlos con pocas líneas de código.

Clase a guardar

Para este ejemplo, la clase que almacenaremos será una clase Person. Es requisito que las clases que se almacenen sean serializables, aunque no es necesario que incluyan el atributo. Existen limitaciones, claro, ya que por ejemplo, una ObservableCollection no se puede serializar, una imagen o un objeto de tipo URI tampoco. Además, tiene una ventaja muy interesante, y es que se pueden guardar listas con la misma facilidad, como se verá en el ejemplo.

public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string PictureURL { get; set; }
        public Person Mother { get; set; }
        public Person Father { get; set; }
    }

Una vez obtenido el componente, se puede agregar el componente WinRtUtility.dll a la solución y ya se puede emplear.

Eligiendo el tipo de guardado y almacenando los datos

Podemos guardar los datos en tres espacios diferentes:

  • Local: Los datos se almacenan en el espacio de la aplicación en el dispositivo. Se borran del dispositivo cuando se elimina la aplicación.
  • Roaming: Los datos se almacenan en el espacio del usuario, de esta manera, si se accedes a la app desde otro ordenador donde esté iniciada la sesión con su cuenta Microsoft, se podrán obtener estos mismos datos.
  • Temp: Almacenamiento temporal y volátil, datos que podamos desechar.

Para almacenar los datos, solamente son necesarias las dos líneas siguientes:

var objectStorageHelper = new ObjectStorageHelper&lt;List&gt;(StorageType.Local);
objectStorageHelper.SaveAsync(personObject);

En la primera línea de código se establece el tipo de objeto a guardar así como el espacio en que se hará el guardado. En la segunda es en la que se realiza en guardado del objeto, de manera asíncrona siguiendo con los patrones de WinRT.

Estas líneas se pueden situar en el método OnSuspending, convenientemente situado para que su ejecución sea asíncrona, y situado en el fichero App.xaml.cs:

        private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            //TODO: Save application state and stop any background activity
            var objectStorageHelper = new ObjectStorageHelper&lt;List&gt;(StorageType.Local);
            await objectStorageHelper.SaveAsync(Persons);
            deferral.Complete();
        }

Carga de los datos

La carga de los datos es tan sencilla como el guardado, ya que se emplean dos líneas muy similares a las vistas anteriormente:

var objectStorageHelper = new ObjectStorageHelper&lt;List&gt;(StorageType.Local);
Persons = await objectStorageHelper.LoadAsync();

Estas líneas se pueden situar en el método OnLoad, una vez más, modificado para que se ejecute de manera asíncrona:

        protected async override void OnLaunched(LaunchActivatedEventArgs args)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

                if (args.PreviousExecutionState != ApplicationExecutionState.Suspended)
                {
                    var objectStorageHelper = new ObjectStorageHelper&lt;List&gt;(StorageType.Local);
                    Persons = await objectStorageHelper.LoadAsync();
                }
                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            [...]
            // Ensure the current window is active
            Window.Current.Activate();
        }

Conclusiones

En este artículo se ha visto que existe otra opción para el almacenamiento de estructuras de datos más complejas utilizando poco más que de dos líneas de código situadas en los eventos de carga y guardado de la aplicación.

Más información: