mashpie / i18n-node

Lightweight simple translation module for node.js / express.js with dynamic json storage. Uses common __('...') syntax in app and templates.
MIT License
3.09k stars 420 forks source link

Introduce a way to toggle header locale detection #216

Open fourpixels opened 8 years ago

fourpixels commented 8 years ago

Hi there, I've recently started using your module and it just worked out of the box, which is amazing!

I just have one basic problem which drives me crazy. The init function uses headers as last resort to find user's locale. Which, used alone, is not quite safe and somehow makes defaultLocale meaningless.

This is my setup:

i18n.configure({
    locales: ['bg', 'en'],
    defaultLocale: 'bg',
    directory: path.join(config.root, '/locales'),
    queryParameter: 'lang',
    autoReload: false, // watch for changes in json files to reload locale on updates - defaults to false
    updateFiles: true // whether to write new locale information to disk - defaults to true
});

And let's say I use this schema for switching languages:

example.com // I hope it's using the default locale
example.com?lang=en // should be using English

But then I understand that my browser sends Accept-Language:en-US,en;q=0.8,bg;q=0.6,fr;q=0.4, so my defaultLocale is just skipped and I get the page in English, despite NOT providing a language parameter.

I think there should be a way to disable header check, just like there is a way to disable both query and cookie lookup (by not providing parameters for them). Relying on user settings in order to get specific language alone seems like very dangerous game, and it's a highly recommended no-no.

I think there could be a parameter if headers check should be done, set to true by default ;) For now, I'm shamelessly short-term fixing my issue with:

app.use(function(req, res, next) {
    delete req.headers['accept-language'];
    next();
});
mashpie commented 8 years ago

ok, got it - adding skipHeaderChecks and/or silently ignoring their absence won't be a big deal... thank you for spotting

fourpixels commented 8 years ago

The problem here is that my browser always sends Accept-Language:en-US,en;q=0.8,bg;q=0.6,fr;q=0.4 no matter what. And therefore, if you browse through the code you'll see that it always ends in finding that language in the available languages and sets it as the language to use.

By I am setting the defaultLocale to something else! Which means that if I do not send query param nor cookie, I should be using the default one, right? But it's not happening, because of browser headers, which I cannot control.

What bothers me is that those headers are sent because of OS configuration like region or language, which I cannot control. And I think they are sent almost always, no matter what. Which makes defaultLocale useless if you have that accept-language in your list of languages.

My use case is that I want the default language to be Bulgarian, but still support English. But most of the users in that country use OS in English, and therefore end up seeing the site in English, despite not providing query param for that. The only option is to always send query parameter, even for the default values, which I dislike :)

Hope that makes sense now :)

mashpie commented 8 years ago

People with Simulator setups used to use a Cookie to persist language setting... Should help until next Release.

Von meinem iPhone gesendet

Am 09.04.2016 um 08:05 schrieb fourpixels notifications@github.com:

The problem here is that my browser always sends Accept-Language:en-US,en;q=0.8,bg;q=0.6,fr;q=0.4 no matter what. And therefore, if you browse through the code you'll see that it always ends in finding that language in the available languages and sets it as the language to use.

By I am setting the defaultLocale to something else! Which means that if I do not send query param nor cookie, I should be using the default one, right? But it's not happening, because of browser headers, which I cannot control.

What bothers me is that those headers are sent because of OS configuration like region or language, which I cannot control. And I think they are sent almost always, no matter what. Which makes defaultLocale useless if you have that accept-language in your list of languages.

My use case is that I want the default language to be Bulgarian, but still support English. But most of the users in that country use OS in English, and therefore end up seeing the site in English, despite not providing query param for that. The only option is to always send query parameter, even for the default values, which I dislike :)

Hope that makes sense now :)

— You are receiving this because you commented. Reply to this email directly or view it on GitHub

fourpixels commented 8 years ago

Oh no worries! :) As I said I've already hacked it and it's now working like a charm. It's not that bad as working only with headers is not suggested by the online gurus anyways.

Providing different content solely based on cookie is really bad fore SEO, so I will just wait for the next release :) Good luck!

mashpie commented 8 years ago

apropos seo... consider hacking some middleware and switch with setLocale based on an url segment, like so:

