diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f73dff6..97bbc7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# Unreleased + +- Add `ignoreEmptyKeys` option (default: true) to ignore empty keys when scanning source files #634 + # 9.3.0 - Allow to use multiple translation functions with different namespaces and keyPrefixes #1083 #494 #973 #737 diff --git a/README.md b/README.md index ac8e5489..9bba085b 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,14 @@ export default { // Example: // { // lineWidth: -1, - // } + + ignoreEmptyKeys: true, + // When true (default), ignores empty keys when scanning source files. + // + // This allows using t('') without adding '' to translation files or + // failing update checks. + // + // When false, empty keys are included in the translation files and will fail with --fail-on-update. } ``` diff --git a/index.d.ts b/index.d.ts index f00a68a9..99dbbb4a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -129,4 +129,5 @@ export interface UserConfig { resetDefaultValueLocale?: string | null i18nextOptions?: Record | null yamlOptions?: Record | null + ignoreEmptyKeys?: boolean } diff --git a/src/helpers.js b/src/helpers.js index 1fd91aa1..fa4edfd9 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -32,11 +32,16 @@ function dotPathToHash(entry, target = {}, options = {}) { entry.keyWithNamespace.length ) - // There is no key to process so we return an empty object - if (!key) { + // The key is empty so we return an empty object + if (key === '') { if (!target[entry.namespace]) { target[entry.namespace] = {} } + + if (!options.ignoreEmptyKeys) { + target[entry.namespace][key] = ''; + } + return { target, duplicate, conflict } } diff --git a/src/transform.js b/src/transform.js index 3d557e4a..b03066cb 100644 --- a/src/transform.js +++ b/src/transform.js @@ -40,6 +40,7 @@ export default class i18nTransform extends Transform { customValueTemplate: null, failOnWarnings: false, yamlOptions: null, + ignoreEmptyKeys: true, } this.options = { ...this.defaults, ...options } @@ -180,6 +181,7 @@ export default class i18nTransform extends Transform { pluralSeparator: this.options.pluralSeparator, value: this.options.defaultValue, customValueTemplate: this.options.customValueTemplate, + ignoreEmptyKeys: this.options.ignoreEmptyKeys, }) if (duplicate) { @@ -197,6 +199,9 @@ export default class i18nTransform extends Transform { }` ) } + } else if (entry.key === '' && this.options.ignoreEmptyKeys) { + // Ignore empty keys when scanning source files + // This allows using `t('')` } else { uniqueCount[entry.namespace] += 1 if (suffix) { diff --git a/test/helpers/dotPathToHash.test.js b/test/helpers/dotPathToHash.test.js index 559c6d4a..81d93d3b 100644 --- a/test/helpers/dotPathToHash.test.js +++ b/test/helpers/dotPathToHash.test.js @@ -36,15 +36,37 @@ describe('dotPathToHash helper function', () => { }) it('handles an empty namespace', (done) => { - const { target, duplicate } = dotPathToHash({ - keyWithNamespace: 'ns.', - namespace: 'ns', - }) + const { target, duplicate } = dotPathToHash( + { + keyWithNamespace: 'ns.', + namespace: 'ns', + }, + {}, + { + ignoreEmptyKeys: true, + } + ) assert.deepEqual(target, { ns: {} }) assert.equal(duplicate, false) done() }) + it('handles an empty namespace when ignoreEmptyKeys is false', (done) => { + const { target, duplicate } = dotPathToHash( + { + keyWithNamespace: 'ns.', + namespace: 'ns', + }, + {}, + { + ignoreEmptyKeys: false, + } + ) + assert.deepEqual(target, { ns: { '': '' } }) + assert.equal(duplicate, false) + done() + }) + it('handles a target hash', (done) => { const { target, duplicate } = dotPathToHash( { keyWithNamespace: 'one.two.three' }, diff --git a/test/parser.test.js b/test/parser.test.js index c1ef2727..b0cccc81 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -359,6 +359,55 @@ describe('parser', () => { i18nextParser.end(fakeFile) }) + it('ignores empty keys by default', (done) => { + let resultContent + const i18nextParser = new i18nTransform({ + locales: ['en'], + defaultNamespace: 'test_empty_keys', + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("t('key'); t('');"), + path: 'file.js', + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith(path.normalize('en/test_empty_keys.json'))) { + resultContent = JSON.parse(file.contents) + } + }) + i18nextParser.on('end', () => { + assert.deepEqual(resultContent, { key: '' }) + done() + }) + + i18nextParser.end(fakeFile) + }) + + it('includes empty keys when ignoreEmptyKeys is false', (done) => { + let resultContent + const i18nextParser = new i18nTransform({ + locales: ['en'], + defaultNamespace: 'test_ignore_empty', + ignoreEmptyKeys: false, + }) + const fakeFile = new Vinyl({ + contents: Buffer.from("t('key'); t('');"), + path: 'file.js', + }) + + i18nextParser.on('data', (file) => { + if (file.relative.endsWith(path.normalize('en/test_ignore_empty.json'))) { + resultContent = JSON.parse(file.contents) + } + }) + i18nextParser.on('end', () => { + assert.deepEqual(resultContent, { key: '', '': '' }) + done() + }) + + i18nextParser.end(fakeFile) + }) + it('applies withTranslation namespace globally', (done) => { let result const i18nextParser = new i18nTransform()