Dealing with translations in nodejs
Dealing with translations is always a difficult task.
Since gettext was not a option for my needs, I developed a nodejs alternative following this philosophy:
-
Scalable: The translations should be available to any number of machines.
-
Fast: Getting translations from memory.
-
Reliable: Translations should be always available on a central repository/database.
-
Flexible: You should be able to use your favorite storage solution.
dialect http://github.com/masylum/dialect
dialect-http http://github.com/masylum/dialect-http
connect-i18n http://github.com/masylum/connect-i18n
To see how these modules work I recorded a screencast tutorial.
Watch screencast
This is my first screencast, so please, be kind.
Code
app.js
require('colors');
var SERVER = {},
Server = require('mongodb/connection').Server,
Db = require('mongodb/db').Db,
db = new Db('example', new Server('localhost', 27017, {auto_reconnect: true, native_parser: true})),
express = require('express'),
port = 3000,
app = express.createServer(),
dialect = require('dialect').dialect({locales: ['es', 'en'], store: {mongodb: {}}}),
funk = require('funk')();
SERVER.app = app;
SERVER.db = db;
SERVER.dialect = dialect;
SERVER.model = function (mod) {
return require('./models/' + mod)(SERVER);
};
// Configuration
app.configure(function () {
app.use(express.favicon());
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express['static'](__dirname + '/public'));
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({secret: 'whatever'}));
app.use(require('connect-i18n')());
app.use(require('./middlewares/locales')(SERVER, {}));
app.use(app.router);
});
// Helpers
app.helpers(require('./helpers/static')(SERVER, {}));
app.dynamicHelpers(require('./helpers/dynamic')(SERVER, {}));
// Controllers
require('./controllers/public')(SERVER, {});
// Open databases and listen
console.log('Opening the database ...'.grey);
dialect.connect(funk.add(function () {
console.log('+ dialect ready'.blue);
}));
db.open(funk.add(function () {
console.log('+ mongo ready'.blue);
}));
// when mongo and dialect are ready...
funk.parallel(function () {
// sync store and memory cache every 5 secs
dialect.sync({interval: 5 * 1000}, function () {
app.listen(port);
console.log('Listening port '.green + port.toString().yellow);
});
});
helpers/static.js
module.exports = function (SERVER, options) {
return {
truncate: function (str, num) {
var limit = num || 20;
if (str && str.length > limit) {
return str.slice(0, limit) + '...';
} else {
return str;
}
},
dateFormat: require('dateformat'),
'_': SERVER.dialect.get,
dialect: SERVER.dialect
};
};
views/layout.jade
!!!
html
head
title= title || ''
link(rel: 'stylesheet', href: '/css/reset.css' )
link(rel: 'stylesheet', href: '/css/global.css' )
-var css
-if(css)
-each file in css
link(rel: 'stylesheet', href: '/css/' + file + '.css' )
body
nav#header
.wrapper
a#logo(href: '/')= _('Products')
.clearer
.wrapper
#content!= body
ul#locales
-each locale in dialect.config('locales')
li
a(href: '?locale=' + locale)= locale
-var js
-if(js)
script(src: 'http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js')
script(src: '/javascripts/global.js' )
-each file in js
script(src: '/javascripts/' + file + '.js' )
middlewares/locales.js
module.exports = function (SERVER, options) {
return function (req, res, next) {
var url = require('url').parse(req.url, true),
setLocale = function (locale) {
var has_locale = SERVER.dialect.config('locales').indexOf(locale) !== -1;
if (has_locale) {
SERVER.dialect.config('current_locale', locale);
req.session.current_locale = locale;
}
return has_locale;
};
// url
if (url.query && url.query.locale) {
setLocale(url.query.locale);
// session
} else if (req.session.current_locale) {
setLocale(req.session.current_locale);
// browser
} else {
req.locales.some(setLocale);
}
next();
};
};