From 16c0ae3487c083fe8a082e516e4d0ae2ba8a405f Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Tue, 16 Dec 2025 10:00:56 -0500 Subject: [PATCH 1/4] fix(clerk-js): Ensure localizations fallback to english locale when key is undefined --- .changeset/cute-gifts-call.md | 5 +++++ packages/clerk-js/src/ui/localization/parseLocalization.ts | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/cute-gifts-call.md diff --git a/.changeset/cute-gifts-call.md b/.changeset/cute-gifts-call.md new file mode 100644 index 00000000000..7f9a23a671d --- /dev/null +++ b/.changeset/cute-gifts-call.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Fix locale fallback logic to render english values when keys are undefined. diff --git a/packages/clerk-js/src/ui/localization/parseLocalization.ts b/packages/clerk-js/src/ui/localization/parseLocalization.ts index 7bb446f1b7f..876d489eab1 100644 --- a/packages/clerk-js/src/ui/localization/parseLocalization.ts +++ b/packages/clerk-js/src/ui/localization/parseLocalization.ts @@ -1,5 +1,5 @@ import type { DeepPartial, LocalizationResource } from '@clerk/shared/types'; -import { fastDeepMergeAndReplace } from '@clerk/shared/utils'; +import { fastDeepMergeAndKeep, fastDeepMergeAndReplace } from '@clerk/shared/utils'; import { dequal as deepEqual } from 'dequal'; import { useOptions } from '../contexts'; @@ -15,8 +15,10 @@ const parseLocalizationResource = ( if (!cache || (!!prev && prev !== userDefined && !deepEqual(userDefined, prev))) { prev = userDefined; const res = {} as LocalizationResource; - fastDeepMergeAndReplace(base, res); + // Merge user-defined first (may contain undefined values) fastDeepMergeAndReplace(userDefined, res); + // Fill in missing/undefined values from base (English) + fastDeepMergeAndKeep(base, res); cache = res; return cache; } From 876f687baa8a3a576b0ffb664d38ec1787a324c7 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Tue, 16 Dec 2025 10:17:18 -0500 Subject: [PATCH 2/4] add tests --- .../__tests__/parseLocalization.test.tsx | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx b/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx index 1843c2eb41e..0caa3df25b2 100644 --- a/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx +++ b/packages/clerk-js/src/ui/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'); + }); }); From 1b5bfaaca206b02a861577a983837fb7b56eb9bc Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 17 Dec 2025 11:43:38 -0500 Subject: [PATCH 3/4] Update .changeset/cute-gifts-call.md Co-authored-by: Dylan Staley <88163+dstaley@users.noreply.github.com> --- .changeset/cute-gifts-call.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cute-gifts-call.md b/.changeset/cute-gifts-call.md index 7f9a23a671d..0ee52a8d90e 100644 --- a/.changeset/cute-gifts-call.md +++ b/.changeset/cute-gifts-call.md @@ -2,4 +2,4 @@ '@clerk/clerk-js': patch --- -Fix locale fallback logic to render english values when keys are undefined. +Fix locale fallback logic to render English values when localization keys are `undefined`. From a2585a4281c956d1b62945c007f4cc84fd8c371b Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 17 Dec 2025 11:43:44 -0500 Subject: [PATCH 4/4] Update packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx Co-authored-by: Dylan Staley <88163+dstaley@users.noreply.github.com> --- .../src/ui/localization/__tests__/parseLocalization.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx b/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx index 0caa3df25b2..bb0da39f4ad 100644 --- a/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx +++ b/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx @@ -76,7 +76,7 @@ describe('Localization parsing and replacing', () => { signIn: { start: { title: undefined, // Should fall back to English - subtitle: 'Custom subtitle', // Should use translation + subtitle: 'Custom subtitle', // Non-undefined should override }, }, },