type TranslationDict = { [key: string]: string | TranslationDict }; /** * Recursively merge `extra` into `base`, so a partial fragment ADDS keys into * existing namespaces instead of replacing them. A shallow spread would clobber a * whole namespace (e.g. `...drift` last would drop every other `socialUi` key). * Plain nested string dictionaries only. */ export function deepMerge(base: T, extra: TranslationDict): T { const out: TranslationDict = { ...base }; for (const [key, value] of Object.entries(extra)) { const current = out[key]; if ( value && typeof value === 'object' && !Array.isArray(value) && current && typeof current === 'object' && !Array.isArray(current) ) { out[key] = deepMerge(current as TranslationDict, value as TranslationDict); } else { out[key] = value; } } return out as T; }