Git Product home page Git Product logo

i18next-parser's Introduction

i18next Parser Build Status codecov

NPM

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!

Become a Patreon

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
  • 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

Versions

You can find information about major releases on the dedicated page. The migration documentation will help you figure out the breaking changes between versions.

  • 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. 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,mjs,json,ts,yaml,yml}).
  • -o, --output : 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 defines itself as the streaming build system. Put simply, it is like Grunt, but performant and elegant.

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 defines itself as a fast, reliable asset pipeline, supporting constant-time rebuilds and compact build definitions.

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.

// 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 to walk through your code and extract translation functions.

The default configuration is below:

{
  // 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:

{
  // 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: [{
    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 components pass the typechecker: e.g.,

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

{
  // HandlebarsLexer default config (hbs, handlebars)
  handlebars: [
    {
      lexer: 'HandlebarsLexer',
      functions: ['t'], // Array of functions to match
    },
  ]
}

Html

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

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 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!

Become a Patreon


Gold Sponsors

i18next-parser's People

Contributors

cielquan avatar connor4312 avatar coyotte508 avatar danielrentz avatar dependabot[bot] avatar elindorath avatar ggalletty avatar glsignal avatar guoyunhe avatar heivo avatar immortal-tofu avatar incleaf avatar jonathansun avatar josh-egan avatar joshkel avatar karellm avatar laszlopandy avatar martijnr avatar mmorel-35 avatar mogelbrod avatar mynockspit avatar nicegamer7 avatar nickasd avatar pxpeterxu avatar queicherius avatar sk-idwell avatar tobiasweibel avatar tstirrat15 avatar vincaslt avatar vovan-ve avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

i18next-parser's Issues

Doesn't extract keys from Interpolation component with react-i18next

Can't be much more descriptive than in the title. Here is a code snippet:

<div className="flex-item-m-2 recent-activity-item-footer">
  <Interpolate i18nKey='Total {{amount}}' useDangerouslySetInnerHTML={true} amount={totalDocs} />
</div>

Sadly, the string 'Total {{amount}}' never makes it to the JSON output of the parser.

Collect concatenated strings

i18next-parser doesnt collect concatenated strings, e.g. i18n.t("Foo: " + "Bar"). It would be awesome if it did.
i18n-extract does this, so maybe it won't be difficult to reimplement?

Scan data-i18n-options in HTML

When data-i18n and data-i18n-options coexist in a DOM only data-i18n is recognized, suppose I wanted a translation with context, I couldn't get it with this tool

i18next-parser applying wrong translation value from original locale files

Suppose I have a key 'foo_bar' in my application and it is currently translated for locale EN but not for FR, ES (which are also supported locales in this app). The parser will grab the value from the EN locale and apply that value to FR, ES parsed locales.

I am trying to use the i18next-parser as a coverage report tool to see what key-value pairs still need translations for other locales other than our default english one. I was expecting keys that do not have have a translation in a particular locale to be parsed by the tool with an empty string as it's default value.

locales
│
└─── en
│  └─── translation.json
│
└─── fr
│  └─── translation.json
│
└─── es
     └─── translation.json

original locales file

locales/en/translation.json = {"foo_bar": "test translation", "welcome": "hello"}
locales/fr/translation.json = {"welcome": "bonjour"}
locales/es/translation.json = {"welcome": "hola"}

Expected

parsed locales file

locales/en/translation.json = {"foo_bar"": "test translation", "welcome": "hello"}
locales/fr/translation.json = {"foo_bar": "", "welcome": "bonjour"}
locales/de/translation.json = {"foo_bar": "", "welcome": "hola"}

Actual

parsed locales file

locales/en/translation.json = {"foo_bar": "test translation", "welcome": "hello"}
locales/fr/translation.json = {"foo_bar": "test translation",  "welcome": "bonjour"}
locales/de/translation.json = {"foo_bar": "test translation", "welcome": "hola"}

Include values as default translations

As already partially discussed in #13 and related to my implementation as described here http://stackoverflow.com/questions/26561879/i18next-best-practice/26737839?noredirect=1, I wonder if it will be possible to:

Parse i18n.t('namespace.example', 'Default value'); methods and insert Default value into the locales/translation files whenever namespace.example is still empy. And do the same for <span data-i18n="namespace.example">Default value</span> HTML tags.

I think it is way more easy to translate a JSON file that looks like:

"namespace" : {
   "example" : "Default value"
}

Than when it looks like:

"namespace" : {
   "example" : ""
}

Assuming that my implementation works correctly. My generated translation files always contain empty strings like in the latter example.

As a side benefit; then even when a lable is untranslated, the UI will at least show the default value (in most cases the English version) instead of an empty string.

EMFILE, too many open files

fs.js:427
  return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode);
                 ^
Error: EMFILE, too many open files '/me/project/app/assets/locales/en/translation.json'
  at Object.fs.openSync (fs.js:427:18)
  at Object.fs.readFileSync (fs.js:284:15)
  at Parser._flush (/me/project/node_modules/i18next-parser/index.js:154:54)
  at Parser.<anonymous> (_stream_transform.js:130:12)
  at Parser.g (events.js:175:14)
  at Parser.EventEmitter.emit (events.js:92:17)
  at finishMaybe (_stream_writable.js:354:12)
  at endWritable (_stream_writable.js:361:3)
  at Parser.Writable.end (_stream_writable.js:339:5)
  at Stream.onend (stream.js:79:10)
  at Stream.EventEmitter.emit (events.js:117:20)
  at end (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:116:39)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:62:17)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:56:14)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:56:14)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:56:14)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:56:14)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:56:14)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:56:14)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:56:14)
  at next (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:71:7)
  at /me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:85:7
  at /me/project/node_modules/gulp/node_modules/vinyl-fs/lib/src/bufferFile.js:8:5
  at fs.js:266:14
  at /me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/graceful-fs/graceful-fs.js:105:5
  at Object.oncomplete (fs.js:107:15)

