i18next-fs-backend is a backend layer for i18next using in Node.js and for Deno to load translations from the filesystem.
JavaScript
117
186 commits
updated Sep 13, 2026
This is an i18next backend to be used in Node.js and Deno. It will load resources from the file system.
It's based on the deprecated i18next-node-fs-backend and can mostly be used as a drop-in replacement.
It will load resources from filesystem. Right now it supports following filetypes:
exports or export default)If you don't like to manage your translation files manually or are simply looking for a better management solution, take a look at i18next-locize-backend. The i18next backend plugin for π Locize βοΈ.
Starting from an app with hardcoded strings? Run npx i18next-cli localize β one command that wraps strings in t(), extracts keys, connects to Locize and AI-translates your app. See the launch post.
# npm package
$ npm install i18next-fs-backend
Wiring up:
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
i18next.use(Backend).init(i18nextOptions);
for Deno:
import i18next from 'https://deno.land/x/i18next/index.js'
import Backend from 'https://deno.land/x/i18next_fs_backend/index.js'
i18next.use(Backend).init(i18nextOptions);
{
// path where resources get loaded from, or a function
// returning a path:
// function(lngs, namespaces) { return customPath; }
// the returned path will interpolate lng, ns if provided like giving a static path
loadPath: '/locales/{{lng}}/{{ns}}.json',
// path to post missing resources
addPath: '/locales/{{lng}}/{{ns}}.missing.json',
// if you use i18next-fs-backend as caching layer in combination with i18next-chained-backend, you can optionally set an expiration time
// an example on how to use it as cache layer can be found here: https://github.com/i18next/i18next-fs-backend/blob/master/example/caching/app.js
// expirationTime: 60 * 60 * 1000
}
Options can be passed in:
preferred - by setting options.backend in i18next.init:
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
i18next.use(Backend).init({
backend: options,
});
on construction:
import Backend from 'i18next-fs-backend';
const Backend = new Backend(null, options);
via calling init:
import Backend from 'i18next-fs-backend';
const Backend = new Backend();
Backend.init(null, options);
To properly type the backend options, you can import the FsBackendOptions interface and use it as a generic type parameter to the i18next's init method, e.g.:
import i18n from 'i18next'
import FsBackend, { FsBackendOptions } from 'i18next-fs-backend'
i18n
.use(FsBackend)
.init<FsBackendOptions>({
backend: {
// fs backend options
},
// other i18next options
})
// i18n.js
const { join } = require('path')
const { readdirSync, lstatSync } = require('fs')
const i18next = require('i18next')
const Backend = require('i18next-fs-backend')
i18next
.use(Backend)
.init({
// debug: true,
initAsync: false,
fallbackLng: 'en',
lng: 'en',
preload: readdirSync(join(__dirname, '../locales')).filter((fileName) => {
const joinedPath = join(join(__dirname, '../locales'), fileName)
const isDirectory = lstatSync(joinedPath).isDirectory()
return isDirectory
}),
ns: 'backend-app',
defaultNS: 'backend-app',
backend: {
loadPath: join(__dirname, '../locales/{{lng}}/{{ns}}.json')
}
})
i18next-fs-backend substitutes the lng and ns options into the
configured loadPath / addPath templates and reads / writes the resulting
file. If those values come from an untrusted source (HTTP query string,
cookie, request header), a crafted value could break out of the intended
locale directory and read or overwrite files elsewhere on disk.
Since 2.6.4, values containing .., \, control characters, prototype
keys (__proto__, constructor, prototype), or longer than 128
characters are rejected β the backend refuses to build the filesystem path
and returns an error to the caller. Any legitimate i18next language-code
shape (BCP-47, underscores, dots, +-joined multi-language requests) is
still accepted.
/ is handled per key: it is rejected in lng, since no legitimate
language-code shape contains one, and allowed in ns since 2.6.5, so
that nested namespace names such as a/b keep mapping to subfolder layouts
like public/locales/en/a/b.json. Directory escape stays blocked in both
cases, because .. and \ are still rejected.
This is a defence-in-depth layer. It does not replace the usual
responsibility to validate lng / ns at your application boundary β
especially when either comes from user input.
.js / .ts locale files are executed via evalThe backend supports loading translation data from .js and .ts files by
eval-ing their content. This is an intentional feature β it allows
expressions, comments, and module-style default exports in locale files β
but it carries a real trust requirement:
Treat every
.js/.tslocale file as code that will run with the full privileges of your Node process, including access toprocess.env, the filesystem, and the network.
Concretely that means:
.js / .ts locale files from an untrusted or writable
source (user uploads, compromised CDN, shared-mount drops).Please do not open a public GitHub issue for security problems. Send reports privately via the GitHub Security Advisories flow on the repository.
Overall
Maintained
Code review
Latest release
Releases this year
659 followers Β· starred Aug 2025
i18next-fs-backend is a backend layer for i18next using in Node.js and for Deno to load translations from the filesystem.
JavaScript
117
186 commits
updated Sep 13, 2026
This is an i18next backend to be used in Node.js and Deno. It will load resources from the file system.
It's based on the deprecated i18next-node-fs-backend and can mostly be used as a drop-in replacement.
It will load resources from filesystem. Right now it supports following filetypes:
exports or export default)If you don't like to manage your translation files manually or are simply looking for a better management solution, take a look at i18next-locize-backend. The i18next backend plugin for π Locize βοΈ.
Starting from an app with hardcoded strings? Run npx i18next-cli localize β one command that wraps strings in t(), extracts keys, connects to Locize and AI-translates your app. See the launch post.
# npm package
$ npm install i18next-fs-backend
Wiring up:
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
i18next.use(Backend).init(i18nextOptions);
for Deno:
import i18next from 'https://deno.land/x/i18next/index.js'
import Backend from 'https://deno.land/x/i18next_fs_backend/index.js'
i18next.use(Backend).init(i18nextOptions);
{
// path where resources get loaded from, or a function
// returning a path:
// function(lngs, namespaces) { return customPath; }
// the returned path will interpolate lng, ns if provided like giving a static path
loadPath: '/locales/{{lng}}/{{ns}}.json',
// path to post missing resources
addPath: '/locales/{{lng}}/{{ns}}.missing.json',
// if you use i18next-fs-backend as caching layer in combination with i18next-chained-backend, you can optionally set an expiration time
// an example on how to use it as cache layer can be found here: https://github.com/i18next/i18next-fs-backend/blob/master/example/caching/app.js
// expirationTime: 60 * 60 * 1000
}
Options can be passed in:
preferred - by setting options.backend in i18next.init:
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
i18next.use(Backend).init({
backend: options,
});
on construction:
import Backend from 'i18next-fs-backend';
const Backend = new Backend(null, options);
via calling init:
import Backend from 'i18next-fs-backend';
const Backend = new Backend();
Backend.init(null, options);
To properly type the backend options, you can import the FsBackendOptions interface and use it as a generic type parameter to the i18next's init method, e.g.:
import i18n from 'i18next'
import FsBackend, { FsBackendOptions } from 'i18next-fs-backend'
i18n
.use(FsBackend)
.init<FsBackendOptions>({
backend: {
// fs backend options
},
// other i18next options
})
// i18n.js
const { join } = require('path')
const { readdirSync, lstatSync } = require('fs')
const i18next = require('i18next')
const Backend = require('i18next-fs-backend')
i18next
.use(Backend)
.init({
// debug: true,
initAsync: false,
fallbackLng: 'en',
lng: 'en',
preload: readdirSync(join(__dirname, '../locales')).filter((fileName) => {
const joinedPath = join(join(__dirname, '../locales'), fileName)
const isDirectory = lstatSync(joinedPath).isDirectory()
return isDirectory
}),
ns: 'backend-app',
defaultNS: 'backend-app',
backend: {
loadPath: join(__dirname, '../locales/{{lng}}/{{ns}}.json')
}
})
i18next-fs-backend substitutes the lng and ns options into the
configured loadPath / addPath templates and reads / writes the resulting
file. If those values come from an untrusted source (HTTP query string,
cookie, request header), a crafted value could break out of the intended
locale directory and read or overwrite files elsewhere on disk.
Since 2.6.4, values containing .., \, control characters, prototype
keys (__proto__, constructor, prototype), or longer than 128
characters are rejected β the backend refuses to build the filesystem path
and returns an error to the caller. Any legitimate i18next language-code
shape (BCP-47, underscores, dots, +-joined multi-language requests) is
still accepted.
/ is handled per key: it is rejected in lng, since no legitimate
language-code shape contains one, and allowed in ns since 2.6.5, so
that nested namespace names such as a/b keep mapping to subfolder layouts
like public/locales/en/a/b.json. Directory escape stays blocked in both
cases, because .. and \ are still rejected.
This is a defence-in-depth layer. It does not replace the usual
responsibility to validate lng / ns at your application boundary β
especially when either comes from user input.
.js / .ts locale files are executed via evalThe backend supports loading translation data from .js and .ts files by
eval-ing their content. This is an intentional feature β it allows
expressions, comments, and module-style default exports in locale files β
but it carries a real trust requirement:
Treat every
.js/.tslocale file as code that will run with the full privileges of your Node process, including access toprocess.env, the filesystem, and the network.
Concretely that means:
.js / .ts locale files from an untrusted or writable
source (user uploads, compromised CDN, shared-mount drops).Please do not open a public GitHub issue for security problems. Send reports privately via the GitHub Security Advisories flow on the repository.
Overall
Maintained
Code review
Latest release
Releases this year
659 followers Β· starred Aug 2025