How to handle language specific subdomains in express? - node.js

Using nodejs and express, I try to set the language based on a "subfolder" in the routes. This language parameter is optional. Is there a way of doing this without having to change all the routes to contain the optional language parameter?
For example, I want
mydomain.tld/mypath/ and mydomain.tld/de/mypath/ to trigger the same route for /mypath/ , with a middleware which detects the /de/ (if present) and sets locale variables accordingly.

You will have to resort to checking for the special case of there being a language tag in the route, and then rewriting req.url in the middleware, setting the correct locale variables. Here is a simple sketch example of how this could be done.
var languages = ["de", ...];
function routeLanguage(req, res, next) {
var lang, parts = req.path.split("/");
if (parts[1] && (lang = languages[parts[1]])) {
req.url = req.url.replace(/^\/[^/]*/, "");
// set locale vars using lang
}
return next();
}
This middleware will reroute any "language path" to the normal route (/de/mypath goes to /mypath).
Be careful, as this will not work for the default language if you have a language tag as a path, i.e. /de will route to / with de as language and /de/de will route to /de.
Update:
This simplifies somewhat if you assume a mount point for the middleware, as express can then use it's regex parsing on the path. You could also defer the localization logic to later in the stack, by assigning the parsed language to for example req.lang.
app.use(/^\/(de|en|...)\//, function (req, res, next) {
req.lang = req.params[0];
req.url = req.url.replace(/^\/[^/]*/, "");
next();
});

Related

Express - Allowing for a closing / at the end of a routing path

While routing in Express is quite straightforward I'm having trouble adjusting it to paths that end with a /.
For example, suggest I define the following route:
app.get('/about', (req,res) => res.render('about'));
Now if someone navigates to www.example.com/about the about view is rendered. However, if that same person navigates to www.example.com/about/ the route I specified above will not work. Some people (me included) have gotten used to naturally adding a closing / at the end of paths. I read the Express routing documentation page but it seems the developers were oblivious to this possibility. The only solution I've found thus far is to use regular expressions for each and every route to account for this variation. For example, the route above would become:
app.get(/\/about\/?/, (req,res) => res.render('about'));
Is there a more elegant (or built in) solution to allow for path with a closing / in Express?
This question has already been answered in https://stackoverflow.com/a/15773824/515774
Basically, you will need to add a middleware which will strip the trailing slash and make a redirect request, which will solve your problem.
Following is the code snippet from the previous answer.
app.use(function(req, res, next) {
if (req.path.substr(-1) == '/' && req.path.length > 1) {
var query = req.url.slice(req.path.length);
res.redirect(301, req.path.slice(0, -1) + query);
} else {
next();
}
});
To avoid redirect, you can just rewrite the URL. Reference https://stackoverflow.com/a/13446128/515774
Note: The browser URL stays the same using this approach.
app.use(function(req, res, next) {
if (req.url.slice(-1) === '/') {
req.url = req.url.slice(0, -1);
}
next();
});

Node express api routes for multilingual directory like url

