diff --git a/src/translate.service.ts b/src/translate.service.ts index 5bebd41d..8b15d0a8 100644 --- a/src/translate.service.ts +++ b/src/translate.service.ts @@ -14,7 +14,7 @@ import {TranslateStore} from "./translate.store"; import {TranslateLoader} from "./translate.loader"; import {MissingTranslationHandler, MissingTranslationHandlerParams} from "./missing-translation-handler"; import {TranslateParser} from "./translate.parser"; -import {isDefined} from "./util"; +import {deepMerge, isDefined} from "./util"; export const USE_STORE = new OpaqueToken('USE_STORE'); @@ -269,7 +269,7 @@ export class TranslateService { */ public setTranslation(lang: string, translations: Object, shouldMerge: boolean = false): void { if(shouldMerge && this.translations[lang]) { - Object.assign(this.translations[lang], translations); + this.translations[lang] = deepMerge(this.translations[lang], translations); } else { this.translations[lang] = translations; } diff --git a/src/util.ts b/src/util.ts index 55da72b6..435118f4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -54,4 +54,72 @@ export function equals(o1: any, o2: any): boolean { export function isDefined(value: any): boolean { return typeof value !== 'undefined' && value !== null; -} \ No newline at end of file +} + +/** Start deep merge, from https://github.com/KyleAMathews/deepmerge **/ +function isMergeableObject(val: T): boolean { + let nonNullObject = val && typeof val === 'object'; + + return nonNullObject + && Object.prototype.toString.call(val) !== '[object RegExp]' + && Object.prototype.toString.call(val) !== '[object Date]'; +} + +function emptyTarget(val: T): any { + return Array.isArray(val) ? [] : {}; +} + +function cloneIfNecessary(value: T, options: DeepMergeOptions): T { + let clone = options && options.clone === true; + return clone && isMergeableObject(value) ? deepMerge(emptyTarget(value), value, options) : value; +} + +function defaultArrayMerge(target: T[], source: T[], options: DeepMergeOptions): T[] { + let destination = target.slice(); + source.forEach(function(e, i) { + if(typeof destination[i] === 'undefined') { + destination[i] = cloneIfNecessary(e, options); + } else if(isMergeableObject(e)) { + destination[i] = deepMerge(target[i], e, options); + } else if(target.indexOf(e) === -1) { + destination.push(cloneIfNecessary(e, options)); + } + }); + return destination; +} + +function mergeObject(target: any, source: any, options: DeepMergeOptions): T { + let destination: any = {}; + if(isMergeableObject(target)) { + Object.keys(target).forEach(function(key) { + destination[key] = cloneIfNecessary(target[key], options) + }); + } + Object.keys(source).forEach(function(key) { + if(!isMergeableObject(source[key]) || !target[key]) { + destination[key] = cloneIfNecessary(source[key], options); + } else { + destination[key] = deepMerge(target[key], source[key], options); + } + }); + return destination; +} + +export interface DeepMergeOptions { + clone?: boolean; + + arrayMerge?(destination: T, source: T, options?: DeepMergeOptions): T; +} + +export function deepMerge(target: T, source: T, options?: DeepMergeOptions): T { + let array = Array.isArray(source); + options = options || {arrayMerge: defaultArrayMerge} as any; + let arrayMerge: any = options.arrayMerge || defaultArrayMerge; + + if(array) { + return Array.isArray(target) ? arrayMerge(target, source, options) : cloneIfNecessary(source, options); + } else { + return mergeObject(target, source, options); + } +} +/** End deep merge **/ diff --git a/tests/translate.service.spec.ts b/tests/translate.service.spec.ts index 9939e06a..c32cef27 100644 --- a/tests/translate.service.spec.ts +++ b/tests/translate.service.spec.ts @@ -172,14 +172,14 @@ describe('TranslateService', () => { }); }); - it("shouldn't override the translations if you set the translations twice", (done: Function) => { + it("should merge translations if option shouldMerge is true", (done: Function) => { translations = {}; - translate.setTranslation('en', {"TEST": "This is a test"}, true); - translate.setTranslation('en', {"TEST2": "This is a test"}, true); + translate.setTranslation('en', {"TEST": {"sub1": "value1"}}, true); + translate.setTranslation('en', {"TEST": {"sub2": "value2"}}, true); translate.use('en'); - translate.get('TEST').subscribe((res: string) => { - expect(res).toEqual('This is a test'); + translate.get('TEST').subscribe((res: any) => { + expect(res).toEqual({"sub1": "value1", "sub2": "value2"}); expect(translations).toEqual({}); done(); });