diff --git a/README.md b/README.md index c18e1b1..82172db 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ module.exports = { // Optional: Default value for new translations defaultValue: '__MISSING_TRANSLATION__', // Optional: Regex to extract transations from source code - parseRegex: /\B\$t\s*\(\s*['"]([\w/: ._-]+)['"]/g + parseRegex: /\B\$t\s*\(\s*['"]([\w/: ._-]+)['"]/g, + // Optional: Keep missing translations and not delete them + keepMissing: false } ``` diff --git a/examples/default/locales/de.json b/examples/default/locales/de.json index 55caf8a..a02c8ab 100644 --- a/examples/default/locales/de.json +++ b/examples/default/locales/de.json @@ -4,9 +4,10 @@ "key_2": "Key 2 DE", "key_4": "Key 4 DE", "context": { - "key_1": "Context Key 1 DE", "key_2": "Context Key 2 DE", + "key_1": "Context Key 1 DE", "nested": { + "old": "Old nested key DE", "key": "Nested Key DE" } }, diff --git a/examples/default/locales/en-GB.json b/examples/default/locales/en-GB.json index 0efd8cd..d5deb37 100644 --- a/examples/default/locales/en-GB.json +++ b/examples/default/locales/en-GB.json @@ -4,9 +4,10 @@ "key_2": "Key 2 EN", "key_4": "Key 4 EN", "context": { - "key_1": "Context Key 1 EN", "key_2": "Context Key 2 EN", + "key_1": "Context Key 1 EN", "nested": { + "old": "Old nested key EN", "key": "Nested Key EN" } }, diff --git a/src/extract.ts b/src/extract.ts index a76b9a8..b16f02d 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -21,7 +21,14 @@ export const writeTranslations = async ( ): Promise => { await Promise.all(Object.values(translationResults).map(async (languageTranslations) => { await Promise.all(Object.values(languageTranslations).map(async ({ filePath, translations }) => { - const content = JSON.stringify(translations, undefined, 2) + + // Sort all keys + const allKeys = new Set() + JSON.stringify(translations, (key, value) => { + allKeys.add(key) + return value + }) + const content = JSON.stringify(translations, Array.from(allKeys).sort(), 2) const directory = dirname(filePath) if (!existsSync(directory)) { diff --git a/src/merge.ts b/src/merge.ts index b83f3d8..d4b08a9 100644 --- a/src/merge.ts +++ b/src/merge.ts @@ -15,14 +15,17 @@ export const generateNewTranslations = async ( options: I18nExtractOptions ): Promise => { return generateTranslationMap(options, async ({ language, namespace }) => { - let translations: TranslationStructure = {} + const _existingTranslations = existingTranslations[language]?.[namespace]?.translations || {} + + let translations: TranslationStructure = options.keepMissing + ? JSON.parse(JSON.stringify(_existingTranslations)) + : {} let result: TranslationResultWrite = { translations, untranslatedCount: 0 } const _translationKeys = translationKeys[namespace] || [] - const _existingTranslations = existingTranslations[language]?.[namespace]?.translations || {} for (const translationKey of _translationKeys) { [translations, result] = await writeTranslationStructure( translationKey, diff --git a/src/types.ts b/src/types.ts index 9d0113a..54b6634 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,4 +28,5 @@ export interface I18nExtractOptions { namespaces?: Namespace[] defaultValue?: string parseRegex?: RegExp + keepMissing?: boolean } diff --git a/tests/extract.spec.ts b/tests/extract.spec.ts index c4c183b..c1cf065 100644 --- a/tests/extract.spec.ts +++ b/tests/extract.spec.ts @@ -104,6 +104,51 @@ describe('i18nExtract', () => { })) }) + it('should extract translations and keep old', async () => { + const options = await import('../examples/default/i18n-extract.config.cjs') + await i18nExtract({ + ...options.default, + keepMissing: true + }) + + expect(mockedMkdir).not.toHaveBeenCalled() + + expect(mockedWriteFile).toHaveBeenCalledTimes(2) + + expect(mockedWriteFile).toHaveBeenCalledWith('examples/default/locales/de.json', toJSON({ + context: { + key_1: 'Context Key 1 DE', + key_2: 'Context Key 2 DE', + nested: { + key: 'Nested Key DE', + old: 'Old nested key DE' + } + }, + key_1: 'Key 1 DE', + key_2: 'Key 2 DE', + key_3: 'Key 3 DE', + key_4: 'Key 4 DE', + new_key: '__MISSING_TRANSLATION__', + old_key: 'Old key' + })) + expect(mockedWriteFile).toHaveBeenCalledWith('examples/default/locales/en-GB.json', toJSON({ + context: { + key_1: 'Context Key 1 EN', + key_2: 'Context Key 2 EN', + nested: { + key: 'Nested Key EN', + old: 'Old nested key EN' + } + }, + key_1: 'Key 1 EN', + key_2: 'Key 2 EN', + key_3: 'Key 3 EN', + key_4: 'Key 4 EN', + new_key: '__MISSING_TRANSLATION__', + old_key: 'Old key' + })) + }) + it('should extract with missing translations', async () => { const options = await import('../examples/default/i18n-extract.config.cjs') await i18nExtract({ diff --git a/tests/load.spec.ts b/tests/load.spec.ts index e71a9f3..266c6ea 100644 --- a/tests/load.spec.ts +++ b/tests/load.spec.ts @@ -79,9 +79,10 @@ describe('loadTranslations', () => { key_2: 'Key 2 DE', key_4: 'Key 4 DE', context: { - key_1: 'Context Key 1 DE', key_2: 'Context Key 2 DE', + key_1: 'Context Key 1 DE', nested: { + old: 'Old nested key DE', key: 'Nested Key DE' } }, @@ -98,9 +99,10 @@ describe('loadTranslations', () => { key_2: 'Key 2 EN', key_4: 'Key 4 EN', context: { - key_1: 'Context Key 1 EN', key_2: 'Context Key 2 EN', + key_1: 'Context Key 1 EN', nested: { + old: 'Old nested key EN', key: 'Nested Key EN' } },