Problem with : (colon) inside text

Hello,
I'm trying to parse text with : (colon) inside it.
For example: "Hello, I have: one, two, three"
Parser will separate this into 2 strings. "Hello, I have" and "one, two, three".
Any suggestion how to handle this case (without splitting into separated strings)?
Thanks

Doesn't escape context

Looks like this parser doesn't support context

example:

{
 "key": "text",
"key_male": "text male",
"key_female": "text female",
"key_male_plural": "text __count__ male"
}

in html

$.i18n.t("key", {context: "male"})

it will delete keys with male and female and move it to _old.json

I‘d like there is custom separator for the parser

sometimes, we will use custom separator for namespace or keys, for the use '.' and ':' may conficit with some text string.
I have use this parser for ourr project,and thanks for your greate job! In practice, we just encounter above problem.So I readed the source code,and add custom separator for our convienience.Also I fork it to my repositories, and commit my fix.
I'd like to share my fix to other people too.Can you check my code and apply this fix,please!

Parse full meteor app folder

Hi,

while trying to parse all meteor app folder got such a mistake:
[parse] /home/username/.meteor/packages/webapp/.1.1.6.6iir9q++os+web.browser+web.cordova/npm/node_modules/connect/node_modules/multiparty/node_modules/readable-stream/test/simple/test-stream2-writable.js
fs.js:113
throw er;
^
Error: Path must be a string without null bytes.
at nullCheck (fs.js:111:14)
at Object.fs.openSync (fs.js:501:3)
at Object.fs.writeFileSync (fs.js:1103:15)
at Transform._transform (/usr/lib/node_modules/i18next-parser/bin/cli.js:125:12)
at Transform._read (/usr/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_transform.js:184:10)
at Transform._write (/usr/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_transform.js:172:12)
at doWrite (/usr/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:238:10)
at writeOrBuffer (/usr/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:228:5)
at Transform.Writable.write (/usr/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:195:11)
at Parser.ondata (_stream_readable.js:540:20)



got an error on 1st launch

Hi,

thank you for awesome step towards i18next translation easiness!!!

