2.14.2012

Nomination part 6 - internationalization



Version en espaƱol


This post is part of the series about creating an app with node.js, express for Facebook



Lets add i18 to our app since the beginning so we dont end up with a lof of strings to translate.


To do it so we are going to add a list of strings in a local file that we load to the app, it depends on the language settings in the user browser the file we are going to send.


Lets start adding a new module to our package called "i18next" that will allow us to handle the localization files, our "package.json" will look like this:


{
    "name": "nomi-nation"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.2"
    , "express-messages" : ">= 0.0.2"
    , "facebook-js" : "1.0.1"
    , "jade": ">= 0.0.1"
    , "log" : "1.2.0"
    , "mongoose" : "2.4.8"
    , "vows" : "0.6.1"
    , "zombie" : "0.12.9"
    , "jsdom" : "0.2.10"
    , "i18next" : "0.5.1"
  }
}

And as always lets update the dependencies with "npm install -d"

We need to create a folder called "locals" and inside of it more folders depending on the languages you are going to support in this case will be en-US, es-MX, en, es, those are in their iso name, inside all this sub folders we are going to add a file called "translation.json" same for all folders, the way i18next works is to get the code from the user request for example "en-US", if doesn't find it then will look for "en" if that also doesn't exists it will go to a default route if doesn't exists anywhere then it just returns the string you used as identifier, got it?... our translation file will look like this:

{
    "app": {
        "name": "Nomi-nation",
         "welcome" : "Welcome to $t(app.name)"
    },
    "creator": {
        "name": "MrPix"
    }
}


and in spanish:

{
    "app": {
        "name": "Nomi-nation",
        "welcome" : "Bienvenido a $t(app.name)"
    },
    "creator": {
        "name": "MrPix"
    }
}

Its a simple json file with keys and values, we can do nice things like in line 2, we are adding the app name inside our welcome string so it will get app.name instead of the code.

We have our files so lets code...

In "app.js" around line 8 lets add this line to load the module

i18next = require('i18next')


After our variables we need to initialize our i18next:

i18next.init({
    ns: { namespaces: ['translation'], defaultNs: 'translation'},
    resSetPath: 'locales/__lng__/new.__ns__.json',
    saveMissing: true
});

Here we are telling i18next that the namespace to load is translation which is our file and we add some default, the "resSetPath" helps to set the path dynamically, the "__" indicates a variable and at the end "saveMissing" its just to save all the keys that i18 didn't find in a new file, we shouldn't use this in prod.


In our configure part, lets add a helper for our views so we can use this without problems in jade

app.use(i18next.handle);


And after the configure part, lets initialize the helper:

i18next.registerAppHelper(app)
    .serveClientScript(app)
    .serveDynamicResources(app)
    .serveMissingKeyRoute(app);


Cool we are done in "app.js", lets see the templates. First "layout.jade" will look now like this:

!!! 5
html(lang="en")
  head
    title= t('app.name')
    link(rel='stylesheet', href='/stylesheets/style.css')
  body!= body
  script(src="http://code.jquery.com/jquery-1.7.1.min.js", type="text/javascript")
  script(src='i18next/i18next.js', type='text/javascript')
  script(src='javascripts/script.js', type='text/javascript')

We are updating the title to now get "t('app.name')" which is the key "app" and then the sub-key "name".

Then we are adding "i18next.js" that i18next server this file, this will help us to initialize i18 in the client side.

"index.jade" will look like this:
h1= t('app.name')
p #{t('app.welcome')}
.by by: #{t('creator.name')}

We are loading the strings in different ways, first we are just adding and h1 with the app name and then we are printing as a variable inside p and the div ".by" the welcome msg and the creator string.

OK, lets initialize i18next in the client side, remember that we already have the js file that we add in the layout, lets see the js code to initialize it:

var options = {
    ns: { namespaces: ['translation'], defaultNs: 'translation'},
    useLocalStorage: false,
    resGetPath: 'locales/resources.json?lng=__lng__&ns=__ns__',
    dynamicLoad: true,
    sendMissing: true
};
$.i18n.init(options, function() {
    //TODO: add more text
});


First we are creating an object to add all the options, we pass the namespace like in the server, the flag to use the localstorage in the browser if present, the path to load the files and to send the missing keys, then we just start the functionality with jquery.


To use it everywhere we can just use the function t like this:

$('#appname').text($.t('app.name'));


or

$.t()


or use the jquery function with something like this:

// file with strings
"nav": {
    "home": "home",
    "1": "link1",
    "2": "link2"
}

// the html
<ul class="nav">
    <li class="active"><a href="#" data-i18n="nav.home">home</a></li>
    <li><a href="#" data-i18n="nav.1">link1</a></li>
    <li><a href="#" data-i18n="nav.2">link2</a></li>
</ul>

// traduce all the elements with "data-i18n"
$('.nav').i18n();


We are set, we have our app ready for i18, instead of hardcoding the strings lets use this functionality and we can present our app in several languages


Resources:


By the way, update the testing because we change the title from "Express" to "Nomi-nation"



Fork the code in github:


Greetings 

No comments:

Post a Comment