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