However here is what I got on my 12.04 Ubuntu:

[mainuser@mainuser:~/test-app]$i18next ~/test-app/client/ -r -l en -f t,tt

i18next Parser

Target: /home/mainuser/test-app/client
Output: undefined

[parse] /home/mainuser/test-app/client/changeLang.html
[parse] /home/mainuser/test-app/client/changeLang.js
[parse] /home/mainuser/test-app/client/client.js
[parse] /home/mainuser/test-app/client/contact.html
[parse] /home/mainuser/test-app/client/contact.js
[parse] /home/mainuser/test-app/client/error.html
[parse] /home/mainuser/test-app/client/footer.html
[parse] /home/mainuser/test-app/client/header.html
[parse] /home/mainuser/test-app/client/home.html
[parse] /home/mainuser/test-app/client/how-it-works.html
[parse] /home/mainuser/test-app/client/index.html
[parse] /home/mainuser/test-app/client/loading.html
[parse] /home/mainuser/test-app/client/dashboard/adminPanel.html
[parse] /home/mainuser/test-app/client/dashboard/borrow.html
[parse] /home/mainuser/test-app/client/dashboard/dashboard.html
[parse] /home/mainuser/test-app/client/dashboard/dashboard.js
[parse] /home/mainuser/test-app/client/dashboard/haveToRegister.html
[parse] /home/mainuser/test-app/client/dashboard/lend.html

fs.js:427
return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode);
^
Error: ENOENT, no such file or directory '/home/mainuser/test-app/locales/en/translation.json'
at Object.fs.openSync (fs.js:427:18)
at Object.fs.writeFileSync (fs.js:966:15)
at Transform._transform (/home/mainuser/.nvm/v0.10.22/lib/node_modules/i18next-parser/bin/cli.js:100:12)
at Transform._read (/home/mainuser/.nvm/v0.10.22/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_transform.js:179:10)
at Transform._write (/home/mainuser/.nvm/v0.10.22/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_transform.js:167:12)
at doWrite (/home/mainuser/.nvm/v0.10.22/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:229:10)
at writeOrBuffer (/home/mainuser/.nvm/v0.10.22/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:219:5)
at Transform.Writable.write (/home/mainuser/.nvm/v0.10.22/lib/node_modules/i18next-parser/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:186:11)
at write (_stream_readable.js:583:24)
at flow (_stream_readable.js:592:7)

How to fix / avoid that?

Thanks a lot!

Bug with Trans component without children

1.0.0-beta6
cli

I'm trying to use Trans component as self-closing tag.

<Trans i18nKey="Foo" />

CLI just do nothing without any error.

<Trans i18nKey="Foo"></Trans>

This works fine

read error with CLI

Can't seem to get this to work. I'm on win7 using version 0.3.4. Throws an error trying to read in the locales dir. It will create the dir and leave it empty.

C:\l2>i18next "C:\l2"

i18next Parser
--------------
Input:  C:\l2
Output: C:\l2\locales


[parse] C:\l2\i18next-1.7.4.js
[parse] C:\l2\index.html

fs.js:432
  return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode);
                 ^
Error: ENOENT, no such file or directory 'C:\l2\locales\en\,
        reuseSuffix.json'
    at Object.fs.openSync (fs.js:432:18)
    at Object.fs.writeFileSync (fs.js:971:15)
    at Transform._transform (C:\Users\user\AppData\Roaming\npm\node_modules\
i18next-parser\bin\cli.js:127:12)
    at Transform._read (C:\Users\user\AppData\Roaming\npm\node_modules\i18ne
xt-parser\node_modules\through2\node_modules\readable-stream\lib\_stream_transfo
rm.js:184:10)
    at Transform._write (C:\Users\user\AppData\Roaming\npm\node_modules\i18n
ext-parser\node_modules\through2\node_modules\readable-stream\lib\_stream_transf
orm.js:172:12)
    at doWrite (C:\Users\user\AppData\Roaming\npm\node_modules\i18next-parse
r\node_modules\through2\node_modules\readable-stream\lib\_stream_writable.js:237
:10)
    at writeOrBuffer (C:\Users\user\AppData\Roaming\npm\node_modules\i18next
-parser\node_modules\through2\node_modules\readable-stream\lib\_stream_writable.
js:227:5)
    at Transform.Writable.write (C:\Users\user\AppData\Roaming\npm\node_modu
les\i18next-parser\node_modules\through2\node_modules\readable-stream\lib\_strea
m_writable.js:194:11)
    at write (_stream_readable.js:585:24)
    at flow (_stream_readable.js:594:7)

