i18next-fs-backend
Version:
i18next-fs-backend is a backend layer for i18next using in Node.js and for Deno to load translations from the filesystem.
198 lines (149 loc) • 6.36 kB
Markdown
# Introduction
[](https://github.com/i18next/i18next-fs-backend/actions?query=workflow%3Anode)
[](https://github.com/i18next/i18next-fs-backend/actions?query=workflow%3Adeno)
[](https://www.npmjs.com/package/i18next-fs-backend)
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](https://github.com/i18next/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:
- .json
- .json5
- .jsonc
- .yml/.yaml
- .js (very limited, checks for `exports` or `export default`)
# Getting started
```bash
# npm package
$ npm install i18next-fs-backend
```
Wiring up:
```js
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
i18next.use(Backend).init(i18nextOptions);
```
for Deno:
```js
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);
```
- As with all modules you can either pass the constructor function (class) to the i18next.use or a concrete instance.
## Backend Options
```js
{
// 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:
```js
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
i18next.use(Backend).init({
backend: options,
});
```
on construction:
```js
import Backend from 'i18next-fs-backend';
const Backend = new Backend(null, options);
```
via calling init:
```js
import Backend from 'i18next-fs-backend';
const Backend = new Backend();
Backend.init(null, options);
```
## TypeScript
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.:
```ts
import i18n from 'i18next'
import FsBackend, { FsBackendOptions } from 'i18next-fs-backend'
i18n
.use(FsBackend)
.init<FsBackendOptions>({
backend: {
// fs backend options
},
// other i18next options
})
```
# If set i18next initAsync option to false it will load the files synchronously
```js
// 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')
}
})
```
## Security considerations
### Language / namespace values reach the filesystem
`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.
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 `eval`
The 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` / `.ts` locale file as code that will run with the
> full privileges of your Node process**, including access to
> `process.env`, the filesystem, and the network.
Concretely that means:
- Never load `.js` / `.ts` locale files from an untrusted or writable
source (user uploads, compromised CDN, shared-mount drops).
- If your build / deploy pipeline produces locale files, secure the
pipeline the same way you would secure any code-producing pipeline
(signed commits, reviewed merges, protected branches).
- Prefer **JSON / JSON5 / YAML / JSONC** for locale files whenever you
don't need expression-level dynamism — those formats are parsed, not
executed.
### Reporting a vulnerability
Please **do not** open a public GitHub issue for security problems. Send
reports privately via the [GitHub Security Advisories](https://github.com/i18next/i18next-fs-backend/security/advisories/new)
flow on the repository.
---
<h3 align="center">Gold Sponsors</h3>
<p align="center">
<a href="https://locize.com/" target="_blank">
<img src="https://raw.githubusercontent.com/i18next/i18next/master/assets/locize_sponsor_240.gif" width="240px">
</a>
</p>