File size: 16,216 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 |
# i18next Parser [](https://travis-ci.org/i18next/i18next-parser) [](https://codecov.io/gh/i18next/i18next-parser)
[](https://www.npmjs.com/package/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](https://locize.com/). They are a sponsor of this project. Actually, if you use this package and like it, supporting me on [Patreon](https://www.patreon.com/karelledru) would mean a great deal!
<p>
<a href="https://www.patreon.com/karelledru" target="_blank">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button.png" alt="Become a Patreon">
</a>
</p>
## Features
- Choose your weapon: A CLI, a standalone parser or a stream transform
- 5 built in lexers: Javascript, JSX, HTML, Handlebars, and TypeScript+tsx
- Handles VueJS 3.0 (.vue) files
- 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
- Parses comments for static keys to support dynamic key translations.
- Supports i18next features:
- **Context**: keys of the form `key_context`
- **Plural**: keys of the form `key_zero`, `key_one`, `key_two`, `key_few`, `key_many` and `key_other` as described [here](https://www.i18next.com/translation-function/plurals)
## Versions
You can find information about major releases on the [dedicated page](https://github.com/i18next/i18next-parser/releases). The [migration documentation](docs/migration.md) will help you figure out the breaking changes between versions.
- `9.x` is tested on Node 18, 20 and 22.
- `8.x` is tested on Node 16, 18 and 20.
- `7.x` is tested on Node 14, 16 and 18.
- `6.x` is tested on Node 14 and 16.
## 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](https://github.com/isaacs/node-glob). 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>**: Path to the config file (default: i18next-parser.config.{js,mjs,json,ts,yaml,yml}).
- **-o, --output <path>**: Path to the output directory (default: locales/$LOCALE/$NAMESPACE.json).
- **-s, --silent**: Disable logging to stdout.
- **--fail-on-warnings**: Exit with an exit code of 1 on warnings
- **--fail-on-update**: Exit with an exit code of 1 when translations are updated (for CI purpose)
### Gulp
Save the package to your devDependencies:
```
yarn add -D i18next-parser
npm install --save-dev i18next-parser
```
[Gulp](http://gulpjs.com/) defines itself as the streaming build system. Put simply, it is like Grunt, but performant and elegant.
```javascript
import { gulp as i18nextParser } from 'i18next-parser'
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](https://github.com/broccolijs/broccoli) defines itself as a fast, reliable asset pipeline, supporting constant-time rebuilds and compact build definitions.
```javascript
import Funnel from 'broccoli-funnel'
import { broccoli as i18nextParser } from 'i18next-parser'
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',
})
export default i18n
```
> **Note**: You may need to configure Broccoli to place temporary files (option: tmpdir) within the current working
> directory as I18next-parser does not traverse down beyond that.
## 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.
```js
// i18next-parser.config.js
export default {
contextSeparator: '_',
// Key separator used in your translation keys
createOldCatalogs: true,
// Save the \_old files
defaultNamespace: 'translation',
// Default namespace used in your i18next config
defaultValue: '',
// Default value to give to keys with no value
// You may also specify a function accepting the locale, namespace, key, and value as arguments
indentation: 2,
// Indentation of the catalog files
keepRemoved: false,
// Keep keys from the catalog that are no longer in code
// You may either specify a boolean to keep or discard all removed keys.
// You may also specify an array of patterns: the keys from the catalog that are no long in the code but match one of the patterns will be kept.
// The patterns are applied to the full key including the namespace, the parent keys and the separators.
keySeparator: '.',
// Key separator used in your translation keys
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
// see below for more details
lexers: {
hbs: ['HandlebarsLexer'],
handlebars: ['HandlebarsLexer'],
htm: ['HTMLLexer'],
html: ['HTMLLexer'],
mjs: ['JavascriptLexer'],
js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
ts: ['JavascriptLexer'],
jsx: ['JsxLexer'],
tsx: ['JsxLexer'],
default: ['JavascriptLexer'],
},
lineEnding: 'auto',
// Control the line ending. See options at https://github.com/ryanve/eol
locales: ['en', 'fr'],
// An array of the locales in your applications
namespaceSeparator: ':',
// Namespace separator used in your translation keys
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
output: 'locales/$LOCALE/$NAMESPACE.json',
// Supports $LOCALE and $NAMESPACE injection
// Supports JSON (.json) and YAML (.yml) file formats
// Where to write the locale files relative to process.cwd()
pluralSeparator: '_',
// Plural separator used in your translation keys
// If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
// If you don't want to generate keys for plurals (for example, in case you are using ICU format), set `pluralSeparator: false`.
input: undefined,
// An array of globs that describe where to look for source files
// relative to the location of the configuration file
sort: false,
// Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters)
verbose: false,
// Display info about the parsing including some stats
failOnWarnings: false,
// Exit with an exit code of 1 on warnings
failOnUpdate: false,
// Exit with an exit code of 1 when translations are updated (for CI purpose)
customValueTemplate: null,
// If you wish to customize the value output the value as an object, you can set your own format.
//
// - ${defaultValue} is the default value you set in your translation function.
// - ${filePaths} will be expanded to an array that contains the absolute
// file paths where the translations originated in, in case e.g., you need
// to provide translators with context
//
// Any other custom property will be automatically extracted from the 2nd
// argument of your `t()` function or tOptions in <Trans tOptions={...} />
//
// Example:
// For `t('my-key', {maxLength: 150, defaultValue: 'Hello'})` in
// /path/to/your/file.js,
//
// Using the following customValueTemplate:
//
// customValueTemplate: {
// message: "${defaultValue}",
// description: "${maxLength}",
// paths: "${filePaths}",
// }
//
// Will result in the following item being extracted:
//
// "my-key": {
// "message": "Hello",
// "description": 150,
// "paths": ["/path/to/your/file.js"]
// }
resetDefaultValueLocale: null,
// The locale to compare with default values to determine whether a default value has been changed.
// If this is set and a default value differs from a translation in the specified locale, all entries
// for that key across locales are reset to the default value, and existing translations are moved to
// the `_old` file.
i18nextOptions: null,
// If you wish to customize options in internally used i18next instance, you can define an object with any
// configuration property supported by i18next (https://www.i18next.com/overview/configuration-options).
// { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals.
yamlOptions: null,
// If you wish to customize options for yaml output, you can define an object here.
// Configuration options are here (https://github.com/nodeca/js-yaml#dump-object---options-).
// Example:
// {
// lineWidth: -1,
// }
}
```
### 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 [Typescript compiler](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) to walk through your code and extract translation functions.
The default configuration is below:
```js
{
// JavascriptLexer default config (js, mjs)
js: [{
lexer: 'JavascriptLexer',
functions: ['t'], // Array of functions to match
namespaceFunctions: ['useTranslation', 'withTranslation'], // Array of functions to match for namespace
}],
}
```
#### Jsx
The JSX lexer builds off of the Javascript lexer and extends it with support for JSX syntax.
Default configuration:
```js
{
// JsxLexer default config (jsx)
// JsxLexer can take all the options of the JavascriptLexer plus the following
jsx: [{
lexer: 'JsxLexer',
attr: 'i18nKey', // Attribute for the keys
componentFunctions: ['Trans'], // Array of components to match
}],
}
```
If your JSX files have `.js` extension (e.g. create-react-app projects) you should override the default `js` lexer with `JsxLexer` to enable jsx parsing from js files:
```js
{
js: [{
lexer: 'JsxLexer'
}],
}
```
##### Supporting helper functions in Trans tags
If you're working with i18next in Typescript, you might be using a helper function to make sure that objects in <Trans /> components pass the typechecker: e.g.,
```tsx
const SomeComponent = (props) => (
<Trans>
Visit
<Link to='/user/john'>{castAsString({ name: props.name })}'s profile</Link>
{/* Equivalent to, but resolves typechecker errors with */}
<Link to='/user/john'>{{ name: props.name }}'s profile</Link>
</Trans>
)
function castAsString(record: Record<string, unknown>): string {
return record as unknown as string
}
```
In order for the parser to extract variables properly, you can add a list of
such functions to the lexer options:
```
{
js: [{
lexer: 'JsxLexer',
transIdentityFunctionsToIgnore: ['castAsString']
}],
}
```
#### 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
```js
{
// HandlebarsLexer default config (hbs, handlebars)
handlebars: [
{
lexer: 'HandlebarsLexer',
functions: ['t'], // Array of functions to match
},
]
}
```
#### Html
```js
{
// HTMLLexer default config (htm, html)
html: [{
lexer: 'HTMLLexer',
attr: 'data-i18n' // Attribute for the keys
optionAttr: 'data-i18n-options' // Attribute for the options
}]
}
```
#### Custom lexers
You can provide function instead of string as a custom lexer.
```js
import CustomJsLexer from './CustomJsLexer';
// ...
{
js: [CustomJsLexer],
jsx: [{
lexer: CustomJsLexer,
customOption: true // Custom attribute passed to CustomJsLexer class constructor
}]
}
// ...
```
### Caveats
While i18next extracts translation keys in runtime, i18next-parser doesn't run the code, so it can't interpolate values in these expressions:
```
t(key)
t('key' + id)
t(`key${id}`)
```
As a workaround you should specify possible static values in comments anywhere in your file:
```
// t('key_1')
// t('key_2')
t(key)
/*
t('key1')
t('key2')
*/
t('key' + id)
```
## 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` event if the file has a key that is not a string litteral or an option object with a spread operator:
`.pipe( i18next().on('warning', (path, key) => {}) )`
Here is a list of the warnings:
- **Key is not a string literal**: the parser cannot parse variables, only literals. If your code contains something like `t(variable)`, the parser will throw a warning.
- **Found same keys with different values**: if you use different default values for the same key, you'll get this error. For example, having `t('key', {defaultValue: 'foo'})` and `t('key', {defaultValue: 'bar'})`. The parser will select the latest one.
- **Found translation key already mapped to a map or parent of new key already mapped to a string**: happens in this kind of situation: `t('parent', {defaultValue: 'foo'})` and `t('parent.child', {defaultValue: 'bar'})`. `parent` is both a translation and an object for `child`.
## Contribute
Any contribution is welcome. Please [read the guidelines](docs/development.md) first.
Thanks a lot to all the previous [contributors](https://github.com/i18next/i18next-parser/graphs/contributors).
If you use this package and like it, supporting me on [Patreon](https://www.patreon.com/karelledru) is another great way to contribute!
<p>
<a href="https://www.patreon.com/karelledru" target="_blank">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button.png" alt="Become a Patreon">
</a>
</p>
---
## Gold Sponsors
<p>
<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>
|