Skip to content

Commit

Permalink
Merge pull request #61 from i18next/trans-no-key
Browse files Browse the repository at this point in the history
Add handling of Trans component without key (#58)
  • Loading branch information
cheton authored Nov 25, 2017
2 parents e478ceb + 10eb9a0 commit 5cbc63b
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 32 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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
// <div data-i18n="key"></div>
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 8 additions & 1 deletion examples/i18next-scanner.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
74 changes: 59 additions & 15 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ const defaults = {
extensions: ['.js', '.jsx']
},

trans: { // Trans component (https://github.com/i18next/react-i18next)
// list: ['Trans'], // TODO
extensions: ['.js', '.jsx'],
// key: 'i18nKey', // TODO
fallbackKey: false
},

lngs: ['en'], // array of supported languages
fallbackLng: 'en', // language to lookup key if not found while calling `parser.get(key, { lng: '' })`

Expand Down Expand Up @@ -107,11 +114,29 @@ 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
/* 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);
}

// Resource
if (_.isUndefined(_.get(options, 'resource.loadPath'))) {
_.set(options, 'resource.loadPath', defaults.resource.loadPath);
}
Expand Down Expand Up @@ -266,7 +291,6 @@ class Parser {
const re = new RegExp(pattern, 'gim');

let r;

while ((r = re.exec(content))) {
const options = {};
const full = r[0];
Expand Down Expand Up @@ -348,24 +372,32 @@ class Parser {
// Parses translation keys from `Trans` components in JSX
// <Trans i18nKey="some.key">Default text</Trans>
parseTransFromString(content, opts = {}, customHandler = null) {
const pattern = '<Trans[^]*?i18nKey="([^"]+)"[^]*?>([^]*?)</\\s*Trans\\s*>';
const re = new RegExp(pattern, 'gim');
let setter = this.set.bind(this);

if (_.isFunction(opts)) {
setter = opts;
customHandler = opts;
opts = {};
}

const reTrans = new RegExp('<Trans([^]*?)>([^]*?)</\\s*Trans\\s*>', 'gim');
const reTransKey = 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 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),
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
Expand Down Expand Up @@ -495,6 +527,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)
Expand Down Expand Up @@ -536,6 +569,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,
Expand Down
25 changes: 15 additions & 10 deletions test/fixtures/app.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
const mycomp = () => (
<Trans i18nKey="key1">Key 1 default</Trans>
<Trans i18nKey="key2">
Key 2
default value
</Trans>
<div>
<Trans i18nKey="key1">Key 1 default</Trans>
<Trans i18nKey="key2">
Key 2
default value
</Trans>

<Trans i18nKey="key3">This is a <strong>test</strong></Trans>
<Trans i18nKey="key4" count={count}>You have {{count}} apples</Trans>
<Trans i18nKey="key5">You have <a>one <i>very</i> bad</a> apple</Trans>
<Trans i18nKey="key6">This is a <strong>{{test}}</strong></Trans>
)
<Trans i18nKey="key3">This is a <strong>test</strong></Trans>
<Trans i18nKey="key4" count={count}>You have {{count}} apples</Trans>
<Trans i18nKey="key5">You have <a>one <i>very</i> bad</a> apple</Trans>
<Trans i18nKey="key6">This is a <strong>{{test}}</strong></Trans>
<Trans>key7 default</Trans>
<Trans count={1}>key8 default {{count}}</Trans>
<Trans>We can use Trans without i18nKey="..." as well!</Trans>
</div>
)
46 changes: 44 additions & 2 deletions test/parser.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from 'fs';
import path from 'path';
import sha1 from 'sha1';
import { test } from 'tap';
import { Parser } from '../src';

Expand Down Expand Up @@ -97,9 +98,47 @@ 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: {
fallbackKey: true
},
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</1>",
"key4": "You have <1>{{count}}</1> apples",
"key5": "You have <1>one <1>very</1> bad</1> apple",
"key6": "This is a <1><0>{{test}}</0></1>",
"key7 default": "key7 default",
"key8 default <1>{{count}}</1>": "key8 default <1>{{count}}</1>",
"We can use Trans without i18nKey=\"...\" as well!": "We can use Trans without i18nKey=\"...\" as well!"
}
}
});
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'
});

Expand All @@ -113,7 +152,10 @@ test('Parse Trans component', (t) => {
"key3": "This is a <1>test</1>",
"key4": "You have <1>{{count}}</1> apples",
"key5": "You have <1>one <1>very</1> bad</1> apple",
"key6": "This is a <1><0>{{test}}</0></1>"
"key6": "This is a <1><0>{{test}}</0></1>",
"4f516979d203813c6bf4ea56043719e11095744f": "key7 default",
"8f5c444dd42fe9a3e42a8ab3a677e04a4a708105": "key8 default <1>{{count}}</1>",
"09e944775f89d688fd87cf7abc95a737dd4c54f6": "We can use Trans without i18nKey=\"...\" as well!"
}
}
});
Expand Down

0 comments on commit 5cbc63b

Please sign in to comment.