Skip to content

Instantly share code, notes, and snippets.

@jamuhl
Created January 13, 2016 07:35
Show Gist options
  • Save jamuhl/f8020960024b4d09f721 to your computer and use it in GitHub Desktop.
Save jamuhl/f8020960024b4d09f721 to your computer and use it in GitHub Desktop.
i18next - multiple express apps
var i18next = require('i18next');
var middleware = require('i18next-express-middleware');
var Backend = require('i18next-node-fs-backend');
var express = require('express');
i18next
.use(Backend)
.init({
// use correct configuration options...look up docs!
backend: {
loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json'
}
});
// pseudo virtual hosts
var vhosts = [
{ lng: 'de', port: 3000 },
{ lng: 'en', port: 3001 }
];
vhosts.forEach(function(host) {
var app = express();
// set req.lng to defined lng in vhost
// see for details:
// https://github.com/i18next/i18next-express-middleware/blob/master/src/index.js#L14
app.use(function(req, res, next) {
req.lng = host.lng;
next();
});
// use the middleware to do the magic
// create a fixed t function for req.lng
// no clones needed as they just would do the same (sharing all but lng)
app.use(middleware.handle(i18next));
// in your request handler
app.get('/', function(req, res) {
var lng = req.language;
var lngs = req.languages;
console.log('language is: ', lng, lngs);
res.send(req.t('key1'));
});
app.listen(host.port);
});
// go to localhost:3000 ---> de
// go to localhost:3001 ---> en
// unclear to me...why you need an array of express apps to archive this.
// one app with custom language detector function detection lng from domainName
// would be enought to detect lng and translate content appropriate
@uroblesmellin
Copy link

var i18next = require('i18next');
var middleware = require('i18next-express-middleware');
var Backend = require('i18next-node-fs-backend');
var express = require('express');

i18next
.use(Backend)
.init({
ns : 'translation',
// use correct configuration options...look up docs!
backend: {
loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json'
}
});

// pseudo virtual hosts
var vhosts = [
{ lng: 'de', port: 3000 },
{ lng: 'en', port: 3001 }
];

vhosts.forEach(function(host) {
var app = express();

// set req.lng to defined lng in vhost
// see for details:
// https://github.com/i18next/i18next-express-middleware/blob/master/src/index.js#L14
app.use(function(req, res, next) {
req.lng = host.lng;

// We needed to add this line. This code is executed when the page is loaded, so the 
// i18next object gets updated with the current locale.
i18next.changeLanguage(host.lng);

next();

});

// use the middleware to do the magic
// create a fixed t function for req.lng
// no clones needed as they just would do the same (sharing all but lng)
app.use(middleware.handle(i18next));

// in your request handler
app.get('/', function(req, res) {
var lng = req.language;
var lngs = req.languages;
console.log('language is: ', lng, lngs);

res.send(req.t('key1'));

});

app.listen(host.port);
});

@jamuhl
Copy link
Author

jamuhl commented Jan 14, 2016

that won't work: for understanding please read http://ejohn.org/blog/a-strategy-for-i18n-and-node/ section i18n Logic after the image:

Since all requests to the server are handled by a single set of servers this means that translation logic
cannot be shared – it must be initialized and used on a request-by-request basis. I’ve seen other i18n
solutions, like i18n-node that assume that the server will only ever be serving up pages in a single 
language, and this tends to fail in practice – especially in the shared-state, asynchronous, realm of Node. For example:


Since whenever a request comes in and set the current language, it sets the current language for the
shared i18n object – and given the asynchronous nature of Node it’s possible that other requests may
be happening at the same time, and thus changing the displayed language of another request as a result.

a) always use req.t instead of i18next.t (as language will be changed on each request - resulting in mixed/wrong translations)
b) use http://i18next.com/docs/api/#get-fixed-t and store a fixed t function on your vhost.

@uroblesmellin
Copy link

I have tried the following:

host.myT = i18next.getFixedT(host.lng);

app.use(function(req, res, next) {
req.lng = host.lng; // this line actually does not do anything. If I ask for {{title}} I get "title" (just the key, not the value).

// This is the one that you mention it has the issue. It "works" but it could have concurrency issues.
// If I ask for {{title}} in the page, I get the right value in the right locale.
// i18next.changeLanguage(host.lng);

// host.myT.changeLanguage(host.lng) // DOES NOT WORK. the "t" object does not have a changeLanguage method (that is in the i18next object). Not sure what to do here.

// We need somehow a way to let the current app that the locale is host.lng. As I said, req.lng does not work. I don't see the
// purpose of req.t (or i18next.t(key, options)) We are not trying to localize a "key" here, we are just trying to set the locale to the current app.
//
next();
});

// So the siteApp is using the i18next object that is shared, so I guess the concurrency issue will also apply to this line? Unless the
// middleware somehow knows that siteApp should use an instance.

siteApp.use(middleware.handle(i18next));

If you recall in the initialization of the i18next generic object:

i18next
.use(Backend)
.init({
ns : 'translation',
// use correct configuration options...look up docs!
backend: {

  loadPath: __dirname + '/app/locales/json/{{lng}}/{{ns}}.json'
}

});

The issue that we are having is to let each instance what the value of "lng" is.

Thanks.

@jamuhl
Copy link
Author

jamuhl commented Jan 15, 2016

