Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

i18next-http-middleware

i18next937.7kMIT3.9.1TypeScript support: included

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

i18next, i18next-backend, i18next-http-middleware, express

readme

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 resolvedLng = req.resolvedLanguage // 'de-CH'
  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 resolvedLng = request.resolvedLanguage // 'de-CH'
    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.resolvedLanguage': ctx.resolvedLanguage,
    'ctx.language': ctx.language,
    'ctx.i18n.resolvedLanguage': ctx.i18n.resolvedLanguage,
    '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 })

Fresh 2.x

import i18next from 'https://deno.land/x/i18next/index.js'
import i18nextMiddleware from 'https://deno.land/x/i18next_http_middleware/index.js'
import { App, createDefine } from "jsr:@fresh/core";

i18next
  .use(i18nextMiddleware.LanguageDetector)
  .init({
    preload: ["en", "fr"],
    fallbackLng: "en",
    resources: {
      en: {
        translation: { hi: "hello" }
      },
      fr: {
        translation: { hi: "bonjour" }
      }
    }
  });

const app = new App()
  .use(i18nextMiddleware.freshPlugin(i18next))
  .get("/", (ctx) => {
    return new Response(ctx.state.t('hi'))
  });

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'
  cookieHttpOnly: true // prevent client-side script access to the cookie

  // 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.


Gold Sponsors

changelog

v3.9.1

  • Adds missing TS declaration and options argument. #99

v3.9.0

  • Fresh 2 support #98

v3.8.3

  • Supports the parsing of Deno headers (fixes #94) #95

v3.8.2

  • fix: properly handle fallbackLng when configured as an object with default property or language-specific fallback chains #92

v3.8.1

  • Try to fix Language detector doesn't fall back to basic language when dialect is present #91 by making sure fix for #26 is still working

v3.8.0

  • Introduce 'cookieHttpOnly' option to configure HttpOnly cookie #89

v3.7.4

  • check for common xss attack patterns on detected language

v3.7.3

  • add shortcut for resolvedLanguage

v3.7.2

  • fix: Fastify no longer complains about the type #83

v3.7.1

  • Just to be sure, sanitize the Content-Language response header. (Eventhough there is no known/reproducible vulnerability yet #80)

v3.7.0

  • support i18next v24

v3.6.0

  • introduce convertDetectedLanguage option

v3.5.0

  • fix: separate cjs and mjs typings

v3.4.1

  • fix(languageDetector): handle es-419 special case #65

v3.4.0

  • support koa #61

v3.3.2

  • deno: oak fix #59

v3.3.1

  • Support TS5 types exports

v3.3.0

  • fallback in getResourcesHandler to check also in route params

v3.2.2

  • optimize header based language detection for 3 char languages #54

v3.2.1

  • fix issue missing i18n.t and t functions before authenticating #52

v3.2.0

  • HapiJs support #50

v3.1.6

  • add Fastify type support #47

v3.1.5

  • fallbackLng option can also be a function

v3.1.4

v3.1.3

  • optimize getQuery() function to check if req.query is iterable

v3.1.2

  • fix the type of the lookup method #37

v3.1.1

  • make sure no undefined language is detected

v3.1.0

  • added types

v3.0.6

  • ignoreCase option true by default

v3.0.5

  • introduce ignoreCase option

v3.0.4

  • fix language detection algorithm to handle fallbackLng correctly

v3.0.3

  • prevent URIError with cookie language detector

v3.0.2

  • transpile also esm

v3.0.1

  • introduce lookupHeaderRegex option

v3.0.0

  • BREAKING needs i18next >= 19.5.0
  • let i18next figure out which detected lng is best match

v2.1.2

  • fix: get whitelist from correct property

v2.1.1

  • extend httpFunctions getQuery to handle some edge cases

v2.1.0

  • LanguageDetector: cookie, new option: cookiePath

v2.0.0

  • potentially BREAKING: change cookie defaults: cookieSameSite: 'strict'