diff --git a/src/utils/merge-locales.ts b/src/utils/merge-locales.ts new file mode 100644 index 00000000000..f514930659c --- /dev/null +++ b/src/utils/merge-locales.ts @@ -0,0 +1,37 @@ +import type { LocaleDefinition } from '..'; + +/** + * Merges the given locales into one locale. + * The locales are merged in the order they are given. + * The first locale that provides an entry for a category will be used for that. + * Mutating the category entries in the returned locale will also mutate the entries in the respective source locale. + * + * @param locales The locales to merge. + * @returns The newly merged locale. + * + * @example + * const de_CH_with_fallbacks = mergeLocales([ de_CH, de, en ]); + */ +export function mergeLocales(locales: LocaleDefinition[]): LocaleDefinition { + const merged: LocaleDefinition = {} as LocaleDefinition; + + for (const locale of locales) { + for (const key in locale) { + if (merged[key] === undefined) { + if (typeof locale[key] === 'object') { + merged[key] = { ...locale[key] }; + } else { + merged[key] = locale[key]; + } + } else { + if (typeof locale[key] === 'object') { + merged[key] = { ...locale[key], ...merged[key] }; + } else { + // Primitive values cannot be merged + } + } + } + } + + return merged; +} diff --git a/test/utils/merge-locales.spec.ts b/test/utils/merge-locales.spec.ts new file mode 100644 index 00000000000..0dcd3ab8a50 --- /dev/null +++ b/test/utils/merge-locales.spec.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest'; +import type { LocaleDefinition } from '../../src'; +import { mergeLocales } from '../../src/utils/merge-locales'; + +describe('mergeLocales', () => { + it('should not overwrite entries', () => { + const locale1: LocaleDefinition = { + title: 'a', + person: { firstName: ['a'] }, + finance: { credit_card: { visa: ['a'] } }, + }; + const locale2: LocaleDefinition = { + title: 'b', + person: { firstName: ['b'] }, + finance: { credit_card: { mastercard: ['b'] } }, + }; + const locale3: LocaleDefinition = { + title: 'c', + person: { firstName: ['c'] }, + finance: { credit_card: {} }, + }; + + const merged = mergeLocales([locale1, locale2, locale3]); + + expect(merged).toEqual({ + title: 'a', + person: { firstName: ['a'] }, + finance: { credit_card: { visa: ['a'] } }, + }); + }); + + it('should extend categories', () => { + const locale1: LocaleDefinition = { + title: 'a', + location: { city: ['a'] }, + person: { first_name: ['a'] }, + }; + const locale2: LocaleDefinition = { + title: 'b', + animal: { cat: ['b'] }, + person: { last_name: ['b'] }, + }; + const locale3: LocaleDefinition = { + title: 'c', + color: { human: ['c'] }, + person: {}, + }; + + const merged = mergeLocales([locale1, locale2, locale3]); + + expect(merged).toEqual({ + title: 'a', + animal: { cat: ['b'] }, + color: { human: ['c'] }, + location: { city: ['a'] }, + person: { first_name: ['a'], last_name: ['b'] }, + }); + }); +});