diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e1c5b2..893740c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# X.X.X + +- Add skipIdenticals option #1036 + # 9.0.2 - Fix cheerio dependency #1045 diff --git a/README.md b/README.md index ac8e5489..1c21498a 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,9 @@ export default { // { // lineWidth: -1, // } + + skipIdenticals: [], + // An array of locales to skip adding entries which their keys are identical to their values or their values are empty string. } ``` diff --git a/index.d.ts b/index.d.ts index f00a68a9..1ddb1902 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 + skipIdenticals?: string[] } diff --git a/src/helpers.js b/src/helpers.js index 1fd91aa1..51c6a46d 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -357,4 +357,5 @@ export { tsConfigLoader, yamlConfigLoader, unescape, + isPlural, } diff --git a/src/transform.js b/src/transform.js index 91f20084..86dc99b6 100644 --- a/src/transform.js +++ b/src/transform.js @@ -12,6 +12,7 @@ import { mergeHashes, transferValues, makeDefaultSort, + isPlural, } from './helpers.js' import Parser from './parser.js' @@ -40,6 +41,7 @@ export default class i18nTransform extends Transform { customValueTemplate: null, failOnWarnings: false, yamlOptions: null, + skipIdenticals: [], } this.options = { ...this.defaults, ...options } @@ -251,6 +253,47 @@ export default class i18nTransform extends Transform { resetValues[namespace] ) + const shouldSkipKey = (entry, skipIdenticals, local) => { + return skipIdenticals.some((skipLocale) => { + return local === skipLocale + }) + } + + const skipIdenticalsFromCatalog = ( + catalog, + skipIdenticalsLocales, + locale + ) => { + if ( + typeof skipIdenticalsLocales !== 'object' || + skipIdenticalsLocales.length === 0 + ) + return + for (const [key, value] of Object.entries(catalog)) { + if (typeof value === 'object') { + skipIdenticalsFromCatalog(value, skipIdenticalsLocales, locale) + } else { + if (!isPlural(key) && shouldSkipKey(catalog, skipIdenticalsLocales, locale)) { + if (value === '') { + delete catalog[key] + } else if (value === key) { + this.warn( + '"' + + key + + '" is identical to value and you may want to remove it' + ) + } + } + } + } + } + + skipIdenticalsFromCatalog( + newCatalog, + this.options.skipIdenticals, + locale + ) + // record values to be reset // assumes that the 'default' namespace is processed first if (resetAndFlag && !resetValues[namespace]) { diff --git a/test/locales/ar/test_skip_identicals.json b/test/locales/ar/test_skip_identicals.json new file mode 100644 index 00000000..cdc94ef1 --- /dev/null +++ b/test/locales/ar/test_skip_identicals.json @@ -0,0 +1,13 @@ +{ + "key": "ar_translation", + "key2": { + "key3": "ar_translation" + }, + "key4": "ar_translation", + "key6_few": "key6_few", + "key6_many": "key6_many", + "key6_one": "key6_one", + "key6_other": "key6_other", + "key6_two": "key6_two", + "key6_zero": "key6_zero" +} diff --git a/test/locales/en/test_skip_identicals.json b/test/locales/en/test_skip_identicals.json new file mode 100644 index 00000000..a6fe2593 --- /dev/null +++ b/test/locales/en/test_skip_identicals.json @@ -0,0 +1,9 @@ +{ + "key": "en_translation", + "key2": { + "key3": "en_translation" + }, + "key4": "key4", + "key6_one": "en_Key6 one", + "key6_other": "en_Key6 other" +} diff --git a/test/locales/fr/test_skip_identicals.json b/test/locales/fr/test_skip_identicals.json new file mode 100644 index 00000000..5d4a75cc --- /dev/null +++ b/test/locales/fr/test_skip_identicals.json @@ -0,0 +1,8 @@ +{ + "key": "fr_translation", + "key2": { + "key3": "fr_translation" + }, + "key6_one": "fr_Key6 one", + "key6_other": "fr_Key6 other" +} diff --git a/test/parser.test.js b/test/parser.test.js index f056f4f8..ca16b35d 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -803,6 +803,86 @@ describe('parser', () => { i18nextParser.end(fakeFile) }) + it('skips identical keys for specified locales', (done) => { + const i18nextParser = new i18nTransform({ + output: 'test/locales/$LOCALE/$NAMESPACE.json', + locales: ['en', 'ar', 'fr'], + skipIdenticals: ['en', 'fr'], + }) + + const fakeFile = new Vinyl({ + contents: Buffer.from( + `t('test_skip_identicals:key') + \n + t('test_skip_identicals:key2.key3') + \n + t('test_skip_identicals:key4') + \n + t('test_skip_identicals:key5') + \n + t('test_skip_identicals:key6',{count:1}) + \n + t('test_skip_identicals:key6',{count:2})` + ), + path: 'file.js', + }) + + let enResult, arResult, frResult + i18nextParser.on('data', (file) => { + if ( + file.relative.endsWith(path.normalize('en/test_skip_identicals.json')) + ) { + enResult = JSON.parse(file.contents) + } else if ( + file.relative.endsWith(path.normalize('ar/test_skip_identicals.json')) + ) { + arResult = JSON.parse(file.contents) + } else if ( + file.relative.endsWith(path.normalize('fr/test_skip_identicals.json')) + ) { + frResult = JSON.parse(file.contents) + } + }) + + i18nextParser.once('end', () => { + assert.deepEqual(enResult, { + key: 'en_translation', + key2: { + key3: 'en_translation', + }, + key4: 'key4', + key6_one: "en_Key6 one", + key6_other: "en_Key6 other" + }) + assert.deepEqual(arResult, { + key: 'ar_translation', + key2: { + key3: 'ar_translation', + }, + key4: 'ar_translation', + key5: '', + key6_few: "key6_few", + key6_many: "key6_many", + key6_one: "key6_one", + key6_other: "key6_other", + key6_two: "key6_two", + key6_zero: "key6_zero", + }) + assert.deepEqual(frResult, { + key: 'fr_translation', + key2: { + key3: 'fr_translation', + }, + key6_many: "", + key6_one: "fr_Key6 one", + key6_other: "fr_Key6 other" + }) + done() + }) + + i18nextParser.end(fakeFile) + }) + describe('options', () => { describe('resetDefaultValueLocale', () => { it('will not reset (nested) keys if a default value has not changed in the locale', (done) => {