From 57bc5065fa29350222b96c019eda8cd388467b37 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 25 Nov 2017 19:46:25 +0800 Subject: [PATCH 1/2] Support for parsing Trans component without key --- src/index.js | 3 ++ src/parser.js | 69 +++++++++++++++++++++++++++++++++---------- test/fixtures/app.jsx | 25 +++++++++------- test/parser.js | 10 ++++++- 4 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/index.js b/src/index.js index dd9d1b4..6245001 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,9 @@ const transform = (parser, customTransform) => { if (includes(get(options, 'func.extensions'), extname)) { // Parse translation function (e.g. i18next.t('key')) parser.parseFuncFromString(content); + } + + if (includes(get(options, 'trans.extensions'), extname)) { // Look for Trans components in JSX parser.parseTransFromString(content); } diff --git a/src/parser.js b/src/parser.js index 32b7623..3e62c8a 100644 --- a/src/parser.js +++ b/src/parser.js @@ -24,6 +24,13 @@ const defaults = { extensions: ['.js', '.jsx'] }, + trans: { // Trans component (https://github.com/i18next/react-i18next) + list: ['Trans'], + extensions: ['.js', '.jsx'], + key: 'i18nKey', + fallbackKey: false + }, + lngs: ['en'], // array of supported languages fallbackLng: 'en', // language to lookup key if not found while calling `parser.get(key, { lng: '' })` @@ -107,11 +114,25 @@ const transformOptions = (options) => { if (_.isUndefined(_.get(options, 'func.list'))) { _.set(options, 'func.list', defaults.func.list); } - - // Resource if (_.isUndefined(_.get(options, 'func.extensions'))) { _.set(options, 'func.extensions', defaults.func.extensions); } + + // Trans + if (_.isUndefined(_.get(options, 'trans.list'))) { + _.set(options, 'trans.list', defaults.trans.list); + } + if (_.isUndefined(_.get(options, 'trans.extensions'))) { + _.set(options, 'trans.extensions', defaults.trans.extensions); + } + if (_.isUndefined(_.get(options, 'trans.key'))) { + _.set(options, 'trans.key', defaults.trans.key); + } + if (_.isUndefined(_.get(options, 'trans.fallbackKey'))) { + _.set(options, 'trans.fallbackKey', defaults.trans.fallbackKey); + } + + // Resource if (_.isUndefined(_.get(options, 'resource.loadPath'))) { _.set(options, 'resource.loadPath', defaults.resource.loadPath); } @@ -266,7 +287,6 @@ class Parser { const re = new RegExp(pattern, 'gim'); let r; - while ((r = re.exec(content))) { const options = {}; const full = r[0]; @@ -348,24 +368,31 @@ class Parser { // Parses translation keys from `Trans` components in JSX // Default text parseTransFromString(content, opts = {}, customHandler = null) { - const pattern = '([^]*?)'; - const re = new RegExp(pattern, 'gim'); - let setter = this.set.bind(this); - if (_.isFunction(opts)) { - setter = opts; + customHandler = opts; opts = {}; } + const reTrans = new RegExp('([^]*?)', 'gim'); + const reKey = new RegExp('[^]*i18nKey="([^"]+)"[^]*', 'im'); + let r; - while ((r = re.exec(content))) { - const key = _.trim(r[1]); - let fragment = _.trim(r[2]); - fragment = fragment.replace(/\s+/g, ' '); - const defaultValue = jsxToText(fragment); - const options = { defaultValue }; - setter(key, options); + while ((r = reTrans.exec(content))) { + const key = _.trim(ensureArray(String(r[1] || '').match(reKey))[1] || ''); + const fragment = _.trim(r[2]).replace(/\s+/g, ' '); + const options = { + defaultValue: jsxToText(fragment), + fallbackKey: opts.fallbackKey || this.options.trans.fallbackKey + }; + + if (customHandler) { + customHandler(key, options); + continue; + } + + this.set(key, options); } + return this; } // Parses translation keys from `data-i18n` attribute in HTML @@ -495,6 +522,7 @@ class Parser { // Set translation key with an optional defaultValue to i18n resource store // @param {string} key The translation key // @param {object} [options] The options object + // @param {boolean|function} [options.fallbackKey] When the key is missing, pass `true` to return `options.defaultValue` as key, or pass a function to return user-defined key. // @param {string} [options.defaultValue] defaultValue to return if translation not found // @param {number} [options.count] count value used for plurals // @param {string} [options.context] used for contexts (eg. male) @@ -536,6 +564,17 @@ class Parser { key = parts[1]; } + if (!key && options.fallbackKey === true) { + key = options.defaultValue; + } + if (!key && typeof options.fallbackKey === 'function') { + key = options.fallbackKey(ns, options.defaultValue); + } + if (!key) { + // Ignore empty key + return; + } + const { lngs, context, diff --git a/test/fixtures/app.jsx b/test/fixtures/app.jsx index f6ab9d2..4e46c38 100644 --- a/test/fixtures/app.jsx +++ b/test/fixtures/app.jsx @@ -1,12 +1,17 @@ const mycomp = () => ( - Key 1 default - - Key 2 - default value - +
+ Key 1 default + + Key 2 + default value + - This is a test - You have {{count}} apples - You have one very bad apple - This is a {{test}} -) \ No newline at end of file + This is a test + You have {{count}} apples + You have one very bad apple + This is a {{test}} + key7 default + key8 default {{count}} + We can use Trans without i18nKey="..." as well! +
+) diff --git a/test/parser.js b/test/parser.js index 68dece7..da2b483 100644 --- a/test/parser.js +++ b/test/parser.js @@ -100,6 +100,11 @@ test('Parse translation function', (t) => { test('Parse Trans component', (t) => { const parser = new Parser({ lngs: ['en'], + trans: { + fallbackKey: true + }, + nsSeparator: false, + keySeparator: false, fallbackLng: 'en' }); @@ -113,7 +118,10 @@ test('Parse Trans component', (t) => { "key3": "This is a <1>test", "key4": "You have <1>{{count}} apples", "key5": "You have <1>one <1>very bad apple", - "key6": "This is a <1><0>{{test}}" + "key6": "This is a <1><0>{{test}}", + "key7 default": "key7 default", + "key8 default <1>{{count}}": "key8 default <1>{{count}}", + "We can use Trans without i18nKey=\"...\" as well!": "We can use Trans without i18nKey=\"...\" as well!" } } }); From 10eb9a05a3e092ab4ed0e058ddc103a150b58171 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 25 Nov 2017 20:13:04 +0800 Subject: [PATCH 2/2] Prepare for v2.2.0 --- README.md | 22 +++++++++++++++--- examples/i18next-scanner.config.js | 9 +++++++- package.json | 2 +- src/parser.js | 13 +++++++---- test/parser.js | 36 +++++++++++++++++++++++++++++- 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 25b73ab..07d6e9e 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,14 @@ module.exports = { options: { debug: true, func: { - list: ['i18next.t', 'i18n.t'] + list: ['i18next.t', 'i18n.t'], + extensions: ['.js', '.jsx'] + }, + trans: { + extensions: ['.js', '.jsx'], + fallbackKey: function(ns, value) { + return value; + } }, lngs: ['en','de'], ns: [ @@ -171,8 +178,9 @@ parser // Parse Trans component content = fs.readFileSync('/path/to/app.jsx', 'utf-8'); parser - .parseFuncFromString(content, customHandler) // pass a custom handler - .parseFuncFromString(content); // use default options and handler + .parseTransFromString(content, customHandler) // pass a custom handler + .parseTransFromString(content, { fallbackKey: true }) // Use fallback key when key is missing + .parseTransFromString(content); // use default options and handler // Parse HTML Attribute //
@@ -305,6 +313,14 @@ Parse translation key from the [Trans component](https://github.com/i18next/reac ```js parser.parseTransFromString(content); +parser.parseTransFromString(content, { fallbackKey: true }); + +parser.parseTransFromString(content, { + fallbackKey: function(ns, value) { + return value; + } +}); + parser.parseTransFromString(content, function(key, options) { options.defaultValue = key; // use key as the value parser.set(key, options); diff --git a/examples/i18next-scanner.config.js b/examples/i18next-scanner.config.js index d42f620..4d04c8b 100644 --- a/examples/i18next-scanner.config.js +++ b/examples/i18next-scanner.config.js @@ -5,7 +5,14 @@ module.exports = { options: { debug: true, func: { - list: ['i18next.t', 'i18n.t'] + list: ['i18next.t', 'i18n.t'], + extensions: ['.js', '.jsx'] + }, + trans: { + extensions: ['.js', '.jsx'], + fallbackKey: (ns, value) => { + return value; + } }, lngs: ['en','de'], ns: [ diff --git a/package.json b/package.json index 2861ee8..59abdeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18next-scanner", - "version": "2.1.5", + "version": "2.2.0", "description": "Scan your code, extract translation keys/values, and merge them into i18n resource files.", "homepage": "https://github.com/i18next/i18next-scanner", "author": "Cheton Wu ", diff --git a/src/parser.js b/src/parser.js index 3e62c8a..f2216e2 100644 --- a/src/parser.js +++ b/src/parser.js @@ -25,9 +25,9 @@ const defaults = { }, trans: { // Trans component (https://github.com/i18next/react-i18next) - list: ['Trans'], + // list: ['Trans'], // TODO extensions: ['.js', '.jsx'], - key: 'i18nKey', + // key: 'i18nKey', // TODO fallbackKey: false }, @@ -119,15 +119,19 @@ const transformOptions = (options) => { } // Trans + /* TODO if (_.isUndefined(_.get(options, 'trans.list'))) { _.set(options, 'trans.list', defaults.trans.list); } + */ if (_.isUndefined(_.get(options, 'trans.extensions'))) { _.set(options, 'trans.extensions', defaults.trans.extensions); } + /* TODO if (_.isUndefined(_.get(options, 'trans.key'))) { _.set(options, 'trans.key', defaults.trans.key); } + */ if (_.isUndefined(_.get(options, 'trans.fallbackKey'))) { _.set(options, 'trans.fallbackKey', defaults.trans.fallbackKey); } @@ -374,11 +378,12 @@ class Parser { } const reTrans = new RegExp('([^]*?)', 'gim'); - const reKey = new RegExp('[^]*i18nKey="([^"]+)"[^]*', 'im'); + const reTransKey = new RegExp('[^]*i18nKey="([^"]+)"[^]*', 'im'); let r; while ((r = reTrans.exec(content))) { - const key = _.trim(ensureArray(String(r[1] || '').match(reKey))[1] || ''); + const transKey = ensureArray(String(r[1] || '').match(reTransKey))[1]; + const key = _.trim(transKey || ''); const fragment = _.trim(r[2]).replace(/\s+/g, ' '); const options = { defaultValue: jsxToText(fragment), diff --git a/test/parser.js b/test/parser.js index da2b483..ae3f4e2 100644 --- a/test/parser.js +++ b/test/parser.js @@ -1,5 +1,6 @@ import fs from 'fs'; import path from 'path'; +import sha1 from 'sha1'; import { test } from 'tap'; import { Parser } from '../src'; @@ -97,7 +98,7 @@ test('Parse translation function', (t) => { t.end(); }); -test('Parse Trans component', (t) => { +test('Parse Trans component #1', (t) => { const parser = new Parser({ lngs: ['en'], trans: { @@ -128,6 +129,39 @@ test('Parse Trans component', (t) => { t.end(); }); +test('Parse Trans component #2', (t) => { + const parser = new Parser({ + lngs: ['en'], + trans: { + fallbackKey: (ns, value) => { + return sha1(value); // return a sha1 as the key + } + }, + nsSeparator: false, + keySeparator: false, + fallbackLng: 'en' + }); + + const content = fs.readFileSync(path.resolve(__dirname, 'fixtures/app.jsx'), 'utf-8'); + parser.parseTransFromString(content); + t.same(parser.get(), { + en: { + translation: { + "key1": "Key 1 default", + "key2": "Key 2 default value", + "key3": "This is a <1>test", + "key4": "You have <1>{{count}} apples", + "key5": "You have <1>one <1>very bad apple", + "key6": "This is a <1><0>{{test}}", + "4f516979d203813c6bf4ea56043719e11095744f": "key7 default", + "8f5c444dd42fe9a3e42a8ab3a677e04a4a708105": "key8 default <1>{{count}}", + "09e944775f89d688fd87cf7abc95a737dd4c54f6": "We can use Trans without i18nKey=\"...\" as well!" + } + } + }); + t.end(); +}); + test('Parse HTML attribute', (t) => { const parser = new Parser({ lngs: ['en'],