router.all('/:lang/*', function(req, res, next){
  i18n.setLocale(req, req.params.lang);
  next();
});

see http://expressjs.com/en/4x/api.html#router

fourpixels commented 8 years ago

Great idea! I was thinking of something similar, but not quite. I'll just have to check if there's that specific segment or not. I want the default language to have clear urls, and only the other ones to have additional segments/parameters or whatever. I can optionally switch to subdomains for each language.

The only things that bothers me is that there might be something in the url similar to any of the languages (this is only hypothetical but still), but I can live with that for now.

Anyway, thanks for the idea - sounds really great!

DJviolin commented 8 years ago

@mashpie For some reason rotuter.all not working for me, only this:

app.all('/:lang/*', function(req, res, next){
  //i18n.setLocale(req, req.params.lang);
  i18n.setLocale([req, res.locals], req.params.lang);
  next();
});

But the problem is that everything is translated, except the {{{body}}} (I use express-handlebars). I've got 404 error.

I'm placing this in my app.js, before every other route. I call the routes with:

app.use('/', require('./routes/index'));
app.use('/blog', require('./routes/blog'));
app.use('/category', require('./routes/blog-category'));
app.use('/tag', require('./routes/blog-tag'));
app.use('/contact', require('./routes/contact'));
mashpie commented 8 years ago

well that totally depends on your express setup, call your express-router router, app, server or pony ...

your 404 will most probably result from unproper nesting of routes. Please refer to http://expressjs.com/en/guide/routing.html esp the very last section:

var birds = require('./birds');
...
app.use('/birds', birds);

The app will now be able to handle requests to /birds and /birds/about, as well as call the timeLog middleware function that is specific to the route.

So I Guess you need to wrap all of your routes into a

app.all('/:lang/*', function(req, res, next){...});

app.use('/:lang', require('./routes/all'))

all.js containing all deeper urls, like

app.use('/', require('./routes/index'));
app.use('/blog', require('./routes/blog'));
app.use('/category', require('./routes/blog-category'));
app.use('/tag', require('./routes/blog-tag'));
app.use('/contact', require('./routes/contact'));

resulting in effectively changing resources to

I'd love to gist an example but need to quit for today...

DJviolin commented 8 years ago

I changed the improper nesting to the recommended method, and it's the same (see /contact):

https://github.com/DJviolin/lantosistvan/blob/0c0ba696ac186286cbcf08405f98aee40f7e37ba/app.js

https://github.com/DJviolin/lantosistvan/blob/6f7b8eb1074422ede2df30204e02b48889c3919c/routes/contact.js

Setting the translation only with a cookie works like a charm. But I want a SEO friendly translation if it's possible.

mashpie commented 8 years ago

https://github.com/DJviolin/lantosistvan/blob/0c0ba696ac186286cbcf08405f98aee40f7e37ba/app.js#L166 should read

app.use('/:lang/contact', contact);

to make URLs work like:

you might also consider https://github.com/mashpie/i18n-node#i18n__l to get completly localized urls

DJviolin commented 8 years ago

Thank You! So I have to init the routers with:

app.use('/contact', contact);
app.use('/:lang/contact', contact);

Probably I will mix this with cookies. My only problem is if someone clicks on a link than the query.param goes away from the URL. My links is hardcoded into the handlebars templates. Is there a way to update them when someone clicking on the logo I want to arrive from /en/contact to /en on the home.

Another problem is for the index translation /en doesn't work, just only /en/. The code is:

app.use('/', index);
app.use('/:lang', index);

Here is the refactored app.js: https://github.com/DJviolin/lantosistvan/blob/857049533b09411f9da92c473c0a68f0636c0dc8/app.js

fourpixels commented 8 years ago

Come on pal, this is for stackoverflow, not here :)

mashpie commented 8 years ago

@DJviolin please refer to expressjs docs - routing issues are not i18n related - try searching for "Enable strict routing" on http://expressjs.com/en/4x/api.html You should find express.Router([options]) - or check out https://github.com/ericf/express-slash

DJviolin commented 8 years ago

@mashpie Thank You!

musabkhunaijir commented 5 years ago

you can use

app.use(function(req, res, next){ i18n.setLocale(req.query.lang); next(); });