i18next Parser
When translating an application, maintaining the translation catalog by hand is painful. This package parses your code and automates this process.
Finally, if you want to make this process even less painful, I invite you to check Locize. They are a sponsor of this project. Actually, if you use this package and like it, supporting me on Patreon would mean a great deal!
Features
- Choose your weapon: A CLI, a standalone parser or a stream transform
- 6 built in lexers: Javascript, JSX, HTML, Handlebars, TypeScript+tsx and Vue
- Creates one catalog file per locale and per namespace
- Backs up the old keys your code doesn't use anymore in
namespace_old.json
catalog - Restores keys from the
_old
file if the one in the translation file is empty - Supports i18next features:
- Context: keys of the form
key_context
- Plural: keys of the form
key_plural
and key_0
, key_1
as described here
- Tested on Node 10+. If you need support for 6 and 8, look at the
1.0.x
versions.
Versions
1.x
has been in beta for a good while. You can follow the pre-releases here. It is a deep rewrite of this package that solves many issues, the main one being that it was slowly becoming unmaintainable. The migration from 0.x
contains all the breaking changes. Everything that follows is related to 1.x
. You can still find the old 0.x
documentation on its dedicated branch.
Usage
CLI
You can use the CLI with the package installed locally but if you want to use it from anywhere, you better install it globally:
yarn global add i18next-parser
npm install -g i18next-parser
i18next 'app/**/*.{js,hbs}' 'lib/**/*.{js,hbs}' [-oc]
Multiple globbing patterns are supported to specify complex file selections. You can learn how to write globs here. Note that glob must be wrapped with single quotes when passed as arguments.
IMPORTANT NOTE: If you pass the globs as CLI argument, they must be relative to where you run the command (aka relative to process.cwd()
). If you pass the globs via the input
option of the config file, they must be relative to the config file.
- -c, --config : Path to the config file (default: i18next-parser.config.js).
- -o, --output : Path to the output directory (default: locales/$LOCALE/$NAMESPACE.json).
- -S, --silent: Disable logging to stdout.
Gulp
Save the package to your devDependencies:
yarn add -D i18next-parser
npm install --save-dev i18next-parser
Gulp defines itself as the streaming build system. Put simply, it is like Grunt, but performant and elegant.
const i18nextParser = require('i18next-parser').gulp;
gulp.task('i18next', function() {
gulp.src('app/**')
.pipe(new i18nextParser({
locales: ['en', 'de'],
output: 'locales/$LOCALE/$NAMESPACE.json'
}))
.pipe(gulp.dest('./'));
});
IMPORTANT: output
is required to know where to read the catalog from. You might think that gulp.dest()
is enough though it does not inform the transform where to read the existing catalog from.
Broccoli
Save the package to your devDependencies:
yarn add -D i18next-parser
npm install --save-dev i18next-parser
Broccoli.js defines itself as a fast, reliable asset pipeline, supporting constant-time rebuilds and compact build definitions.
const Funnel = require('broccoli-funnel')
const i18nextParser = require('i18next-parser').broccoli;
const appRoot = 'broccoli'
let i18n = new Funnel(appRoot, {
files: ['handlebars.hbs', 'javascript.js'],
annotation: 'i18next-parser'
})
i18n = new i18nextParser([i18n], {
output: 'broccoli/locales/$LOCALE/$NAMESPACE.json'
})
module.exports = i18n
Options
Using a config file gives you fine-grained control over how i18next-parser treats your files. Here's an example config showing all config options with their defaults.
module.exports = {
contextSeparator: '_',
createOldCatalogs: true,
defaultNamespace: 'translation',
defaultValue: '',
indentation: 2,
keepRemoved: false,
keySeparator: '.',
lexers: {
hbs: ['HandlebarsLexer'],
handlebars: ['HandlebarsLexer'],
htm: ['HTMLLexer'],
html: ['HTMLLexer'],
mjs: ['JavascriptLexer'],
js: ['JavascriptLexer'],
ts: ['JavascriptLexer'],
jsx: ['JsxLexer'],
tsx: ['JsxLexer'],
default: ['JavascriptLexer']
},
lineEnding: 'auto',
locales: ['en', 'fr'],
namespaceSeparator: ':',
output: 'locales/$LOCALE/$NAMESPACE.json',
input: undefined,
reactNamespace: false,
sort: false,
skipDefaultValues: false,
useKeysAsDefaultValue: false,
verbose: false,
customValueTemplate: null,
}
Lexers
The lexers
option let you configure which Lexer to use for which extension. Here is the default:
Note the presence of a default
which will catch any extension that is not listed.
There are 4 lexers available: HandlebarsLexer
, HTMLLexer
, JavascriptLexer
and
JsxLexer
. Each has configurations of its own. Typescript is supported via JavascriptLexer
and JsxLexer
.
If you need to change the defaults, you can do it like so:
Javascript
The Javascript lexer uses Acorn to walk through your code and extract references
translation functions. If your code uses features not supported natively by Acorn, you can enable support through
injectors
and plugins
configuration. Note that you must install these additional dependencies yourself through
yarn
or npm
; they are not included in this package. This is an example configuration that adds all non-jsx plugins supported by acorn
at the time of writing:
const injectAcornStaticClassPropertyInitializer = require('acorn-static-class-property-initializer/inject');
const injectAcornStage3 = require('acorn-stage3/inject');
const injectAcornEs7 = require('acorn-es7');
js: [{
lexer: 'JavascriptLexer'
functions: ['t'],
acorn: {
injectors: [
injectAcornStaticClassPropertyInitializer,
injectAcornStage3,
injectAcornEs7,
],
plugins: {
staticClassPropertyInitializer: true,
stage3: true,
es7: true,
}
}
}],
If you receive an error that looks like this:
TypeError: baseVisitor[type] is not a function
The problem is likely that you are missing a plugin that supports a feature that your code uses.
The default configuration is below:
{
js: [{
lexer: 'JavascriptLexer'
functions: ['t'],
acorn: {
sourceType: 'module',
ecmaVersion: 9,
injectors: [],
plugins: {},
}
}],
}
Jsx
The JSX lexer builds off of the Javascript lexer, and additionally requires the acorn-jsx
plugin. To use it, add acorn-jsx
to your dev dependencies:
npm install -D acorn-jsx
yarn add -D acorn-jsx@4.1.1
Default configuration:
{
jsx: [{
lexer: 'JsxLexer',
attr: 'i18nKey',
acorn: {
sourceType: 'module',
ecmaVersion: 9,
injectors: [],
plugins: {},
}
}],
}
Ts(x)
Typescript is supported via Javascript and Jsx lexers. If you are using Javascript syntax (e.g. with React), follow the steps in Jsx section, otherwise Javascript section.
Handlebars
{
handlebars: [{
lexer: 'HandlebarsLexer',
functions: ['t']
}]
}
Html
{
html: [{
lexer: 'HtmlLexer',
attr: 'data-i18n'
optionAttr: 'data-i18n-options'
}]
}
Custom lexers
You can provide function instead of string as a custom lexer.
const CustomJsLexer = require('./CustomJsLexer');
{
js: [CustomJsLexer],
jsx: [{
lexer: CustomJsLexer,
customOption: true
}]
}
Events
The transform emits a reading
event for each file it parses:
.pipe( i18next().on('reading', (file) => {}) )
The transform emits a error:json
event if the JSON.parse on json files fail:
.pipe( i18next().on('error:json', (path, error) => {}) )
The transform emits a warning:variable
event if the file has a key that contains a variable:
.pipe( i18next().on('warning:variable', (path, key) => {}) )
Contribute
Any contribution is welcome. Please read the guidelines first.
Thanks a lot to all the previous contributors.
If you use this package and like it, supporting me on Patreon is another great way to contribute!