// We need somehow a way to let the current app that the locale is host.lng. As I said, req.lng does not work. I don't see the
// purpose of req.t (or i18next.t(key, options)) We are not trying to localize a "key" here, we are just trying to set the locale to the current app.

sorry now i don't get it....for what you need i18next if you don't want to translate something?

@uroblesmellin
Copy link

Hi,

Yes, of course we want to translate all handlebar (we are using handlebars as our template library) references in a given page for a given host. We are defining a handlebars helper called "i18n" that actually calls your t method:

var boltHelpers = {
    helpers : {
        // Handlebars helper from i18next
        'i18n' : function(i18n_key) {
            var result = _i18n.t(i18n_key);
            return result;
        }
    }
};

For instance, say in index.hbs we have

<h1>{{i18n "title"}}</h1>

{{i18n "title"}} should become the corresponding value given in the file en_US/translations.json or fr_FR/translations.json, etc.

My point was that we are not translating anything in the loop that is setting up the app for a given host (the code shown above). There we don't need to translate a key to get a value, just to set or initialize the i18next instance. We are using handlebars to then read the translated value when a page gets loaded. That's working fine with:

app.use(function(req, res, next) {
i18next.changeLanguage(host.lng);
next();
});

So {{i18n "title"}} will become something like "Hello World" or "Hallo Welt"

Originally, I tried with:

app.use(function(req, res, next) {
req.lng = host.lng;
next();
});

But that just rendered "title" in the html output, not the corresponding localized value.

I do see the point of your comment of not using i18next.changeLanguage(host.lng); because it could yield concurrency issues. The only reminding issue to solve is how to attach the host.lng (the language of the current host) to the "instance of i18next" for that host.

Thanks for your time!

@jamuhl
Copy link
Author

jamuhl commented Jan 16, 2016

your issue is related to handlebars being a singleton!

see http://stackoverflow.com/questions/17274142/setting-a-handlebars-helper-to-return-a-specific-value-per-request-in-express

// change the order - first the middleware to get req.t
app.use(middleware.handle(i18next));

// set your lng
// add handlebars helper
app.use(function(req, res, next) {
  req.lng = host.lng; 

  // not sure how you assert in handlebars request save functions...as i always used jade on server
  // which does not suffering from the singleton problem 
  // t helper will be dangerous as race conditions on requests can overwrite it
  // see http://stackoverflow.com/questions/17274142/setting-a-handlebars-helper-to-return-a-specific-value-per-request-in-express
  var handlebars = new ???;
  handlebars.registerHelper('t', function(key, options) {
    var result = req.t(key, options.hash);
    return new handlebars.SafeString(result);
  });

  next();
});

@uroblesmellin
Copy link

Thanks Jan.

The problem is that req.t is undefined:

app.use(function(req, res, next) {
req.lng = host.lng;

// not sure how you assert in handlebars request save functions...as i always used jade on server
// which does not suffering from the singleton problem
// t helper will be dangerous as race conditions on requests can overwrite it
// see http://stackoverflow.com/questions/17274142/setting-a-handlebars-helper-to-return-a-specific-value-per-request-in-express
var handlebars = new ???;
handlebars.registerHelper('t', function(key, options) {
var result = req.t(key, options.hash); // <========== req.t is undefined.
return new handlebars.SafeString(result);
});

next();
});

We still need a way to retrieve the i18next instance. Going back to the original question. req.t === undefined.

Thanks

@jamuhl
Copy link
Author

jamuhl commented Jan 22, 2016

req.t === undefined --> gets set by app.use(middleware.handle(i18next));

@uroblesmellin
Copy link

Hi Jan,

I get this error just by placing this line before as you mentioned:

// change the order - first the middleware to get req.t
app.use(middleware.handle(i18next));

If I do that before :
app.use(function(req, res, next) {
// ...
});

I get:

TypeError: Cannot read property 'indexOf' of undefined
at e.a.value (/Users/uroblesmellin/projects/bolt20-frontend/bolt-2dot0-frontend/node_modules/i18next/bin/index.js:1:13811)
at t.u.value (/Users/uroblesmellin/projects/bolt20-frontend/bolt-2dot0-frontend/node_modules/i18next/bin/index.js:2:8308)
at /Users/uroblesmellin/projects/bolt20-frontend/bolt-2dot0-frontend/node_modules/i18next-express-middleware/lib/index.js:82:40
at Layer.handle as handle_request
at trim_prefix (/Users/uroblesmellin/projects/bolt20-frontend/bolt-2dot0-frontend/node_modules/express/lib/router/index.js:312:13)
at /Users/uroblesmellin/projects/bolt20-frontend/bolt-2dot0-frontend/node_modules/express/lib/router/index.js:280:7
at Function.process_params (/Users/uroblesmellin/projects/bolt20-frontend/bolt-2dot0-frontend/node_modules/express/lib/router/index.js:330:12)
at next (/Users/uroblesmellin/projects/bolt20-frontend/bolt-2dot0-frontend/node_modules/express/lib/router/index.js:271:10)
at /Users/uroblesmellin/projects/bolt20-frontend/bolt-2dot0-frontend/server/middlewares/write-header.js:9:9
at Layer.handle as handle_request

If I do it in the original way (without changing the order of

app.use(middleware.handle(i18next));

I don't get that error.

Thanks,
Ulises

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment