From 6895ef4b6635a179c6d98420ca8059e034b1296a Mon Sep 17 00:00:00 2001 From: Hyeonsu Lee Date: Thu, 2 Dec 2021 02:08:12 +0900 Subject: [PATCH 1/2] Support `keyPrefix` of `useTranslation` hook --- src/lexers/javascript-lexer.js | 28 ++++++++++++++++++++++ src/lexers/jsx-lexer.js | 5 +++- src/transform.js | 7 +++++- test/gulp/gulp.test.js | 18 ++++++++++++++ test/parser.test.js | 38 +++++++++++++++++++++++++++--- test/templating/keyPrefix-hook.jsx | 19 +++++++++++++++ 6 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 test/templating/keyPrefix-hook.jsx diff --git a/src/lexers/javascript-lexer.js b/src/lexers/javascript-lexer.js index a1d98db8..3ecd246a 100644 --- a/src/lexers/javascript-lexer.js +++ b/src/lexers/javascript-lexer.js @@ -49,6 +49,17 @@ export default class JavascriptLexer extends BaseLexer { return keys } + setKeyPrefixes(keys) { + if (this.keyPrefix) { + return keys.map((key) => ({ + ...key, + keyPrefix: this.keyPrefix, + })) + } + + return keys + } + extract(content, filename = '__default.js') { const keys = [] @@ -117,8 +128,25 @@ export default class JavascriptLexer extends BaseLexer { node.arguments.length ) { const { text, elements } = node.arguments[0] + + // useTranslation if (text) { this.defaultNamespace = text + const optionsArgument = node.arguments[1] + + if ( + optionsArgument && + optionsArgument.kind === ts.SyntaxKind.ObjectLiteralExpression + ) { + const node = optionsArgument.properties.find( + (p) => p.name.escapedText === 'keyPrefix' + ) + if (node != null) { + const keyPrefixValue = node.initializer.text + this.keyPrefix = keyPrefixValue + } + } + // withTranslation } else if (elements && elements.length) { this.defaultNamespace = elements[0].text } diff --git a/src/lexers/jsx-lexer.js b/src/lexers/jsx-lexer.js index d9ace4e5..b8a6ab79 100644 --- a/src/lexers/jsx-lexer.js +++ b/src/lexers/jsx-lexer.js @@ -55,7 +55,10 @@ export default class JsxLexer extends JavascriptLexer { ) parseTree(sourceFile) - return this.setNamespaces(keys) + const keysWithNamespace = this.setNamespaces(keys) + const keysWithPrefixes = this.setKeyPrefixes(keysWithNamespace) + + return keysWithPrefixes } jsxExtractor(node, sourceText) { diff --git a/src/transform.js b/src/transform.js index 039eaf96..819b3a2f 100644 --- a/src/transform.js +++ b/src/transform.js @@ -46,7 +46,7 @@ export default class i18nTransform extends Transform { this.options.i18nextOptions = { ...options.i18nextOptions, pluralSeparator: this.options.pluralSeparator, - nsSeparator: this.options.namespaceSeparator + nsSeparator: this.options.namespaceSeparator, } if (this.options.keySeparator === false) { @@ -108,6 +108,11 @@ export default class i18nTransform extends Transform { for (const entry of entries) { let key = entry.key + + if (entry.keyPrefix) { + key = entry.keyPrefix + this.options.keySeparator + key + } + const parts = key.split(this.options.namespaceSeparator) // make sure we're not pulling a 'namespace' out of a default value diff --git a/test/gulp/gulp.test.js b/test/gulp/gulp.test.js index ee961b07..802e45cf 100644 --- a/test/gulp/gulp.test.js +++ b/test/gulp/gulp.test.js @@ -48,6 +48,18 @@ describe('gulp plugin', function () { assert.strictEqual(error.code, 'ENOENT') } + const enKeyPrefix = await fs.readJson( + path.resolve(__dirname, './locales/en/key-prefix.json') + ) + + try { + await fs.readJson( + path.resolve(__dirname, './locales/en/key-prefix_old.json') + ) + } catch (error) { + assert.strictEqual(error.code, 'ENOENT') + } + const enTranslation = await fs.readJson( path.resolve(__dirname, './locales/en/translation.json') ) @@ -113,6 +125,12 @@ describe('gulp plugin', function () { 'test-1': '', 'test-2': '', }) + assert.deepEqual(enKeyPrefix, { + 'test-prefix': { + foo: '', + bar: '', + }, + }) assert.deepEqual(enTranslation, { fifth: 'bar', fifth_male: '', diff --git a/test/parser.test.js b/test/parser.test.js index 11ebcb3e..758f29b9 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -380,6 +380,35 @@ describe('parser', () => { i18nextParser.end(fakeFile) }) + it('applies useTranslation keyPrefix globally', (done) => { + let result + const i18nextParser = new i18nTransform() + const fakeFile = new Vinyl({ + contents: fs.readFileSync( + path.resolve(__dirname, 'templating/keyPrefix-hook.jsx') + ), + path: 'file.jsx', + }) + const expected = { + 'test-prefix': { + foo: '', + bar: '', + }, + } + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith(path.normalize('en/key-prefix.json'))) { + result = JSON.parse(file.contents) + } + }) + i18nextParser.on('end', () => { + assert.deepEqual(result, expected) + done() + }) + + i18nextParser.end(fakeFile) + }) + it('handles escaped single and double quotes', (done) => { let result const i18nextParser = new i18nTransform() @@ -750,7 +779,7 @@ describe('parser', () => { }) afterEach(() => { - console.log.restore(); + console.log.restore() }) describe('with defaultResetLocale', () => { @@ -1383,7 +1412,7 @@ describe('parser', () => { it('generates plurals according to compatibilityJSON value', (done) => { let result const i18nextParser = new i18nTransform({ - i18nextOptions: { compatibilityJSON: 'v3' } + i18nextOptions: { compatibilityJSON: 'v3' }, }) const fakeFile = new Vinyl({ contents: Buffer.from("t('test {{count}}', { count: 1 })"), @@ -1408,7 +1437,10 @@ describe('parser', () => { it('generates plurals according to compatibilityJSON value for languages with multiple plural forms', (done) => { let result - const i18nextParser = new i18nTransform({ locales: ['ar'], i18nextOptions: { compatibilityJSON: 'v3'} }) + const i18nextParser = new i18nTransform({ + locales: ['ar'], + i18nextOptions: { compatibilityJSON: 'v3' }, + }) const fakeFile = new Vinyl({ contents: Buffer.from("t('test {{count}}', { count: 1 })"), path: 'file.js', diff --git a/test/templating/keyPrefix-hook.jsx b/test/templating/keyPrefix-hook.jsx new file mode 100644 index 00000000..cd2d3584 --- /dev/null +++ b/test/templating/keyPrefix-hook.jsx @@ -0,0 +1,19 @@ +import React from 'react' +import { useTranslation, Trans } from 'react-i18next' + +// This will have test-namespace even though it comes before useTranslation during parsing +const Component = () => { + return +} + +function TestComponent() { + const { t } = useTranslation('key-prefix', { keyPrefix: 'test-prefix' }) + return ( + <> + +

{t('bar')}

+ + ) +} + +export default TestComponent From 774b6ec6be3b4bef7897e558ba07740231da7c54 Mon Sep 17 00:00:00 2001 From: Hyeonsu Lee Date: Thu, 2 Dec 2021 02:12:56 +0900 Subject: [PATCH 2/2] Restore --- test/parser.test.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/parser.test.js b/test/parser.test.js index 758f29b9..38f38cc1 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -779,7 +779,7 @@ describe('parser', () => { }) afterEach(() => { - console.log.restore() + console.log.restore(); }) describe('with defaultResetLocale', () => { @@ -1412,7 +1412,7 @@ describe('parser', () => { it('generates plurals according to compatibilityJSON value', (done) => { let result const i18nextParser = new i18nTransform({ - i18nextOptions: { compatibilityJSON: 'v3' }, + i18nextOptions: { compatibilityJSON: 'v3' } }) const fakeFile = new Vinyl({ contents: Buffer.from("t('test {{count}}', { count: 1 })"), @@ -1437,10 +1437,7 @@ describe('parser', () => { it('generates plurals according to compatibilityJSON value for languages with multiple plural forms', (done) => { let result - const i18nextParser = new i18nTransform({ - locales: ['ar'], - i18nextOptions: { compatibilityJSON: 'v3' }, - }) + const i18nextParser = new i18nTransform({ locales: ['ar'], i18nextOptions: { compatibilityJSON: 'v3'} }) const fakeFile = new Vinyl({ contents: Buffer.from("t('test {{count}}', { count: 1 })"), path: 'file.js',