Does any one knows an example or could explain here how node.js and express would have to route for a multilanguage site? I'm using i18n-node for translation and folder like routing ( /es/, /de/ , etc ) for different languages. This all are static routes but I also have routes like apiRoutes.route('/user/profile') using 'app' at the begining ( app.get('/app/user/profile') so please consider this in your answer so is NOT necesary route to : app.get('/es/app/user/profile') .
having 15 routes like this now:
app.get('/terms', function(req, res) {
res.render('terms',{
...
});
});
how it have to be set for routes like:
app.get('/es/terms', function(req, res) {
res.render('terms',{
...
});
});
Should I duplicate this routes and add for example a locale for
each like:
app.get('/es/terms', function(req, res) {
res.render('terms',{
...
});
});
Or Should do something like:
if cookie['lang'] && cookie['lang'] is in locales
// then redirect to /:lang/terms
else
// show default language in /terms
if req.headers["accept-language"] && req.headers["accept-language"]
// then redirect to /:lang/terms
else
//show default language in /terms
Or there is another way I should approach this that follows good practices or is better respecting standards?
Miro's Answer in :
How can I get the browser language in node.js (express.js)? says I should use app.all('*', ...
Is this all I need?, ..still, it might have a syntax error or i'm not understanding well this two parts
var rxLocal = /^\/(de|en)/i;
...
app.get(/\/(de|en)\/login/i, routes.login);
thanks in advance
You need to consider 2 things :
1. How get the local :
Accept-Language
The HTTP protocole define the Accept-Language header to manage the local. This is a normalized method. You can access it with the req.acceptsLanguages method of express.
+Normalized
+Natively support by brower
-Not easy to by passe by the end user
Path / Cookies
You can get the local from the path. In express it can be do with a parameter patter like /:local/rest/of/path and retrieve in the request object with the req.param method.
You can also get the information from the cookies with the req.cookies properties (don't forgot to set it).
Both
To increase the user experience you can mix the both method. For exemple get the default language from the HTTP header send by the browser but permite to the user to override this in you application and store this parameter in the cookies.
2. Use the local:
Each methods to get the local can be used from different way. I will
use random of them in exemple but they are all compatible.
Top level configuration.
In case of you use a template Engine and you controller can be local agnostic. You can use a middleware to get the local information and configure the render engine.
app.use('/:local' (req, res, next) => {
let localKey = req.param('local');
res.locals = // Some ingenious method to get the locales from localKey
next();
}
Check res.locals and your engine documentation.
Use it in controller.
If the local is part of the contoller process. You can get directly is value in controller.
In case of you use a complexe method to determine the final value of the local, you can also use a middleware to determine this value and enrich the request with it.
app.use((req, res, next) => {
let local = req.cookies.local;
if(!local) local = req.acceptsLanguages();
if(!local) local = 'en-US';
req.local = local;
}
Both
You can use both method too. It depend of what you need. Find the best way to get a maintainable code and avoid replication for your use case.
When you use middle where witch impact the controllers, be sure you declare them before your routes.
You can use a route parameter to get the locale from the URL, like this:
app.get('/:lang/terms', function (req, res) {
if (req.params === 'es') {
res.send('¡Hola!');
else {
res.send('Hi!');
}
});
The colon character tells Express to put whatever is between the first to slashes of the path in req.params.lang.
See express routing documentation for details.

Relative paths to another relative path

I have a Node/Express server set up, and we are making the transition from subdirectories to sub domains for localisation, e.g.
es.example.com // old way
www.example.com/es // new way
Doing this for a variety of reasons, but mostly to facilitate the preservation of JWT login state across the internationalised content.
I have run into the problem of links generated dynamically - how to ensure they will preserve the subdirectory?
For example, if I am linking a user from index.html to /user-profile/1000, how can I maintain /es/user-profile/1000? Is there a way of having relative paths to other relative paths? If that even makes sense?
You can use a middleware solution that redirects to the original paths. Here's a rudimentary example
var languages = ['en', 'es'], _ = require('lodash');
app.use("/:lang/*", function(req, res, next) {
console.log(req.params, req.path);
var found = _.find(languages, function(v) {
return v === req.params.lang;
});
if (!!found) {
res.redirect('/' + req.params[0]);
} else {
next();
}
});
This will check for all valid languages and if it recognizes one, it will strip the language from the path and redirect to the original path minus language. Otherwise it will continue to the path provided.

In App redirect in expressjs using middleware

I am trying to make a middleware for handling url aliases, what I am doing right now is :
// [...]
module.exports = function() {
return function(req, res, next) {
// getAlias would get an object {alias:"alias/path",source:"/real/path"} or null
var alias = getAlias(req.url);
if(alias) {
req.url = alias.source;
}
next();
};
};
So basicaly I am looking in a store for the requested url and if it is found as an alias I change request.url to the source path to that alias so that express calls the right route.
The problem is request.url and request.path have the same value, but changing request.path does not work while request.url works. In addition I am not sure which one i have to test agains.
Things work when I interact with request.url but just wanted to make sure that I am doing it the proper way.
Any thoughts ?
Rewriting the req.url property is the correct way for internally rerouting requests. That is why there is a req.originalUrl for the cases where one does change the original URL.
This is what the Express documentation states for req.originalUrl:
This property is much like req.url, however it retains the original
request url, allowing you to rewrite req.url freely for internal
routing purposes.
The req.url property isn't documented, but from the statement above you can infer it's meant to be used in the way you explained. It is also used in that way in some of the Express tests.
You can use run-middleware module exactly for that. Just run the handler you want by using the URL & method & data.
https://www.npmjs.com/package/run-middleware
For example:
module.exports = function() {
return function(req, res, next) {
// getAlias would get an object {alias:"alias/path",source:"/real/path"} or null
var alias = getAlias(req.url);
if(alias) {
res.runMiddleware(alias,(status,data)=>(res.status(status).send(data))
}
next();
};
};

Multi-language routes in express.js?

I'm wondering if there is a best practise example on how to implement multi-lanuage routes in express.js. i want to use the accept-language header to get the browser language and then redirect automatically to the corresponding language route like
www.foo.bar/de/startseite OR
www.foo.bar/en/home
Any advice on this?
i have done the following:
install i18n-node modul and register in the express js. here is code.
var express = require('express')
, routes = require('./routes')
, http = require('http')
, i18n = require("i18n");
var app = express();
i18n.configure({
// setup some locales - other locales default to en silently
locales:['de', 'en'],
// disable locale file updates
updateFiles: false
});
app.configure(function(){
...
app.use(i18n.init);
...
});
// register helpers for use in templates
app.locals({
__i: i18n.__,
__n: i18n.__n
});
after this set the following to get all request
// invoked before each action
app.all('*', function(req, res, next) {
// set locale
var rxLocal = /^\/(de|en)/i;
if(rxLocal.test(req.url)){
var arr = rxLocal.exec(req.url);
var local=arr[1];
i18n.setLocale(local);
} else {
i18n.setLocale('de');
}
// add extra logic
next();
});
app.get(/\/(de|en)\/login/i, routes.login);
maybe this help.
I'd just serve up the content in the detected language directly.
For example, example.com/home serves up the home page in the best available Accept-Language (possibly overridden by cookie if you provide a language selection option on the site itself).
You'd want to make sure that your response's Vary: header includes Accept-Language.
IMO, including language codes in the URI is an ugly hack. The RFC's intent is that a single resource (your home page) is universally represented by a single URI. The entity returned for a URI can vary based on other information, such as language preferences.
Consider what happens when a German-speaking user copies a URL and sends it to an English-speaking user. That recipient would prefer to see your site in English, but because he has received a link that points to example.com/de/startseite, he goes straight to the German version.
Obviously, this isn't ideal for full internationalization of what the user sees in the address bar (since home is English), but it's more in line with the RFCs' intent, and I'd argue it works better for users, especially as links get spread around email/social/whatever.
Middleware recommendation
The answer by #miro is very good but can be improved as in the following middleware in a separate file (as #ebohlman suggests).
The middleware
module.exports = {
configure: function(app, i18n, config) {
app.locals.i18n = config;
i18n.configure(config);
},
init: function(req, res, next) {
var rxLocale = /^\/(\w\w)/i;
if (rxLocale.test(req.url)){
var locale = rxLocale.exec(req.url)[1];
if (req.app.locals.i18n.locales.indexOf(locale) >= 0)
req.setLocale(locale);
}
//else // no need to set the already default
next();
},
url: function(app, url) {
var locales = app.locals.i18n.locales;
var urls = [];
for (var i = 0; i < locales.length; i++)
urls[i] = '/' + locales[i] + url;
urls[i] = url;
return urls;
}
};
Also in sample project in github.
Explanation
The middleware has three functions. The first is a small helper that configures i18n-node and also saves the settings in app.locals (haven't figured out how to access the settings from i18n-node itself).
The main one is the second, which takes the locale from the url and sets it in the request object.
The last one is a helper which, for a given url, returns an array with all possible locales. Eg calling it with '/about' we would get ['/en/about', ..., '/about'].
How to use
In app.js:
// include
var i18n = require('i18n');
var services = require('./services');
// configure
services.i18nUrls.configure(app, i18n, {
locales: ['el', 'en'],
defaultLocale: 'el'
});
// add middleware after static
app.use(services.i18nUrls.init);
// router
app.use(services.i18nUrls.url(app, '/'), routes);
Github link
The locale can be accessed from eg any controller with i18n-node's req.getLocale().
RFC
What #josh3736 recommends is surely compliant with RFC etc. Nevertheless, this is a quite common requirement for many i18n web sites and apps, and even Google respects same resources localised and served under different urls (can verify this in webmaster tools). What I would recommended though is to have the same alias after the lang code, eg /en/home, /de/home etc.
Not sure how you plan on organizing or sharing content but you can use regular expressions with express routes and then server up different templates. Something like this:
app.get(/^\/(startseite|home)$/, function(req, res){
});
One thing that I did was to organize my content with subdomains and then use middleware to grab the content out of the database based splitting the url, but they all shared the same routes and templates.
Write a middleware function that parses any "Accept-Language" headers and sets a request-level local variable to an appropriate code (like a two-letter language code) with a default value (like "en") if there are no such headers or you don't support any language listed. In your routes, retrieve the local and tack it on to any template file names, and branch on it if there's any language-dependent processing other than template selection.

Resources