Localization In Ember – How to effectivly globalize and localize an ember application



Localization In Ember – How to effectivly globalize and localize an ember application

1 1


ember-globalization-presentation

Presentation about localization in ember using reveal.js

On Github Kilowhisky / ember-globalization-presentation

Localization In Ember

How to effectivly globalize and localize an ember application

(and web apps in general)

Created by Chris Rice / Github: Kilowhisky

What we are after

Whats the difference?

Globalization is the process of designing and developing applications that function for multiple cultures.

Localization is the process of customizing your application for a given culture and locale.

Source

What things need globalization?

Number parsing Date parsing Regex Currency Non-Latin Characters

123.43 -> 123,43

10/01/2016 -> 2016/10/01

Mitigation is in the API and platform specific

Globalization basics

Here are some quick tips for API communication.
  • Never pass a string formatted date. Always .toJSON()!
  • Never pass a string formatted number.
  • Always use and store in UTF-8!
  • Be flexible in validation. Try not to apply your locale's conventions. (phone numbers,etc..)
  • Be sure to localize error messages!
  • Decide early on if you want to support right-to-left languages or other alphabets.

What needs localization?

Numbers Dates Text Plugins API Responses

Basic Premise of localizing a web app

Detect the users localization automatically Tell the browser we want to localize Tell our API that we want to localize Manipulate our application into awesomeness

What will we need?

  • ember-i18n addon that is the de facto standard localizing an ember application
  • ember-moment addon that helps with date localization
  • ember-cli-moment-shim if you don't like ember-moment you use this instead
  • ember-i18n-csv for exporting translations to csv so you can send them to a translator

Install Plugins

ember install ember-i18n
ember install ember-moment

Setup Plugins

// config/environment.js
module.exports = function (environment)
{
    var ENV = {
        i18n: {
            defaultLocale: 'en-us'
        },
        moment: {
            includeLocales: ['en', 'pt-br', 'es'],
        }
    };
    return ENV;
};

Generate Locale's

ember generate locale en-us
ember generate locale pt-br
ember generate locale es-es

Defining i18n translations

Initializing the locale

// app/instance-initializers/restore-locale.js
let locale = session.get('MyLocalePreference');
let locales = i18n.get('locales');
if (!locale) {
    locale = navigator.language || // Everyone
             navigator.userLanguage || // IE... because
             Config.i18n.defaultLocale;
}
locale = locale.toLowerCase();
for (let i = 0; i < locales.length; i++) {
    let appLocale = locales.objectAt(i);
    if (appLocale.toLowerCase().indexOf(locale) === 0) {
        i18n.set('locale', appLocale);
        moment.locale(appLocale);
        break;
    }
}

Protip

If you are using ember-validations you need to hack i18n support

// app/instance-initializers/restore-locale.js
export function initialize(instance) {
    Ember.I18n = instance.lookup('service:i18n');
}

Issue on the subject

Setup the html lang tag

The HTML lang tag is important in informing the browser that the app is localized

// app/instance-initializers/restore-locale.js
export function initialize(instance) {
    let i18n = instance.lookup('service:i18n');
    let router = instance.lookup('router:main');

    Ember.addObserver(i18n, 'locale', function() {
        Ember.run.schedule('afterRender', this, function() {
            $('html').attr('lang', locale);
            if(router.isActive('application')){
                router.updateTitle();
            }
        });
    });

Protip: If you are using ember-cli-document-title you need to hack i18n support

Tell the world

Inform all servers of our locale preference on each request

// app/instance-initializers/restore-locale.js
export function initialize(instance) {
    let i18n = instance.lookup('service:i18n');

    $.ajaxPrefilter((options, originalOptions, xhr) => {
        xhr.setRequestHeader("Accept-Language", i18n.get('locale'));
    });
}

more info about the accept language header

API response flow

Perform conditional processing based on Accept-language header Have our API inform us of the locale its sending with Content-Language header (optional) Have ember data interpret the response header via ajaxSuccess hook

Handle missing locales

i18n's default behavior for missing locales is to return nothing. Thats annoying to me.

// app/utils/i18n/missing-message.js
var defaultLocale;
export default function(locale, key, data) {
    if(locale === 'en-us'){
        return `Missing Translation for ${key}`;
    }
    if(!defaultLocale){
        defaultLocale = new Locale('en-us', getOwner(this));
    }
    const count = Ember.get(data, 'count');
    const defaults = Ember.makeArray(Ember.get(data, 'default'));
    defaults.unshift(key);
    const template = defaultLocale.getCompiledTemplate(defaults, count);
    return template(data);
}

Grabbed from here

Now on to the fun of i18n

(the incredibly unbearably tedious type of fun)

Rules of the road

  • Try and avoid putting HTML in your templates
  • Build your app with as little text in images as possible
  • When desiging your UI keep in mind that some words may be longer in other languages

General flow

Write a component, route, unit of code in your native language Pull all the language out of the template and into your locale file Replace language with locale keys

Locale files

// app/locales/en-us.js
export default {
    "locales": {
        "es-es": "Spanish",
        "en-us": "English",
        "pt-br": "Brazilian Portuguese"
    },

Locale files are just JSON files.

Keys can be as nested as you want.

Extracting text in templates

This

<strong>Report Application Issue</strong>

Becomes

<strong>{{t 'reportIssues.title'}}</strong>
// app/locales/en-us.js
"reportIssues": {
        "title" : "Report Application Issue"
}

How to handle numbers

This

<p>You have submitted 6 support cases</p>

Becomes

<p>{{t 'reportIssues.numberCases' count=userReportedCaseCount}}</p>
// app/locales/en-us.js
"reportIssues": {
        "numberCases" : "You have submitted {{count}} support cases"
}

You can pass safeStrings, numbers, computedProperties, etc..

Handling html

Use Individual keys (recommended for large html chunks). Put HTML in locale file (ok for minor stuff).
<p>You should <strong>really</strong> think</p>
<p>{{{t 'reportIssues.think'}}}</p>
// app/locales/en-us.js
"reportIssues": {
     "think" : "You should <strong>really</strong> think"
}
Note the tripple {{{. This disables HTML escaping.

Using localization in code

// someComponent.js
i18n: Ember.inject.service(),
actions: {
    error() {
     this.notifications.error(this.get('i18n').t('loadingError'));
    }
}

Using directly as a property

// someComponent.js
import { translationMacro as t } from "ember-i18n";
export default Ember.Component.extend({
    title: t('myTitle')
})
// someComponent.hbs
{{title}}

Observe the locale change

// someComponent.js
i18n: Ember.inject.service(),
landingImgUrl: Ember.computed('i18n.locale', () => {
    this.get('i18n.locale').t('landingImgUrl');
})

Now what?

What do you do with that hundred of keys in en-us.js

Get your files translated!

Export your translations to CSV if you want

ember-i18n-csv to-csv --locales-path=/app/locales/ --csv-path=i18n.csv --only-missing

Once done you can import them back.

ember-i18n-csv to-js --csv-path=i18n.csv --locales-path=/app/ocales --merge

Questions?

Demo Time

1
Localization In Ember How to effectivly globalize and localize an ember application (and web apps in general) Created by Chris Rice / Github: Kilowhisky