C:\l2>

Parse data-i18n attributes in html element

Hello,
I am trying to parse 'data-i18n' attributes from html files with custom regex:
i18next modules/ -o lang/ -r '[^a-zA-Z0-9](?:(?:t)|(?:i18n\\.t)|(?:data-i18n))(?:\\(|\\s|=)\\s*(?:(?:\'((?:(?:\\\\\')?[^\']+)+[^\\\\])\')|(?:"((?:(?:\\\\")?[^"]+)+[^\\\\])"))'.
It doesn't seem to work. How to do it properly?

Use the same option names as i18next does

I noticed a small difference between i18next and this library regarding the option names - i18next uses nsSeparator, whereas i18next-parser uses namespaceSeparator.

We should probably check other option names as well.

Support for extended handlebars helper

As described in http://i18next.com/pages/doc_templates.html, to use context and option variables in Handlebars, we need an extended helper

Handlebars.registerHelper('tr', function(context, options) { 
   var opts = i18n.functions.extend(options.hash, context);
   if (options.fn) opts.defaultValue = options.fn(context);

   var result = i18n.t(opts.key, opts);

   return new Handlebars.SafeString(result);
});

Is it sensible to support this along with the standard t?

JSON.parse error management

If JSON.parse crashes, so does the parser.

SyntaxError: Unexpected end of input
  at Object.parse (native)
  at Parser._flush (/Users/kalema/Sites/BookMyCoach/bmc_frontend/node_modules/i18next-parser/index.js:154:44)

Handle dynamically computed keys

Idea 1
Have a file keys.json with translation keys we want to force or remove in the library:

{
    add: {
        namespace: {
            parent_key: {
                child_key: ""
            }
        }
    },
    removeEmpty: true,
    remove: {
        namespace: {
            bla: ""
        }
    }
}

We could also have a file per namespace : translation_keys.json, namespace_keys.json...

Do not sort keys

Keys are currently sorted alphabetically translation.json:

{
  "A": "a",
  "B": "b",
  "C": "c"
}

Would be nice to have the keys without order, as they appear inside the source code (e.g. in the order they are parsed):

{
  "C": "c",
  "A": "a",
  "B": "b"
}

This way the keys follow the source code logic.

I even believe the "source code order" should be the default one and a --alphabetical-order option should be added.

Keys as content

I'm using i18next with React (react-i18next), and this parser is working fine, except that the i18next keys are the same as the content. Like this example.

Right now, when I run the parser, the new keys stores empty values in the JSON files, as default and I have to copy / paste the key to the value. Is there any way to set the value to the same as the key?

Another nice feature, would be to set a default value, so it's easier to see what hasn't been translated yet

Examples

Currently

{
  "Hello world, this is some text": "",
  "Another internationalized string": ""
}

Expected

{
  "Hello world, this is some text": "Hello world, this is some text",
  "Another internationalized string": "Another internationalized string"
}

Default Value

{
  "Hello world, this is some text": "__UNTRANSLATED_VALUE__",
  "Another internationalized string": "__UNTRANSLATED_VALUE__"
}

How to keep my old keys

With default gulp setup as
Gulpfile.coffee

gulp.task 'i18n', ->
  gulp.src 'src/features/**/*.html'
      .pipe i18next {locales: ['en', 'de', 'ru', 'en-US'], output:'translation.json'}
      .on 'reading', (file)->
        console.log 'looking for $.i18n.t function with keys in ' + file
      .pipe gulp.dest 'src/locales'

