Introduction
This is a middleware to be used with Node.js web frameworks like express or Fastify and also for Deno.
It’s based on the deprecated i18next-express-middleware and can be used as a drop-in replacement. It’s not bound to a specific http framework anymore.
Advice:
To get started with server side internationalization, you may also have a look at this blog post also using using i18next-http-middleware.
Getting started
# npm package
$ npm install i18next-http-middleware
wire up i18next to request object
var i18next = require('i18next')
var middleware = require('i18next-http-middleware')
var express = require('express')
i18next.use(middleware.LanguageDetector).init({
preload: ['en', 'de', 'it'],
...otherOptions
})
var app = express()
app.use(
middleware.handle(i18next, {
ignoreRoutes: ['/foo'], // or function(req, res, options, i18next) { /* return true to ignore */ }
removeLngFromUrl: false // removes the language from the url when language detected in path
})
)
// in your request handler
app.get('myRoute', (req, res) => {
var lng = req.language // 'de-CH'
var lngs = req.languages // ['de-CH', 'de', 'en']
req.i18n.changeLanguage('en') // will not load that!!! assert it was preloaded
var exists = req.i18n.exists('myKey')
var translation = req.t('myKey')
})
// in your views, eg. in pug (ex. jade)
div = t('myKey')
Fastify usage
var i18next = require('i18next')
var middleware = require('i18next-http-middleware')
var fastify = require('fastify')
i18next.use(middleware.LanguageDetector).init({
preload: ['en', 'de', 'it'],
...otherOptions
})
var app = fastify()
app.register(i18nextMiddleware.plugin, {
i18next,
ignoreRoutes: ['/foo'] // or function(req, res, options, i18next) { /* return true to ignore */ }
})
// or
// app.addHook('preHandler', i18nextMiddleware.handle(i18next, {
// ignoreRoutes: ['/foo'] // or function(req, res, options, i18next) { /* return true to ignore */ }
// }))
// in your request handler
app.get('myRoute', (request, reply) => {
var lng = request.language // 'de-CH'
var lngs = v.languages // ['de-CH', 'de', 'en']
request.i18n.changeLanguage('en') // will not load that!!! assert it was preloaded
var exists = request.i18n.exists('myKey')
var translation = request.t('myKey')
})
Hapi usage
const i18next = require('i18next')
const middleware = require('i18next-http-middleware')
const Hapi = require('@hapi/hapi')
i18next.use(middleware.LanguageDetector).init({
preload: ['en', 'de', 'it'],
...otherOptions
})
const server = Hapi.server({
port: port,
host: '0.0.0.0',
await server.register({
plugin: i18nextMiddleware.hapiPlugin,
options: {
i18next,
ignoreRoutes: ['/foo'] // or function(req, res, options, i18next) { /* return true to ignore
}
})
// in your request handler
server.route({
method: 'GET',
path: '/myRoute',
handler: (request, h) => {
var lng = request.language // 'de-CH'
var lngs = v.languages // ['de-CH', 'de', 'en']
request.i18n.changeLanguage('en') // will not load that!!! assert it was preloaded
var exists = request.i18n.exists('myKey')
var translation = request.t('myKey')
}
})
Koa usage
var i18next = require('i18next')
var middleware = require('i18next-http-middleware')
const Koa = require('koa')
const router = require('@koa/router')()
i18next.use(middleware.LanguageDetector).init({
preload: ['en', 'de', 'it'],
...otherOptions
})
var app = new Koa()
app.use(i18nextMiddleware.koaPlugin(i18next, {
ignoreRoutes: ['/foo'] // or function(req, res, options, i18next) { /* return true to ignore */ }
}))
// in your request handler
router.get('/myRoute', ctx => {
ctx.body = JSON.stringify({
'ctx.language': ctx.language,
'ctx.i18n.language': ctx.i18n.language,
'ctx.i18n.languages': ctx.i18n.languages,
'ctx.i18n.languages[0]': ctx.i18n.languages[0],
'ctx.t("home.title")': ctx.t('home.title')
}, null, 2)
})
Deno usage
abc
import i18next from 'https://deno.land/x/i18next/index.js'
import Backend from 'https://cdn.jsdelivr.net/gh/i18next/i18next-fs-backend/index.js'
import i18nextMiddleware from 'https://deno.land/x/i18next_http_middleware/index.js'
import { Application } from 'https://deno.land/x/abc/mod.ts'
import { config } from 'https://deno.land/x/dotenv/dotenv.ts'
i18next
.use(Backend)
.use(i18nextMiddleware.LanguageDetector)
.init({
// debug: true,
backend: {
// eslint-disable-next-line no-path-concat
loadPath: 'locales/{{lng}}/{{ns}}.json',
// eslint-disable-next-line no-path-concat
addPath: 'locales/{{lng}}/{{ns}}.missing.json'
},
fallbackLng: 'en',
preload: ['en', 'de']
})
const port = config.PORT || 8080
const app = new Application()
const handle = i18nextMiddleware.handle(i18next)
app.use((next) => (c) => {
handle(c.request, c.response, () => {})
return next(c)
})
app.get('/', (c) => c.request.t('home.title'))
await app.start({ port })
ServestJS
import i18next from 'https://deno.land/x/i18next/index.js'
import Backend from 'https://cdn.jsdelivr.net/gh/i18next/i18next-fs-backend/index.js'
import i18nextMiddleware from 'https://deno.land/x/i18next_http_middleware/index.js'
import { createApp } from 'https://servestjs.org/@v1.0.0-rc2/mod.ts'
import { config } from 'https://deno.land/x/dotenv/dotenv.ts'
i18next
.use(Backend)
.use(i18nextMiddleware.LanguageDetector)
.init({
// debug: true,
backend: {
// eslint-disable-next-line no-path-concat
loadPath: 'locales/{{lng}}/{{ns}}.json',
// eslint-disable-next-line no-path-concat
addPath: 'locales/{{lng}}/{{ns}}.missing.json'
},
fallbackLng: 'en',
preload: ['en', 'de']
})
const port = config.PORT || 8080
const app = createApp()
app.use(i18nextMiddleware.handle(i18next))
app.get('/', async (req) => {
await req.respond({
status: 200,
headers: new Headers({
'content-type': 'text/plain'
}),
body: req.t('home.title')
})
})
await app.listen({ port })
add routes
// missing keys make sure the body is parsed (i.e. with [body-parser](https://github.com/expressjs/body-parser#bodyparserjsonoptions))
app.post('/locales/add/:lng/:ns', middleware.missingKeyHandler(i18next))
// addPath for client: http://localhost:8080/locales/add/{{lng}}/{{ns}}
// multiload backend route
app.get('/locales/resources.json', middleware.getResourcesHandler(i18next))
// can be used like:
// GET /locales/resources.json
// GET /locales/resources.json?lng=en
// GET /locales/resources.json?lng=en&ns=translation
// serve translations:
app.use('/locales', express.static('locales'))
// GET /locales/en/translation.json
// loadPath for client: http://localhost:8080/locales/{{lng}}/{{ns}}.json
// or instead of static
app.get('/locales/:lng/:ns', middleware.getResourcesHandler(i18next))
// GET /locales/en/translation
// loadPath for client: http://localhost:8080/locales/{{lng}}/{{ns}}
app.get('/locales/:lng/:ns', middleware.getResourcesHandler(i18next, {
maxAge: 60 * 60 * 24 * 30, // adds appropriate cache header if cache option is passed or NODE_ENV === 'production', defaults to 30 days
cache: true // defaults to false
}))
add localized routes
You can add your routes directly to the express app
var express = require('express'),
app = express(),
i18next = require('i18next'),
FilesystemBackend = require('i18next-fs-backend'),
i18nextMiddleware = require('i18next-http-middleware'),
port = 3000
i18next
.use(i18nextMiddleware.LanguageDetector)
.use(FilesystemBackend)
.init({ preload: ['en', 'de', 'it'], ...otherOptions }, () => {
i18nextMiddleware.addRoute(
i18next,
'/:lng/key-to-translate',
['en', 'de', 'it'],
app,
'get',
(req, res) => {
//endpoint function
}
)
})
app.use(i18nextMiddleware.handle(i18next))
app.listen(port, () => {
console.log('Server listening on port', port)
})
or to an express router
var express = require('express'),
app = express(),
i18next = require('i18next'),
FilesystemBackend = require('i18next-fs-backend'),
i18nextMiddleware = require('i18next-http-middleware'),
router = require('express').Router(),
port = 3000
i18next
.use(i18nextMiddleware.LanguageDetector)
.use(FilesystemBackend)
.init({ preload: ['en', 'de', 'it'], ...otherOptions }, () => {
i18nextMiddleware.addRoute(
i18next,
'/:lng/key-to-translate',
['en', 'de', 'it'],
router,
'get',
(req, res) => {
//endpoint function
}
)
app.use('/', router)
})
app.use(i18nextMiddleware.handle(i18next))
app.listen(port, () => {
console.log('Server listening on port', port)
})
custom http server
Define your own functions to handle your custom request or response
middleware.handle(i18next, {
getPath: (req) => req.path,
getUrl: (req) => req.url,
setUrl: (req, url) => (req.url = url),
getQuery: (req) => req.query,
getParams: (req) => req.params,
getBody: (req) => req.body,
setHeader: (res, name, value) => res.setHeader(name, value),
setContentType: (res, type) => res.contentType(type),
setStatus: (res, code) => res.status(code),
send: (res, body) => res.send(body)
})
language detection
Detects user language from current request. Comes with support for:
- path
- cookie
- header
- querystring
- session
Based on the i18next language detection handling: https://www.i18next.com/misc/creating-own-plugins#languagedetector
Wiring up:
var i18next = require('i18next')
var middleware = require('i18next-http-middleware')
i18next.use(middleware.LanguageDetector).init(i18nextOptions)
As with all modules you can either pass the constructor function (class) to the i18next.use or a concrete instance.
Detector Options
{
// order and from where user language should be detected
order: [/*'path', 'session', */ 'querystring', 'cookie', 'header'],
// keys or params to lookup language from
lookupQuerystring: 'lng',
lookupCookie: 'i18next',
lookupHeader: 'accept-language',
lookupHeaderRegex: /(([a-z]{2})-?([A-Z]{2})?)\s*;?\s*(q=([0-9.]+))?/gi,
lookupSession: 'lng',
lookupPath: 'lng',
lookupFromPathIndex: 0,
// cache user language, you can define if an how the detected language should be "saved" => 'cookie' and/or 'session'
caches: false, // ['cookie']
ignoreCase: true, // ignore case of detected language
// optional expire and domain for set cookie
cookieExpirationDate: new Date(),
cookieDomain: 'myDomain',
cookiePath: '/my/path',
cookieSecure: true, // if need secure cookie
cookieSameSite: 'strict', // 'strict', 'lax' or 'none'
// optional conversion function used to modify the detected language code
convertDetectedLanguage: 'Iso15897',
convertDetectedLanguage: (lng) => lng.replace('-', '_')
}
Options can be passed in:
preferred - by setting options.detection in i18next.init:
var i18next = require('i18next')
var middleware = require('i18next-http-middleware')
i18next.use(middleware.LanguageDetector).init({
detection: options
})
on construction:
var middleware = require('i18next-http-middleware')
var lngDetector = new middleware.LanguageDetector(null, options)
via calling init:
var middleware = require('i18next-http-middleware')
var lngDetector = new middleware.LanguageDetector()
lngDetector.init(options)
Adding own detection functionality
interface
module.exports = {
name: 'myDetectorsName',
lookup: function (req, res, options) {
// options -> are passed in options
return 'en'
},
cacheUserLanguage: function (req, res, lng, options) {
// options -> are passed in options
// lng -> current language, will be called after init and on changeLanguage
// store it
}
}
adding it
var i18next = require('i18next')
var middleware = require('i18next-http-middleware')
var lngDetector = new middleware.LanguageDetector()
lngDetector.addDetector(myDetector)
i18next.use(lngDetector).init({
detection: options
})
Don’t forget: You have to add the name of your detector (myDetectorsName
in this case) to the order
array in your options
object. Without that, your detector won’t be used. See the Detector Options section for more.