diff --git a/src/lexers/javascript-lexer.js b/src/lexers/javascript-lexer.js index b0532cd3..7772cfe1 100644 --- a/src/lexers/javascript-lexer.js +++ b/src/lexers/javascript-lexer.js @@ -53,6 +53,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 = [] @@ -120,8 +131,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 7af299c4..5a31e761 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 fa965728..1e160add 100644 --- a/src/transform.js +++ b/src/transform.js @@ -109,6 +109,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 e143442a..0477f0a9 100644 --- a/test/gulp/gulp.test.js +++ b/test/gulp/gulp.test.js @@ -51,6 +51,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') ) @@ -116,6 +128,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 4c577fc0..3b7b3877 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -382,6 +382,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() 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