it completely overrides whole directories without even saving the old keys.

What is wrong? How do I keep old keys + keeping my actual values.
it overrides with empty values.

Starting from version 0.8.0 scanning i18n.t(var1 + var2) is broken

Scanning

  i18n.t(prefix + param);

Will result in:

[exec] [error] i18next-parser does not support variables in translation functions, use a string literal: t(prefix + param)

Version 0.7.0 or lower doesn't have this issue.
Is this construction is deliberately not supported anymore or did something break ?

btw thanks for this great scanner !

Some nice to have features

I'm using your tools to automatize the translation process. Like to say thank you at first.
I'd like to share some nice to have features for the future.

  • Write result to the STDOUT
  • If you write to the hard drive, could force the file creation even if there is no key in the file. (empty json)

Hope it was helpful.

Using keySeparator: false in the config breaks things

Version: 1.0.0-beta-6

I'm using "keySeparator": false so I can use strrings rather than key: t('Hello! How are you?') instead of t('welcome.message').

The reason is so that this can be extracted: t('Logging in...'). With keySeparator set to false, the dots should not cause problems.

However doing that causes a bunch of weird files to popup: locales/en/translationfalseKey1.json, locales/en/translationfalseKey2.json, ... One different file per language and per individual key.

Here is my JSON:

{
  "keySeparator": false,
  "namespaceSeparator": false,
  "lexers": {
    "js": ["JavascriptLexer", "JsxLexer"]
  },
  "sort": true,
  "locales": ["fr","en"]
}

Here is how I call with the cli: i18next 'app/**/*.js' -c locales/i18next-parser.config.json -o .

Current workaround: use something like "__NO_KEY_SEPARATOR__" instead of false.

