diff --git a/.changeset/silver-mirrors-repeat.md b/.changeset/silver-mirrors-repeat.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/silver-mirrors-repeat.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/shared/src/utils/fastDeepMerge.ts b/packages/shared/src/utils/fastDeepMerge.ts index 1aa08d7e0d9..617f097df3c 100644 --- a/packages/shared/src/utils/fastDeepMerge.ts +++ b/packages/shared/src/utils/fastDeepMerge.ts @@ -17,7 +17,7 @@ export const fastDeepMergeAndReplace = ( target[key] = new (Object.getPrototypeOf(source[key]).constructor)(); } fastDeepMergeAndReplace(source[key], target[key]); - } else if (Object.prototype.hasOwnProperty.call(source, key)) { + } else if (Object.prototype.hasOwnProperty.call(source, key) && source[key] !== undefined) { target[key] = source[key]; } } diff --git a/packages/ui/src/localization/__tests__/parseLocalization.test.tsx b/packages/ui/src/localization/__tests__/parseLocalization.test.tsx index 1843c2eb41e..bb0da39f4ad 100644 --- a/packages/ui/src/localization/__tests__/parseLocalization.test.tsx +++ b/packages/ui/src/localization/__tests__/parseLocalization.test.tsx @@ -37,4 +37,65 @@ describe('Localization parsing and replacing', () => { const localizedValue = result.current.t(localizationKeys('backButton')); expect(localizedValue).toBe('test'); }); + + it('falls back to English when user locale has undefined value for a key', async () => { + const { wrapper: Wrapper } = await createFixtures(); + const wrapperBefore = ({ children }) => ( + + + {children} + + + ); + + const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); + + // undefined value should fall back to English + const backButtonValue = result.current.t(localizationKeys('backButton')); + expect(backButtonValue).toBe(defaultResource.backButton); + + // Non-undefined value should use the translation + const formButtonValue = result.current.t(localizationKeys('formButtonPrimary')); + expect(formButtonValue).toBe('Translated'); + }); + + it('falls back to English for nested keys with undefined values', async () => { + const { wrapper: Wrapper } = await createFixtures(); + const wrapperBefore = ({ children }) => ( + + + {children} + + + ); + + const { result } = renderHook(() => useLocalizations(), { wrapper: wrapperBefore }); + + // undefined nested value should fall back to English (tokens get replaced by t()) + const titleValue = result.current.t(localizationKeys('signIn.start.title')); + // The English default is 'Sign in to {{applicationName}}', tokens get replaced + expect(titleValue).toContain('Sign in to'); + + // Non-undefined nested value should use the translation + const subtitleValue = result.current.t(localizationKeys('signIn.start.subtitle')); + expect(subtitleValue).toBe('Custom subtitle'); + }); });