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.
To get started with server side internationalization, you may also have a look at this blog post also using using i18next-http-middleware.
# npm package
$ npm install i18next-http-middleware
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')
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')
})
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')
}
})
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 })
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 })
// 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
}))
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)
})
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)
})
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.
{
// 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'
}
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)
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
}
}
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.