in jade file attributes preceded with ( are not matched

For example:

.my-title(t="home.title") is not matched by attribute regex on line 144 of index.js
var attributeWithValueRegex = new RegExp( '(?:\\s+' + attributes + '=")([^"]*)(?:")', 'gi' );
the regex requires a preceding space (as is always the case in html)

suggested fix:
instead of space class \s use non-word class \W
var attributeWithValueRegex = new RegExp( '(?:\\W+' + attributes + '=")([^"]*)(?:")', 'gi' );

License

Under which license is this code published? MIT? Apache-2.0?

how to parse i18n.t("some key") ?

Hi,

I try to parse next code in .js file:
i18n.t("Change lang 2");

I've tried
[mainuser@mainuser:~]$i18next ~/my-app/client/ -r -l en,ru,ua -f t,tt,i18n.t
and
[mainuser@mainuser:~]$i18next ~/my-app/client/ -r -l en,ru,ua -f t,tt

both do nothing with that code form .js file.

What I'm doing wrong?

Thanks a lot, Karel! This awesome parser helps me a lot!

Gulp syntax error: Unexpected token import

  • Is this a bug or a feature request?
    Bug

  • What version are you running (0.x or 1.x)?
    1.0.0-beta9

  • Which runner (cli, gulp...)?
    gulp

What I'm trying to do?

Extract translations from JS and HTML (pseudo handlebars templates) files

How am I doing it?

Here is my config (extracted from gulpfile.js):

{
  locales: ['en', 'fr'],
  output: '../locales',
  functions: ['__'],
  createOldCatalogs: false
}

The command I'm running:

gulp i18next

What was the result?

An error:

.../node_modules/i18next-parser/src/index.js:1
(function (exports, require, module, __filename, __dirname) { import { dotPathToHash, mergeHashes, populateHash } from './helpers'
                                                              ^^^^^^

SyntaxError: Unexpected token import
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:616:28)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (.../gulpfile.js:34:17)

What was I expecting?

My translations in output folder.

Tracking file path references for each key

(I opened a related issue for the i18next-gettext-converter here: i18next/i18next-gettext-converter#55)

tldr:

Wanting to add the functionality to track file path references to each key for the purpose of having those comments end up in .po files for our translators to get better context as-needed, requiring modifications both in this repo and in the i18next-gettext-converter, and curious how amenable you'd be to merging that sort of thing in?


long form:

I've been helping with some efforts to internationalize a medium-large size Ember app, and since gettext doesn't play nice with html and handlebars, we needed these two repos to generate our translation strings and convert between .json -> .po and vice versa.

But we ran into a slight issue, our translators will need file path references for each key, as comments in the .po files (something that gettext does by default in a typical i18n workflow, except gettext has line numbers, which we won't be needing) and although the gettext-converter does have some* ability to add comments, the data structure output by this tool doesn't keep track of those file paths.

Initially I modified the i18next-parser to output something that looks more like:

{
  'some key': {
    msgstr: '',
    paths: ['app/templates/error.hbs', '../fake/path']
  }
}

Where paths is an array of each file path location that a given key is found at, in case you have the same key in multiple places in your source code.

rather than the current output:

{
  'some key': ''
}

and I modified the gettext-converter to handle both formats dynamically while converting from .json -> .po, so you can have keys with file path information and others without, in the same file.

Currently, my little demo of this working involves either symlinking my local changes or pointing our app at my own forks of these two repos, which is less than ideal. I'm hoping to get these changes merged in at some point, so in the past couple days I've gone through and modified my code to use an additional flag '-t, --track-paths', so it generates the original data structure unless you include that flag and set it to true.

To further support our workflow, since the i18next runtime still needs the original format, I also made it so that using '--track-paths' will prepend '/tmp' to the output location of the translation file but not the translation_old file. So rather than running this tool to get our .json, and then running the converter to get our .po - we are running this tool with --track-paths true to get our new .json format, then running the converter on the output /tmp/locales/en/translation.json to get our .po file, and finally running the converter a second time to go from .po -> .json so that we're back in the original format, and now back in the correct directory as well.

Ideally we'd like to merge these changes back into the repos, rather than maintaining our own forks. I'm doing my best to ensure that this is all opt-in behavior and that it doesn't affect any other workflows that people may be using. Just wanted to put the feelers out there before I get my PR's up. Any feedback is appreciated!

Gulp task sometimes crashes unexpectidly

Error log:

undefined:0

^
SyntaxError: Unexpected end of input
  at Object.parse (native)
  at Parser._flush (/me/project/node_modules/i18next-parser/index.js:154:44)
  at Parser.<anonymous> (_stream_transform.js:130:12)
  at Parser.g (events.js:175:14)
  at Parser.EventEmitter.emit (events.js:92:17)
  at finishMaybe (_stream_writable.js:354:12)
  at endWritable (_stream_writable.js:361:3)
  at Parser.Writable.end (_stream_writable.js:339:5)
  at Stream.onend (stream.js:79:10)
  at Stream.EventEmitter.emit (events.js:117:20)
  at end (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:116:39)
  at queueData (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:62:17)
  at next (/me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:71:7)
  at /me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/map-stream/index.js:85:7
  at /me/project/node_modules/gulp/node_modules/vinyl-fs/lib/src/bufferFile.js:8:5
  at fs.js:266:14
  at /me/project/node_modules/gulp/node_modules/vinyl-fs/node_modules/graceful-fs/graceful-fs.js:105:5
  at Object.oncomplete (fs.js:107:15)

keep the values in the translation file

Hello, it's me again.
Was wondering, maybe I am doing something wrong, but the values in the translation.json file which I filled out manually are always overwritten by empty strings when I re-parse my html/js.

Is it a normal behavior, and if so, is it possible to keep the values somehow?
Thank you

Templated strings not parsed

Version: 1.0.0-beta-6

I'm not sure if this is a bug (not handling all quote formats) or a feature (handling new quote format).

This gets parsed properly:

t('Hello world')

Neither of those gets parsed:

t(`Hello world`)
t(`Hello ${name}`)

Config file:

{
  "keySeparator": "__NO_KEY_SEPARATOR__",
  "namespaceSeparator": "|",
  "lexers": {
    "js": ["JavascriptLexer", "JsxLexer"]
  },
  "reactNamespace": true,
  "sort": true,
  "locales": ["en", "fr"]
}

Usage through cli.

Doesn't handle context via { context: 'male' }

We are trying to keep our translations in "real" english to have a acceptable fallback if a string isn't translated (Do you want to continue ? looks better than modal.question.continue when the string is not available in your language at the moment).
i18next doesn't handle i18n.t('From', { context: 'date' }) but i18n.t('From::date'). It's an issue when you rely on key for fallback.
I quickly look at the code, and it seems we don't get any options atm.

filterFolder exclude not working

This example from the readme.md does not appear to be working in the cli:

i18next /path/to/file/or/dir -filterFolder *.hbs,*.js -filterFolder !.git

Whenever I try to exclude a folder I get this error:

-bash: !.git: event not found

Parser hangs when a translation key ends with an escaped inverted comma followed by a dot

Hey,

I've found that the parser cannot handles translation keys of the following form

i18n.t('Lorem ipsum dolor sit \'amet\'.'); 

The parser will hang if a file containing this string is passed to it. The problem is the final dot; remove this and the parse works correctly.

The issue is in the regular expression on line 99 of index.js. For the function i18n.t it creates the following regex

[^a-zA-Z0-9_](?:(?:i18n\.t))(?:\(|\s)\s*(?:(?:'((?:(?:\\')?[^']+)+[^\\])')|(?:"((?:(?:\\")?[^"]+)+[^\\])"))

With this regex, you can see the problem here http://www.regexr.com/39p62 We get a timeout; remove the dot (or add characters after it) and we get 1 match.

I'll try to have a look at the regex tomorrow and see if I can resolve the problem; debugging regexes aren't really my forté though, so I thought I'd raise it here first :)

More options for tweaking file names

For another i18n library I'm trying to use in combination with this (awesome) scanner, it's required the JSON files are in a structure like i18n/en.i18n.json. However, it seems besides the folder the files are placed I have no ability to tweak the file naming of the generated JSON files. I have browsed through the source, but cannot find if/how to do this. Is this possible, and if not, could it be made possible you think?

On cli usage -l de extracts to folder fr on 1.0.0 beta2

Hello there, i have this problem i am using the cli with this command:
i18next "src/**/*.js" -o . -l en,de
and i get ./locales/en and ./locales/fr
Is there an issue with the german language or am i doing something wrong
Thank you

Extraction behavior is not correct when paring the react file in <Trans> component.

  • Is this a bug or a feature request?
    • bug
  • What version are you running (0.x or 1.x)?
    • 1.x (1.0.0-beta2)
  • Which runner (cli, gulp...)?
    • cli

What I'm trying to do?

I try to extract json files from react-i18next examples (react_withHOC & react_renderProps)

How am I doing it?

Here is my config file:

// i18next-parser.config.js
module.exports = {
  reactNamespace: true,
  locales: ['en', 'de'],
  output: './locales'
};

The command I'm running:

i18next "src/**/*.{js,jsx}" -c ./i18next-parser.config.js

What was the result?

both en and de are

// translation_old.json
{}

// translation.json
{
  "title": "",
  "description": {
    "part2": ""
  }
}

What was I expecting?

Need to extract the text from <Trans> too. But description.part1 key is missing.

{
  "title": "",
  "description": {
    "part1": "",
    "part2": ""
  }
}

Problem understanding treatment of plural forms

Hey,

I have a problem understanding how the parser handles plural forms.

Ordinarily, I would crate a plural key in the following way

{
 "contacts" : "__total__ contact",
 "contacts_plural" : "__total__ contacts"
}

And use it in a script via i18n.t('contacts', {total: total}), with the plural selected by i18next according to the value of total.

The parser does not detect such usages (I get only a "contacts" key in the outputted JSON). (I don't see how it could, in fact, detect these and distinguish them from translations that are just parameterized). Is there a special syntax I should be using so the parser can detect plurals that are declared in this fashion?

Moreover, in this parser unit test it appears to know that the second plural should have a special form for the value "12". How is this determined from the input text "asd t('test_plural:first') t('test_plural:second')"?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.