i18next http middleware

i18next-http-middleware is a middleware to be used with Node.js web frameworks like express or Fastify and also for Deno.

90
22
JavaScript

Introduction

Actions
Actions deno
Travis
npm version

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'
}

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.


Gold Sponsors