From d6ae74213c224be638113287bf2d2e45c9db995f Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Wed, 23 Sep 2026 18:02:29 -0400 Subject: [PATCH 01/10] feat(shared,ui,localizations): Show password confirmation attempt countdown --- .changeset/small-mice-change.md | 5 ++ .changeset/tidy-password-attempts.md | 7 ++ .../src/core/resources/__tests__/Base.test.ts | 42 ++++++++++ packages/localizations/src/ar-SA.ts | 3 + packages/localizations/src/be-BY.ts | 3 + packages/localizations/src/bg-BG.ts | 3 + packages/localizations/src/bn-IN.ts | 3 + packages/localizations/src/ca-ES.ts | 3 + packages/localizations/src/cs-CZ.ts | 3 + packages/localizations/src/da-DK.ts | 3 + packages/localizations/src/de-DE.ts | 3 + packages/localizations/src/el-GR.ts | 3 + packages/localizations/src/en-GB.ts | 3 + packages/localizations/src/en-US.ts | 4 + packages/localizations/src/es-CR.ts | 3 + packages/localizations/src/es-ES.ts | 3 + packages/localizations/src/es-MX.ts | 3 + packages/localizations/src/es-UY.ts | 3 + packages/localizations/src/fa-IR.ts | 3 + packages/localizations/src/fi-FI.ts | 3 + packages/localizations/src/fr-FR.ts | 3 + packages/localizations/src/he-IL.ts | 3 + packages/localizations/src/hi-IN.ts | 3 + packages/localizations/src/hr-HR.ts | 3 + packages/localizations/src/hu-HU.ts | 3 + packages/localizations/src/id-ID.ts | 3 + packages/localizations/src/is-IS.ts | 3 + packages/localizations/src/it-IT.ts | 3 + packages/localizations/src/ja-JP.ts | 3 + packages/localizations/src/kk-KZ.ts | 3 + packages/localizations/src/ko-KR.ts | 3 + packages/localizations/src/mn-MN.ts | 3 + packages/localizations/src/ms-MY.ts | 3 + packages/localizations/src/nb-NO.ts | 3 + packages/localizations/src/nl-BE.ts | 3 + packages/localizations/src/nl-NL.ts | 3 + packages/localizations/src/pl-PL.ts | 3 + packages/localizations/src/pt-BR.ts | 3 + packages/localizations/src/pt-PT.ts | 3 + packages/localizations/src/ro-RO.ts | 3 + packages/localizations/src/ru-RU.ts | 3 + packages/localizations/src/sk-SK.ts | 3 + packages/localizations/src/sr-RS.ts | 3 + packages/localizations/src/sv-SE.ts | 3 + packages/localizations/src/ta-IN.ts | 3 + packages/localizations/src/te-IN.ts | 3 + packages/localizations/src/th-TH.ts | 3 + packages/localizations/src/tr-TR.ts | 3 + packages/localizations/src/uk-UA.ts | 3 + packages/localizations/src/vi-VN.ts | 3 + packages/localizations/src/zh-CN.ts | 3 + packages/localizations/src/zh-TW.ts | 3 + .../errors/__tests__/clerkApiError.test.ts | 26 +++++++ packages/shared/src/errors/clerkApiError.ts | 1 + packages/shared/src/errors/parseError.ts | 1 + packages/shared/src/types/errors.ts | 3 + packages/shared/src/types/localization.ts | 3 + .../__tests__/makeLocalizable.test.tsx | 78 +++++++++++++++++++ .../ui/src/localization/makeLocalizable.tsx | 15 ++++ 59 files changed, 329 insertions(+) create mode 100644 .changeset/small-mice-change.md create mode 100644 .changeset/tidy-password-attempts.md create mode 100644 packages/shared/src/errors/__tests__/clerkApiError.test.ts diff --git a/.changeset/small-mice-change.md b/.changeset/small-mice-change.md new file mode 100644 index 00000000000..5f62990d193 --- /dev/null +++ b/.changeset/small-mice-change.md @@ -0,0 +1,5 @@ +--- +'@clerk/shared': patch +--- + +Password validation API errors now expose `meta.remainingAttempts`, including when the value is `0`. The API returns it only when an instance enables password confirmation protection, which is off by default. diff --git a/.changeset/tidy-password-attempts.md b/.changeset/tidy-password-attempts.md new file mode 100644 index 00000000000..59c8508ab6c --- /dev/null +++ b/.changeset/tidy-password-attempts.md @@ -0,0 +1,7 @@ +--- +'@clerk/localizations': patch +'@clerk/shared': patch +'@clerk/ui': patch +--- + +Show remaining password confirmation attempts and session-ended explanations during password changes and reverification. These appear only when an instance enables password confirmation protection, which is off by default. The messages are localizable through `unstable__errors.password_confirmation_attempt_remaining`, `unstable__errors.password_confirmation_attempts_remaining` (with `{{remainingAttempts}}`), and `unstable__errors.password_confirmation_session_ended`. diff --git a/packages/clerk-js/src/core/resources/__tests__/Base.test.ts b/packages/clerk-js/src/core/resources/__tests__/Base.test.ts index 0eb8022b2a2..c67026ffee5 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Base.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Base.test.ts @@ -11,6 +11,10 @@ class TestResource extends BaseResource { return this._baseGet(); } + mutate() { + return BaseResource._fetch({ method: 'PATCH', path: '/test' }); + } + fromJSON() { return this; } @@ -58,4 +62,42 @@ describe('BaseResource', () => { console.dir(errResponse); expect(errResponse.retryAfter).toBe(undefined); }); + + it('applies a piggybacked client update before throwing a failed mutation response', async () => { + const requestingSession = { id: 'sess_requesting', status: 'ended' }; + const otherSession = { id: 'sess_other', status: 'active' }; + const client = { id: 'client_1', sessions: [requestingSession, otherSession] }; + const updateClient = vi.fn(); + + BaseResource.clerk = { + // @ts-expect-error - We're not about to mock the entire FapiClient + getFapiClient: () => ({ + request: vi.fn().mockResolvedValue({ + payload: { + client, + errors: [{ code: 'form_password_validation_failed', message: 'Password is incorrect' }], + }, + status: 422, + statusText: 'Unprocessable Entity', + headers: new Headers(), + }), + }), + __internal_setCountry: vi.fn(), + updateClient, + } as any; + + const resource = new TestResource(); + await expect(resource.mutate()).rejects.toMatchObject({ + errors: [expect.objectContaining({ code: 'form_password_validation_failed' })], + }); + + expect(updateClient).toHaveBeenCalledWith( + expect.objectContaining({ + sessions: expect.arrayContaining([ + expect.objectContaining({ id: requestingSession.id, status: requestingSession.status }), + expect.objectContaining({ id: otherSession.id, status: otherSession.status }), + ]), + }), + ); + }); }); diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index 0665f4b68d0..cc87f666678 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -2101,6 +2101,9 @@ export const arSA: LocalizationResource = { requireUppercase: 'حرف كبير', sentencePrefix: 'كلمة السر يجب أن تحتوي على', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'هذا الرقم مأخوذ الرجاء أختيار رقم آخر', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index e4ad0e6c004..0304e113972 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -2112,6 +2112,9 @@ export const beBY: LocalizationResource = { requireUppercase: 'літару ў верхнім рэгістры', sentencePrefix: 'Ваш пароль павінен утрымліваць', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Гэты нумар тэлефона ўжо заняты. Калі ласка, паспрабуйце іншы.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index 717ee166f6e..66ffb70cf71 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -2105,6 +2105,9 @@ export const bgBG: LocalizationResource = { requireUppercase: 'главна буква', sentencePrefix: 'Вашата парола трябва да съдържа', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Този телефонен номер е зает. Моля, опитайте с друг.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index 022665b1f17..30777dd244d 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -2130,6 +2130,9 @@ export const bnIN: LocalizationResource = { requireUppercase: 'একটি বড় হাতের অক্ষর', sentencePrefix: 'আপনার পাসওয়ার্ডে অবশ্যই থাকতে হবে', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'এই ফোন নম্বর ব্যবহৃত হয়েছে। দয়া করে অন্য একটি ব্যবহার করুন।', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index 6fecadbf40b..e735abe4712 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -2118,6 +2118,9 @@ export const caES: LocalizationResource = { requireUppercase: 'una lletra majúscula', sentencePrefix: 'La teva contrasenya ha de contenir', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: "Aquest número de telèfon ja està en ús. Si us plau, prova'n un altre.", protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index 2050a63cde1..2af3ea14417 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -2117,6 +2117,9 @@ export const csCZ: LocalizationResource = { requireUppercase: 'velké písmeno', sentencePrefix: 'Vaše heslo musí obsahovat', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Toto telefonní číslo se používá. Zkuste prosím jiný.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 863123bccb2..49ab8f806c2 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -2102,6 +2102,9 @@ export const daDK: LocalizationResource = { requireUppercase: 'et stort bogstav', sentencePrefix: 'Din adgangskode skal indeholde', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Dette telefonnummer er allerede taget. Prøv et andet.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 3dae5675b44..e1369b48705 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -2135,6 +2135,9 @@ export const deDE: LocalizationResource = { requireUppercase: 'einen Großbuchstaben enthalten', sentencePrefix: 'Das Passwort muss', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Diese Telefonnummer ist bereits vergeben. Bitte wählen Sie eine Andere.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index cbaa83a14eb..7a507b3ec86 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -2126,6 +2126,9 @@ export const elGR: LocalizationResource = { requireUppercase: 'ένα κεφαλαίο γράμμα', sentencePrefix: 'Ο κωδικός πρόσβασής σας πρέπει να περιέχει', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Αυτός ο αριθμός τηλεφώνου χρησιμοποιείται ήδη. Δοκιμάστε έναν άλλο.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index 8cf3fa19f17..b86fc6f7515 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -2109,6 +2109,9 @@ export const enGB: LocalizationResource = { requireUppercase: 'an uppercase letter', sentencePrefix: 'Your password must contain', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'This phone number is taken. Please try another.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 34ba72cb85c..50f8c4d9a66 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -2161,6 +2161,10 @@ export const enUS: LocalizationResource = { requireUppercase: 'an uppercase letter', sentencePrefix: 'Your password must contain', }, + password_confirmation_attempt_remaining: 'Incorrect password. You have 1 attempt remaining.', + password_confirmation_attempts_remaining: 'Incorrect password. You have {{remainingAttempts}} attempts remaining.', + password_confirmation_session_ended: + 'Your session has ended because you reached the password confirmation attempt limit. Sign in again to continue.', phone_number_exists: undefined, protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index 881651ad605..f7e67c75103 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -2117,6 +2117,9 @@ export const esCR: LocalizationResource = { requireUppercase: 'al menos una letra mayúscula', sentencePrefix: 'Tu contraseña debe contener', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Este número de telefónico ya está en uso.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index 352b491767c..2559a7ec9d4 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -2118,6 +2118,9 @@ export const esES: LocalizationResource = { requireUppercase: 'Al menos una letra mayúscula', sentencePrefix: 'Tu contraseña debe contener:', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Este número de teléfono ya está en uso. Por favor, inténtelo con otro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index fb8f136a774..bd26067b450 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -2118,6 +2118,9 @@ export const esMX: LocalizationResource = { requireUppercase: 'al menos una letra mayúscula', sentencePrefix: 'Tu contraseña debe contener', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Este número de telefónico ya está en uso.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index 4bc80046ddc..a6b6e1cac79 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -2119,6 +2119,9 @@ export const esUY: LocalizationResource = { requireUppercase: 'una letra mayúscula', sentencePrefix: 'Tu contraseña debe contener', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Este número de teléfono ya está en uso. Por favor, probá con otro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index 1cec93c532a..eaf6bb0941f 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -2116,6 +2116,9 @@ export const faIR: LocalizationResource = { requireUppercase: 'یک حرف بزرگ', sentencePrefix: 'رمز عبور شما باید حاوی', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'این شماره تلفن قبلاً استفاده شده است.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index af968a9348d..84e6694cc3a 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -2132,6 +2132,9 @@ export const fiFI: LocalizationResource = { requireUppercase: 'iso kirjain', sentencePrefix: 'Salasanan on sisällettävä', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Tämä puhelinnumero on jo käytössä. Kokeile toista.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index f10b2f0e17e..acaae492d8c 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -2127,6 +2127,9 @@ export const frFR: LocalizationResource = { requireUppercase: 'une lettre majuscule', sentencePrefix: 'Votre mot de passe doit contenir', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Ce numéro de téléphone est déjà utilisé. Veuillez essayer un autre.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 9238c575468..9a51eebe602 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -2093,6 +2093,9 @@ export const heIL: LocalizationResource = { requireUppercase: 'אות גדולה', sentencePrefix: 'הסיסמה שלך חייבת להכיל', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'מספר הטלפון הזה כבר בשימוש. אנא נסה מספר אחר.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index 50faad7f25b..410aa6a6a84 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -2131,6 +2131,9 @@ export const hiIN: LocalizationResource = { requireUppercase: 'एक अपरकेस अक्षर', sentencePrefix: 'आपके पासवर्ड में होना चाहिए', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'यह फोन नंबर पहले से लिया गया है। कृपया दूसरा प्रयास करें।', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index d4f315fc89c..6fecaae949b 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -2136,6 +2136,9 @@ export const hrHR: LocalizationResource = { requireUppercase: 'veliko slovo', sentencePrefix: 'Vaša lozinka mora sadržavati', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Ovaj telefonski broj je zauzet. Molimo pokušajte s drugim.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index 87f03437062..82b650c0078 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -2135,6 +2135,9 @@ export const huHU: LocalizationResource = { requireUppercase: 'egy nagybetű', sentencePrefix: 'A jelszavadnak tartalmaznia kell', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Ez a telefonszám már foglalt. Kérlek próbálj meg egy másikat.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 4a56e4e5711..066071a1fef 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -2115,6 +2115,9 @@ export const idID: LocalizationResource = { requireUppercase: 'huruf besar', sentencePrefix: 'Kata sandi Anda harus mengandung', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Nomor telepon ini sudah digunakan. Silakan coba yang lain.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 78dc41a170e..5c214fe3e6f 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -2133,6 +2133,9 @@ export const isIS: LocalizationResource = { requireUppercase: 'hástaf', sentencePrefix: 'Lykilorðið þitt verður að innihalda', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Þetta símanúmer er þegar í notkun. Vinsamlegast reyndu annað.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index bb419bfd479..aa32a8196e6 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -2114,6 +2114,9 @@ export const itIT: LocalizationResource = { requireUppercase: undefined, sentencePrefix: undefined, }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Questo numero di telefono è già in uso. Per favore, prova con un altro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 4f1fe4d90ce..bd91689b260 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -2129,6 +2129,9 @@ export const jaJP: LocalizationResource = { requireUppercase: '大文字を含む', sentencePrefix: 'パスワードは次の条件を満たす必要があります:', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'この電話番号は既に使用されています。別のものをお試しください。', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index f669637237f..1f2c6a00ac8 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -2095,6 +2095,9 @@ export const kkKZ: LocalizationResource = { requireUppercase: 'бас әріп', sentencePrefix: 'Құпия сөзде болуы керек:', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Бұл телефон нөмірі тіркелген. Басқасын қолданыңыз.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 88e554eaefa..26ee32f1eb0 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -2102,6 +2102,9 @@ export const koKR: LocalizationResource = { requireUppercase: '대문자', sentencePrefix: '비밀번호에는 다음이 꼭 포함돼야 해요', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: '이 전화번호는 이미 사용 중이에요. 다른 번호를 시도해 주세요.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 6aa4840b5a8..352c12f4823 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -2107,6 +2107,9 @@ export const mnMN: LocalizationResource = { requireUppercase: 'том үсэг', sentencePrefix: 'таны нууц үг агуулсан байх ёстой', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Энэ утасны дугаарыг авсан. Өөр оролдоно уу.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index 7c53a9ae9c3..0136e20861d 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -2140,6 +2140,9 @@ export const msMY: LocalizationResource = { requireUppercase: 'huruf besar', sentencePrefix: 'Kata laluan anda mesti mengandungi', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Nombor telefon ini telah diambil. Sila cuba yang lain.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index c31ca1ffa75..657b58f0ed2 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -2133,6 +2133,9 @@ export const nbNO: LocalizationResource = { requireUppercase: 'en stor bokstav', sentencePrefix: 'Passordet ditt må inneholde', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Dette telefonnummeret er allerede i bruk. Vennligst bruk et annet telefonnummer.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index 69a5f136b18..02abb0a5eb7 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -2106,6 +2106,9 @@ export const nlBE: LocalizationResource = { requireUppercase: 'Wachtwoord moet minstens 1 hoofdletter bevatten.', sentencePrefix: 'Wachtwoord moet minstens 1 speciaal teken bevatten.', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Dit telefoonnummer is al in gebruik. Probeer een ander nummer.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 27da0499ce9..aabf45ee430 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -2106,6 +2106,9 @@ export const nlNL: LocalizationResource = { requireUppercase: 'Wachtwoord moet minstens 1 hoofdletter bevatten.', sentencePrefix: 'Wachtwoord moet minstens 1 speciaal teken bevatten.', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Dit telefoonnummer is al in gebruik. Probeer een ander nummer.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index 04af979284e..de66899802b 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -2114,6 +2114,9 @@ export const plPL: LocalizationResource = { requireUppercase: 'wielką literę', sentencePrefix: 'Twoje hasło musi zawierać', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Numer telefonu jest już zajęty. Proszę spróbować innego.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 99b368031c2..dc987f0c9e8 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -2124,6 +2124,9 @@ export const ptBR: LocalizationResource = { requireUppercase: 'uma letra maiúscula', sentencePrefix: 'Sua senha deve conter', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Este número de telefone já está em uso. Por favor, tente outro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 48d6a572489..764bd22a21a 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -2130,6 +2130,9 @@ export const ptPT: LocalizationResource = { requireUppercase: 'uma letra maiúscula', sentencePrefix: 'A sua palavra-passe deve conter', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Este número de telemóvel já está em uso. Por favor, tente outro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index be1e270ce09..e944000e011 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -2125,6 +2125,9 @@ export const roRO: LocalizationResource = { requireUppercase: 'o literă mare', sentencePrefix: 'Parola ta trebuie să conțină', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: undefined, protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 4403998a770..16307b3094e 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -2122,6 +2122,9 @@ export const ruRU: LocalizationResource = { requireUppercase: 'букву в верхнем регистре', sentencePrefix: 'Ваш пароль должен содержать', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Этот номер телефона уже занят. Пожалуйста, попробуйте другой.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index c73b35e563c..6b28f7702c3 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -2113,6 +2113,9 @@ export const skSK: LocalizationResource = { requireUppercase: 'veľké písmeno', sentencePrefix: 'Vaše heslo musí obsahovať', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Toto telefónne číslo je už obsadené. Skúste prosím iné.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index e43def61292..8cf9e73366e 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -2105,6 +2105,9 @@ export const srRS: LocalizationResource = { requireUppercase: 'veliko slovo', sentencePrefix: 'Tvoja lozinka mora sadržati', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Ovaj telefonski broj je zauzet. Molimo pokušaj sa drugim.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index 31dfc362a9e..0fc0ec75a9e 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -2108,6 +2108,9 @@ export const svSE: LocalizationResource = { requireUppercase: 'Kräver stora bokstäver', sentencePrefix: 'Lösenordet måste innehålla', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Detta telefonnummer är taget. Vänligen prova ett annat.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index 71ba727aadc..a9f55a7b2de 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -2141,6 +2141,9 @@ export const taIN: LocalizationResource = { requireUppercase: 'ஒரு பெரிய எழுத்து', sentencePrefix: 'உங்கள் கடவுச்சொல்லில் இருக்க வேண்டும்', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'இந்த தொலைபேசி எண் எடுக்கப்பட்டுள்ளது. வேறொன்றை முயற்சிக்கவும்.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index e5bf22ca4cd..ec0ead246ca 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -2135,6 +2135,9 @@ export const teIN: LocalizationResource = { requireUppercase: 'ఒక పెద్ద అక్షరం', sentencePrefix: 'మీ పాస్‌వర్డ్‌లో ఉండాలి', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'ఈ ఫోన్ నంబర్ తీసుకోబడింది. దయచేసి మరొకదాన్ని ప్రయత్నించండి.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index 25185bfbb26..69e9b589523 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -2103,6 +2103,9 @@ export const thTH: LocalizationResource = { requireUppercase: 'ตัวอักษรพิมพ์ใหญ่', sentencePrefix: 'รหัสผ่านของคุณต้องมี', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: undefined, protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index addbef60fef..0edc84c435a 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -2109,6 +2109,9 @@ export const trTR: LocalizationResource = { requireUppercase: 'bir büyük harf içermeli', sentencePrefix: 'Şifreniz;', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Bu telefon numarası zaten kullanılıyor. Lütfen başka bir numara deneyin.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index f714296df0c..bef156a2163 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -2103,6 +2103,9 @@ export const ukUA: LocalizationResource = { requireUppercase: 'букву у верхньому регістрі', sentencePrefix: 'Ваш пароль повинен містити', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: 'Цей номер телефону вже використовується. Спробуйте інший.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index f4af25fadb9..c072877a322 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -2126,6 +2126,9 @@ export const viVN: LocalizationResource = { requireUppercase: 'một chữ cái viết hoa', sentencePrefix: 'Mật khẩu của bạn phải chứa', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: undefined, protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index 429fba2d4d1..fd2a5d59349 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -2086,6 +2086,9 @@ export const zhCN: LocalizationResource = { requireUppercase: '一个大写字母', sentencePrefix: '您的密码必须包含', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: '该电话号码已被使用,请尝试其他号码。', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index 0a59569f066..ec1788ab4da 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -2091,6 +2091,9 @@ export const zhTW: LocalizationResource = { requireUppercase: '一個大寫字母', sentencePrefix: '您的密碼必須包含:', }, + password_confirmation_attempt_remaining: undefined, + password_confirmation_attempts_remaining: undefined, + password_confirmation_session_ended: undefined, phone_number_exists: '此電話號碼已被使用,請嘗試其他號碼。', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/shared/src/errors/__tests__/clerkApiError.test.ts b/packages/shared/src/errors/__tests__/clerkApiError.test.ts new file mode 100644 index 00000000000..6ba2047adb0 --- /dev/null +++ b/packages/shared/src/errors/__tests__/clerkApiError.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; + +import { ClerkAPIError } from '../clerkApiError'; +import { errorToJSON } from '../parseError'; + +describe('ClerkAPIError remainingAttempts metadata', () => { + it('converts remaining_attempts to remainingAttempts, including zero', () => { + const error = new ClerkAPIError({ + code: 'form_password_validation_failed', + message: 'Password is incorrect', + meta: { remaining_attempts: 0 }, + }); + + expect(error.meta.remainingAttempts).toBe(0); + }); + + it('serializes remainingAttempts as remaining_attempts', () => { + const error = new ClerkAPIError({ + code: 'form_password_incorrect', + message: 'Password is incorrect', + meta: { remaining_attempts: 2 }, + }); + + expect(errorToJSON(error).meta?.remaining_attempts).toBe(2); + }); +}); diff --git a/packages/shared/src/errors/clerkApiError.ts b/packages/shared/src/errors/clerkApiError.ts index adaeb3e5b9d..102c87c09f4 100644 --- a/packages/shared/src/errors/clerkApiError.ts +++ b/packages/shared/src/errors/clerkApiError.ts @@ -35,6 +35,7 @@ export class ClerkAPIError implements Cler linkUrl: json.meta?.link_url, linkText: json.meta?.link_text, data: json.meta?.data, + remainingAttempts: json.meta?.remaining_attempts, } as unknown as Meta, }; this.code = parsedError.code; diff --git a/packages/shared/src/errors/parseError.ts b/packages/shared/src/errors/parseError.ts index a13945d8f10..a3e0620a2f0 100644 --- a/packages/shared/src/errors/parseError.ts +++ b/packages/shared/src/errors/parseError.ts @@ -48,6 +48,7 @@ export function errorToJSON(error: ClerkAPIError | null): ClerkAPIErrorJSON { link_url: error?.meta?.linkUrl, link_text: error?.meta?.linkText, data: error?.meta?.data, + remaining_attempts: error?.meta?.remainingAttempts, }, }; } diff --git a/packages/shared/src/types/errors.ts b/packages/shared/src/types/errors.ts index f54e6982ea0..1192fb43ddb 100644 --- a/packages/shared/src/types/errors.ts +++ b/packages/shared/src/types/errors.ts @@ -30,6 +30,7 @@ export interface ClerkAPIErrorJSON { link_url?: string; link_text?: string; data?: Record; + remaining_attempts?: number; }; } @@ -122,6 +123,8 @@ export interface ClerkAPIError { * sensitive. */ data?: Record; + /** Remaining password confirmation attempts in the current session; zero means the session has ended. */ + remainingAttempts?: number; }; } diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 912ac54e607..99a12004e0a 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -2506,6 +2506,9 @@ type UnstableErrors = WithParamName<{ form_password_incorrect: LocalizationValue; form_password_or_identifier_incorrect: LocalizationValue; form_password_validation_failed: LocalizationValue; + password_confirmation_attempt_remaining: LocalizationValue; + password_confirmation_attempts_remaining: LocalizationValue<'remainingAttempts'>; + password_confirmation_session_ended: LocalizationValue; not_allowed_access: LocalizationValue; oauth_access_denied: LocalizationValue; form_identifier_exists: LocalizationValue; diff --git a/packages/ui/src/localization/__tests__/makeLocalizable.test.tsx b/packages/ui/src/localization/__tests__/makeLocalizable.test.tsx index c3d9b8f1458..01938c7ff5c 100644 --- a/packages/ui/src/localization/__tests__/makeLocalizable.test.tsx +++ b/packages/ui/src/localization/__tests__/makeLocalizable.test.tsx @@ -145,6 +145,84 @@ describe('Test localizable components', () => { ).toBe('form_identifier_exists__email_address'); }); + it.each([ + ['form_password_validation_failed', 2, 'Incorrect password. You have 2 attempts remaining.'], + ['form_password_incorrect', 1, 'Incorrect password. You have 1 attempt remaining.'], + [ + 'form_password_validation_failed', + 0, + 'Your session has ended because you reached the password confirmation attempt limit. Sign in again to continue.', + ], + [ + 'form_password_incorrect', + 0, + 'Your session has ended because you reached the password confirmation attempt limit. Sign in again to continue.', + ], + ])( + 'shows the confirmation countdown for %s with %i attempts remaining', + async (code, remainingAttempts, expected) => { + const { wrapper, fixtures } = await createFixtures(); + fixtures.options.localization = { + unstable__errors: { + form_password_validation_failed: 'Generic translated password error', + form_password_incorrect: 'Generic translated password error', + }, + }; + const { result } = renderHook(() => useLocalizations(), { wrapper }); + + expect( + result.current.translateError({ + code, + message: 'Incorrect password', + longMessage: 'Server message', + meta: { remainingAttempts }, + }), + ).toBe(expected); + }, + ); + + it('uses customer translations for the confirmation countdown', async () => { + const { wrapper, fixtures } = await createFixtures(); + fixtures.options.localization = { + unstable__errors: { + password_confirmation_attempts_remaining: 'Mot de passe incorrect. {{remainingAttempts}} tentatives restantes.', + password_confirmation_session_ended: 'Votre session est terminée.', + }, + }; + const { result } = renderHook(() => useLocalizations(), { wrapper }); + + expect( + result.current.translateError({ + code: 'form_password_validation_failed', + message: 'Incorrect password', + meta: { remainingAttempts: 3 }, + }), + ).toBe('Mot de passe incorrect. 3 tentatives restantes.'); + expect( + result.current.translateError({ + code: 'form_password_incorrect', + message: 'Incorrect password', + meta: { remainingAttempts: 0 }, + }), + ).toBe('Votre session est terminée.'); + }); + + it('keeps translated password errors when there is no confirmation countdown', async () => { + const { wrapper, fixtures } = await createFixtures(); + fixtures.options.localization = { + unstable__errors: { form_password_validation_failed: 'Translated password error' }, + }; + const { result } = renderHook(() => useLocalizations(), { wrapper }); + + expect( + result.current.translateError({ + code: 'form_password_validation_failed', + message: 'Incorrect password', + longMessage: 'Incorrect password. Please try again.', + }), + ).toBe('Translated password error'); + }); + it('translates native OAuth access denied errors using the default localization resource', async () => { const { wrapper } = await createFixtures(); const { result } = renderHook(() => useLocalizations(), { wrapper }); diff --git a/packages/ui/src/localization/makeLocalizable.tsx b/packages/ui/src/localization/makeLocalizable.tsx index 371f8f53351..c1228c7a370 100644 --- a/packages/ui/src/localization/makeLocalizable.tsx +++ b/packages/ui/src/localization/makeLocalizable.tsx @@ -99,6 +99,21 @@ export const useLocalizations = () => { return ''; } + // Password confirmation failures carry a countdown; zero means the session has ended. + const remainingAttempts = meta?.remainingAttempts; + if ( + (code === 'form_password_validation_failed' || code === 'form_password_incorrect') && + typeof remainingAttempts === 'number' + ) { + const countdownKey = + remainingAttempts === 0 + ? localizationKeys('unstable__errors.password_confirmation_session_ended') + : remainingAttempts === 1 + ? localizationKeys('unstable__errors.password_confirmation_attempt_remaining') + : localizationKeys('unstable__errors.password_confirmation_attempts_remaining', { remainingAttempts }); + return t(countdownKey) || longMessage || message; + } + return ( t(localizationKeys(`unstable__errors.${code}__${paramName}` as any)) || t(localizationKeys(`unstable__errors.${code}` as any)) || From 849a0152a5338ac070417eed9c45e27456b291b3 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Wed, 23 Sep 2026 18:15:14 -0400 Subject: [PATCH 02/10] refactor(ui): Build password confirmation countdown on the password fields --- .../components/UserProfile/PasswordForm.tsx | 4 +- .../UserVerificationFactorOnePassword.tsx | 6 +- .../__tests__/makeLocalizable.test.tsx | 78 ------------------- .../ui/src/localization/makeLocalizable.tsx | 15 ---- .../utils/__tests__/passwordUtils.test.tsx | 52 ++++++++++++- packages/ui/src/utils/passwordUtils.ts | 21 +++++ 6 files changed, 80 insertions(+), 96 deletions(-) diff --git a/packages/ui/src/components/UserProfile/PasswordForm.tsx b/packages/ui/src/components/UserProfile/PasswordForm.tsx index c9038469e86..6821a449af4 100644 --- a/packages/ui/src/components/UserProfile/PasswordForm.tsx +++ b/packages/ui/src/components/UserProfile/PasswordForm.tsx @@ -10,7 +10,7 @@ import { FormContainer } from '@/ui/elements/FormContainer'; import { InformationBox } from '@/ui/elements/InformationBox'; import type { SuccessPage } from '@/ui/elements/SuccessPage'; import { handleError } from '@/ui/utils/errorHandler'; -import { createPasswordError } from '@/ui/utils/passwordUtils'; +import { createPasswordConfirmationError, createPasswordError } from '@/ui/utils/passwordUtils'; import { useFormControl } from '@/ui/utils/useFormControl'; import { useEnvironment } from '../../contexts'; @@ -69,6 +69,8 @@ export const PasswordForm = withCardStateProvider((props: PasswordFormProps) => type: 'password', label: localizationKeys('formFieldLabel__currentPassword'), isRequired: true, + validatePassword: false, + buildErrorMessage: errors => createPasswordConfirmationError(errors, { t }), }); const passwordField = useFormControl('newPassword', '', { diff --git a/packages/ui/src/components/UserVerification/UserVerificationFactorOnePassword.tsx b/packages/ui/src/components/UserVerification/UserVerificationFactorOnePassword.tsx index a8f62c47a04..f34b751e07a 100644 --- a/packages/ui/src/components/UserVerification/UserVerificationFactorOnePassword.tsx +++ b/packages/ui/src/components/UserVerification/UserVerificationFactorOnePassword.tsx @@ -6,9 +6,10 @@ import { useCardState } from '@/ui/elements/contexts'; import { Form } from '@/ui/elements/Form'; import { Header } from '@/ui/elements/Header'; import { handleError } from '@/ui/utils/errorHandler'; +import { createPasswordConfirmationError } from '@/ui/utils/passwordUtils'; import { useFormControl } from '@/ui/utils/useFormControl'; -import { Col, descriptors, Flow, localizationKeys } from '../../customizables'; +import { Col, descriptors, Flow, localizationKeys, useLocalizations } from '../../customizables'; import { HavingTrouble } from '../SignIn/HavingTrouble'; import { useAfterVerification } from './use-after-verification'; @@ -22,6 +23,7 @@ export function UserVerificationFactorOnePasswordCard(props: UserVerificationFac const { handleVerificationResponse } = useAfterVerification(); const card = useCardState(); + const { t } = useLocalizations(); const [showHavingTrouble, setShowHavingTrouble] = React.useState(false); const toggleHavingTrouble = React.useCallback(() => setShowHavingTrouble(s => !s), [setShowHavingTrouble]); @@ -30,6 +32,8 @@ export function UserVerificationFactorOnePasswordCard(props: UserVerificationFac type: 'password', label: localizationKeys('formFieldLabel__password'), placeholder: localizationKeys('formFieldInputPlaceholder__password'), + validatePassword: false, + buildErrorMessage: errors => createPasswordConfirmationError(errors, { t }), }); const handlePasswordSubmit: React.FormEventHandler = async e => { diff --git a/packages/ui/src/localization/__tests__/makeLocalizable.test.tsx b/packages/ui/src/localization/__tests__/makeLocalizable.test.tsx index 01938c7ff5c..c3d9b8f1458 100644 --- a/packages/ui/src/localization/__tests__/makeLocalizable.test.tsx +++ b/packages/ui/src/localization/__tests__/makeLocalizable.test.tsx @@ -145,84 +145,6 @@ describe('Test localizable components', () => { ).toBe('form_identifier_exists__email_address'); }); - it.each([ - ['form_password_validation_failed', 2, 'Incorrect password. You have 2 attempts remaining.'], - ['form_password_incorrect', 1, 'Incorrect password. You have 1 attempt remaining.'], - [ - 'form_password_validation_failed', - 0, - 'Your session has ended because you reached the password confirmation attempt limit. Sign in again to continue.', - ], - [ - 'form_password_incorrect', - 0, - 'Your session has ended because you reached the password confirmation attempt limit. Sign in again to continue.', - ], - ])( - 'shows the confirmation countdown for %s with %i attempts remaining', - async (code, remainingAttempts, expected) => { - const { wrapper, fixtures } = await createFixtures(); - fixtures.options.localization = { - unstable__errors: { - form_password_validation_failed: 'Generic translated password error', - form_password_incorrect: 'Generic translated password error', - }, - }; - const { result } = renderHook(() => useLocalizations(), { wrapper }); - - expect( - result.current.translateError({ - code, - message: 'Incorrect password', - longMessage: 'Server message', - meta: { remainingAttempts }, - }), - ).toBe(expected); - }, - ); - - it('uses customer translations for the confirmation countdown', async () => { - const { wrapper, fixtures } = await createFixtures(); - fixtures.options.localization = { - unstable__errors: { - password_confirmation_attempts_remaining: 'Mot de passe incorrect. {{remainingAttempts}} tentatives restantes.', - password_confirmation_session_ended: 'Votre session est terminée.', - }, - }; - const { result } = renderHook(() => useLocalizations(), { wrapper }); - - expect( - result.current.translateError({ - code: 'form_password_validation_failed', - message: 'Incorrect password', - meta: { remainingAttempts: 3 }, - }), - ).toBe('Mot de passe incorrect. 3 tentatives restantes.'); - expect( - result.current.translateError({ - code: 'form_password_incorrect', - message: 'Incorrect password', - meta: { remainingAttempts: 0 }, - }), - ).toBe('Votre session est terminée.'); - }); - - it('keeps translated password errors when there is no confirmation countdown', async () => { - const { wrapper, fixtures } = await createFixtures(); - fixtures.options.localization = { - unstable__errors: { form_password_validation_failed: 'Translated password error' }, - }; - const { result } = renderHook(() => useLocalizations(), { wrapper }); - - expect( - result.current.translateError({ - code: 'form_password_validation_failed', - message: 'Incorrect password', - longMessage: 'Incorrect password. Please try again.', - }), - ).toBe('Translated password error'); - }); - it('translates native OAuth access denied errors using the default localization resource', async () => { const { wrapper } = await createFixtures(); const { result } = renderHook(() => useLocalizations(), { wrapper }); diff --git a/packages/ui/src/localization/makeLocalizable.tsx b/packages/ui/src/localization/makeLocalizable.tsx index c1228c7a370..371f8f53351 100644 --- a/packages/ui/src/localization/makeLocalizable.tsx +++ b/packages/ui/src/localization/makeLocalizable.tsx @@ -99,21 +99,6 @@ export const useLocalizations = () => { return ''; } - // Password confirmation failures carry a countdown; zero means the session has ended. - const remainingAttempts = meta?.remainingAttempts; - if ( - (code === 'form_password_validation_failed' || code === 'form_password_incorrect') && - typeof remainingAttempts === 'number' - ) { - const countdownKey = - remainingAttempts === 0 - ? localizationKeys('unstable__errors.password_confirmation_session_ended') - : remainingAttempts === 1 - ? localizationKeys('unstable__errors.password_confirmation_attempt_remaining') - : localizationKeys('unstable__errors.password_confirmation_attempts_remaining', { remainingAttempts }); - return t(countdownKey) || longMessage || message; - } - return ( t(localizationKeys(`unstable__errors.${code}__${paramName}` as any)) || t(localizationKeys(`unstable__errors.${code}` as any)) || diff --git a/packages/ui/src/utils/__tests__/passwordUtils.test.tsx b/packages/ui/src/utils/__tests__/passwordUtils.test.tsx index d1e596d93ac..5d6b443e4b0 100644 --- a/packages/ui/src/utils/__tests__/passwordUtils.test.tsx +++ b/packages/ui/src/utils/__tests__/passwordUtils.test.tsx @@ -4,7 +4,7 @@ import { bindCreateFixtures } from '@/test/create-fixtures'; import { renderHook } from '@/test/utils'; import { OptionsProvider } from '@/ui/contexts'; import { useLocalizations } from '@/ui/customizables'; -import { createPasswordError } from '@/ui/utils/passwordUtils'; +import { createPasswordConfirmationError, createPasswordError } from '@/ui/utils/passwordUtils'; const { createFixtures } = bindCreateFixtures('SignIn'); @@ -286,3 +286,53 @@ describe('createPasswordError() constructs error that password', () => { ); }); }); + +describe('createPasswordConfirmationError()', () => { + const renderLocalizations = async (localization = {}) => { + const { wrapper: Wrapper } = await createFixtures(); + const wrapper = ({ children }) => ( + + {children} + + ); + return renderHook(() => useLocalizations(), { wrapper }).result; + }; + + it.each([ + [2, 'Incorrect password. You have 2 attempts remaining.'], + [1, 'Incorrect password. You have 1 attempt remaining.'], + [ + 0, + 'Your session has ended because you reached the password confirmation attempt limit. Sign in again to continue.', + ], + ])('shows the countdown with %i attempts remaining', async (remainingAttempts, expected) => { + const result = await renderLocalizations(); + + const res = createPasswordConfirmationError( + [{ code: 'form_password_validation_failed', message: 'Incorrect password', meta: { remainingAttempts } }], + { t: result.current.t }, + ); + expect(res).toBe(expected); + }); + + it('uses customer translations for the countdown', async () => { + const result = await renderLocalizations({ + unstable__errors: { + password_confirmation_attempts_remaining: 'Mot de passe incorrect. {{remainingAttempts}} tentatives restantes.', + }, + }); + + const res = createPasswordConfirmationError( + [{ code: 'form_password_incorrect', message: 'Incorrect password', meta: { remainingAttempts: 3 } }], + { t: result.current.t }, + ); + expect(res).toBe('Mot de passe incorrect. 3 tentatives restantes.'); + }); + + it('returns other errors unchanged', async () => { + const result = await renderLocalizations(); + const error = { code: 'form_password_validation_failed', message: 'Incorrect password' }; + + expect(createPasswordConfirmationError([error], { t: result.current.t })).toBe(error); + }); +}); diff --git a/packages/ui/src/utils/passwordUtils.ts b/packages/ui/src/utils/passwordUtils.ts index 55447f7937d..e36fa108469 100644 --- a/packages/ui/src/utils/passwordUtils.ts +++ b/packages/ui/src/utils/passwordUtils.ts @@ -92,6 +92,27 @@ export const createPasswordError = ( return passwordErrorMessage; }; +// A password confirmation failure carries the remaining attempts; zero means the session has ended. +// Other errors are returned unchanged so they keep the default translation. +export const createPasswordConfirmationError = ( + errors: ClerkAPIError[], + localizationConfig: Pick, +) => { + const remainingAttempts = errors[0]?.meta?.remainingAttempts; + if (typeof remainingAttempts !== 'number') { + return errors[0]; + } + + const { t } = localizationConfig; + if (remainingAttempts === 0) { + return t(localizationKeys('unstable__errors.password_confirmation_session_ended')); + } + if (remainingAttempts === 1) { + return t(localizationKeys('unstable__errors.password_confirmation_attempt_remaining')); + } + return t(localizationKeys('unstable__errors.password_confirmation_attempts_remaining', { remainingAttempts })); +}; + export const addFullStop = (string: string | undefined) => { return !string ? '' : string.endsWith('.') ? string : `${string}.`; }; From e0f8264804baf540116573c4ec14d82a26fdecdb Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Wed, 23 Sep 2026 18:49:02 -0400 Subject: [PATCH 03/10] chore(repo): Consolidate password confirmation changesets --- .changeset/password-confirmation-attempts.md | 10 ++++++++++ .changeset/small-mice-change.md | 5 ----- .changeset/tidy-password-attempts.md | 7 ------- 3 files changed, 10 insertions(+), 12 deletions(-) create mode 100644 .changeset/password-confirmation-attempts.md delete mode 100644 .changeset/small-mice-change.md delete mode 100644 .changeset/tidy-password-attempts.md diff --git a/.changeset/password-confirmation-attempts.md b/.changeset/password-confirmation-attempts.md new file mode 100644 index 00000000000..445fdcc764d --- /dev/null +++ b/.changeset/password-confirmation-attempts.md @@ -0,0 +1,10 @@ +--- +'@clerk/localizations': minor +'@clerk/shared': minor +'@clerk/ui': minor +--- + +Show how many password attempts remain when a user enters the wrong current password while changing their password or during reverification, and explain that the session has ended once the attempts run out. This only applies to instances that enable password confirmation protection, which is off by default. + +- `ClerkAPIError.meta.remainingAttempts` holds the attempts left in the current session. `0` means the session has ended. +- Customize the messages with the `unstable__errors.password_confirmation_attempt_remaining`, `unstable__errors.password_confirmation_attempts_remaining` (receives `{{remainingAttempts}}`), and `unstable__errors.password_confirmation_session_ended` localization keys. diff --git a/.changeset/small-mice-change.md b/.changeset/small-mice-change.md deleted file mode 100644 index 5f62990d193..00000000000 --- a/.changeset/small-mice-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@clerk/shared': patch ---- - -Password validation API errors now expose `meta.remainingAttempts`, including when the value is `0`. The API returns it only when an instance enables password confirmation protection, which is off by default. diff --git a/.changeset/tidy-password-attempts.md b/.changeset/tidy-password-attempts.md deleted file mode 100644 index 59c8508ab6c..00000000000 --- a/.changeset/tidy-password-attempts.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@clerk/localizations': patch -'@clerk/shared': patch -'@clerk/ui': patch ---- - -Show remaining password confirmation attempts and session-ended explanations during password changes and reverification. These appear only when an instance enables password confirmation protection, which is off by default. The messages are localizable through `unstable__errors.password_confirmation_attempt_remaining`, `unstable__errors.password_confirmation_attempts_remaining` (with `{{remainingAttempts}}`), and `unstable__errors.password_confirmation_session_ended`. From 2ba308be3862311474b3f02228b0abc592cf2cb4 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Wed, 23 Sep 2026 18:49:03 -0400 Subject: [PATCH 04/10] feat(localizations): Translate password confirmation attempt messages --- packages/localizations/src/ar-SA.ts | 7 ++++--- packages/localizations/src/be-BY.ts | 7 ++++--- packages/localizations/src/bg-BG.ts | 7 ++++--- packages/localizations/src/bn-IN.ts | 7 ++++--- packages/localizations/src/ca-ES.ts | 7 ++++--- packages/localizations/src/cs-CZ.ts | 7 ++++--- packages/localizations/src/da-DK.ts | 7 ++++--- packages/localizations/src/de-DE.ts | 7 ++++--- packages/localizations/src/el-GR.ts | 8 +++++--- packages/localizations/src/en-GB.ts | 7 ++++--- packages/localizations/src/es-CR.ts | 7 ++++--- packages/localizations/src/es-ES.ts | 7 ++++--- packages/localizations/src/es-MX.ts | 7 ++++--- packages/localizations/src/es-UY.ts | 7 ++++--- packages/localizations/src/fa-IR.ts | 7 ++++--- packages/localizations/src/fi-FI.ts | 7 ++++--- packages/localizations/src/fr-FR.ts | 7 ++++--- packages/localizations/src/he-IL.ts | 7 ++++--- packages/localizations/src/hi-IN.ts | 7 ++++--- packages/localizations/src/hr-HR.ts | 7 ++++--- packages/localizations/src/hu-HU.ts | 7 ++++--- packages/localizations/src/id-ID.ts | 8 +++++--- packages/localizations/src/is-IS.ts | 7 ++++--- packages/localizations/src/it-IT.ts | 7 ++++--- packages/localizations/src/ja-JP.ts | 7 ++++--- packages/localizations/src/kk-KZ.ts | 7 ++++--- packages/localizations/src/ko-KR.ts | 7 ++++--- packages/localizations/src/mn-MN.ts | 7 ++++--- packages/localizations/src/ms-MY.ts | 7 ++++--- packages/localizations/src/nb-NO.ts | 7 ++++--- packages/localizations/src/nl-BE.ts | 7 ++++--- packages/localizations/src/nl-NL.ts | 7 ++++--- packages/localizations/src/pl-PL.ts | 7 ++++--- packages/localizations/src/pt-BR.ts | 7 ++++--- packages/localizations/src/pt-PT.ts | 7 ++++--- packages/localizations/src/ro-RO.ts | 7 ++++--- packages/localizations/src/ru-RU.ts | 7 ++++--- packages/localizations/src/sk-SK.ts | 7 ++++--- packages/localizations/src/sr-RS.ts | 7 ++++--- packages/localizations/src/sv-SE.ts | 7 ++++--- packages/localizations/src/ta-IN.ts | 8 +++++--- packages/localizations/src/te-IN.ts | 7 ++++--- packages/localizations/src/th-TH.ts | 7 ++++--- packages/localizations/src/tr-TR.ts | 7 ++++--- packages/localizations/src/uk-UA.ts | 7 ++++--- packages/localizations/src/vi-VN.ts | 7 ++++--- packages/localizations/src/zh-CN.ts | 6 +++--- packages/localizations/src/zh-TW.ts | 6 +++--- 48 files changed, 193 insertions(+), 144 deletions(-) diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index cc87f666678..c7b7537a37e 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -2101,9 +2101,10 @@ export const arSA: LocalizationResource = { requireUppercase: 'حرف كبير', sentencePrefix: 'كلمة السر يجب أن تحتوي على', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'كلمة المرور غير صحيحة. لديك محاولة واحدة متبقية.', + password_confirmation_attempts_remaining: 'كلمة المرور غير صحيحة. عدد المحاولات المتبقية: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'انتهت جلستك لأنك وصلت إلى الحد الأقصى لمحاولات تأكيد كلمة المرور. سجّل الدخول مرة أخرى للمتابعة.', phone_number_exists: 'هذا الرقم مأخوذ الرجاء أختيار رقم آخر', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index 0304e113972..68a7598a4f5 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -2112,9 +2112,10 @@ export const beBY: LocalizationResource = { requireUppercase: 'літару ў верхнім рэгістры', sentencePrefix: 'Ваш пароль павінен утрымліваць', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Няправільны пароль. У вас засталася 1 спроба.', + password_confirmation_attempts_remaining: 'Няправільны пароль. Засталося спроб: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Ваш сеанс завершаны, бо вы дасягнулі ліміту спроб пацвярджэння пароля. Увайдзіце зноў, каб працягнуць.', phone_number_exists: 'Гэты нумар тэлефона ўжо заняты. Калі ласка, паспрабуйце іншы.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index 66ffb70cf71..7ba04c82119 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -2105,9 +2105,10 @@ export const bgBG: LocalizationResource = { requireUppercase: 'главна буква', sentencePrefix: 'Вашата парола трябва да съдържа', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Грешна парола. Остава ви 1 опит.', + password_confirmation_attempts_remaining: 'Грешна парола. Остават ви {{remainingAttempts}} опита.', + password_confirmation_session_ended: + 'Сесията ви приключи, защото достигнахте лимита на опитите за потвърждение на паролата. Влезте отново, за да продължите.', phone_number_exists: 'Този телефонен номер е зает. Моля, опитайте с друг.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index 30777dd244d..551ada0bd2c 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -2130,9 +2130,10 @@ export const bnIN: LocalizationResource = { requireUppercase: 'একটি বড় হাতের অক্ষর', sentencePrefix: 'আপনার পাসওয়ার্ডে অবশ্যই থাকতে হবে', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'ভুল পাসওয়ার্ড। আপনার আর ১টি প্রচেষ্টা বাকি আছে।', + password_confirmation_attempts_remaining: 'ভুল পাসওয়ার্ড। আপনার আর {{remainingAttempts}}টি প্রচেষ্টা বাকি আছে।', + password_confirmation_session_ended: + 'পাসওয়ার্ড নিশ্চিতকরণের প্রচেষ্টার সীমায় পৌঁছানোর কারণে আপনার সেশন শেষ হয়েছে। চালিয়ে যেতে আবার সাইন ইন করুন।', phone_number_exists: 'এই ফোন নম্বর ব্যবহৃত হয়েছে। দয়া করে অন্য একটি ব্যবহার করুন।', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index e735abe4712..861ae37b5a5 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -2118,9 +2118,10 @@ export const caES: LocalizationResource = { requireUppercase: 'una lletra majúscula', sentencePrefix: 'La teva contrasenya ha de contenir', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Contrasenya incorrecta. Et queda 1 intent.', + password_confirmation_attempts_remaining: 'Contrasenya incorrecta. Et queden {{remainingAttempts}} intents.', + password_confirmation_session_ended: + "La teva sessió ha finalitzat perquè has arribat al límit d'intents de confirmació de la contrasenya. Torna a iniciar sessió per continuar.", phone_number_exists: "Aquest número de telèfon ja està en ús. Si us plau, prova'n un altre.", protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index 2af3ea14417..caaf61fd279 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -2117,9 +2117,10 @@ export const csCZ: LocalizationResource = { requireUppercase: 'velké písmeno', sentencePrefix: 'Vaše heslo musí obsahovat', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Nesprávné heslo. Zbývá vám 1 pokus.', + password_confirmation_attempts_remaining: 'Nesprávné heslo. Počet zbývajících pokusů: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Vaše relace byla ukončena, protože jste dosáhli limitu pokusů o potvrzení hesla. Pro pokračování se znovu přihlaste.', phone_number_exists: 'Toto telefonní číslo se používá. Zkuste prosím jiný.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 49ab8f806c2..bf9973e36a1 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -2102,9 +2102,10 @@ export const daDK: LocalizationResource = { requireUppercase: 'et stort bogstav', sentencePrefix: 'Din adgangskode skal indeholde', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Forkert adgangskode. Du har 1 forsøg tilbage.', + password_confirmation_attempts_remaining: 'Forkert adgangskode. Du har {{remainingAttempts}} forsøg tilbage.', + password_confirmation_session_ended: + 'Din session er afsluttet, fordi du har nået grænsen for forsøg på at bekræfte adgangskoden. Log ind igen for at fortsætte.', phone_number_exists: 'Dette telefonnummer er allerede taget. Prøv et andet.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index e1369b48705..02e4163df88 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -2135,9 +2135,10 @@ export const deDE: LocalizationResource = { requireUppercase: 'einen Großbuchstaben enthalten', sentencePrefix: 'Das Passwort muss', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Falsches Passwort. Sie haben noch 1 Versuch.', + password_confirmation_attempts_remaining: 'Falsches Passwort. Sie haben noch {{remainingAttempts}} Versuche.', + password_confirmation_session_ended: + 'Ihre Sitzung wurde beendet, da Sie die maximale Anzahl an Versuchen zur Passwortbestätigung erreicht haben. Melden Sie sich erneut an, um fortzufahren.', phone_number_exists: 'Diese Telefonnummer ist bereits vergeben. Bitte wählen Sie eine Andere.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 7a507b3ec86..a963a258f64 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -2126,9 +2126,11 @@ export const elGR: LocalizationResource = { requireUppercase: 'ένα κεφαλαίο γράμμα', sentencePrefix: 'Ο κωδικός πρόσβασής σας πρέπει να περιέχει', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Λανθασμένος κωδικός πρόσβασης. Σας απομένει 1 προσπάθεια.', + password_confirmation_attempts_remaining: + 'Λανθασμένος κωδικός πρόσβασης. Σας απομένουν {{remainingAttempts}} προσπάθειες.', + password_confirmation_session_ended: + 'Η συνεδρία σας έληξε επειδή φτάσατε το όριο προσπαθειών επιβεβαίωσης κωδικού πρόσβασης. Συνδεθείτε ξανά για να συνεχίσετε.', phone_number_exists: 'Αυτός ο αριθμός τηλεφώνου χρησιμοποιείται ήδη. Δοκιμάστε έναν άλλο.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index b86fc6f7515..3020c1511af 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -2109,9 +2109,10 @@ export const enGB: LocalizationResource = { requireUppercase: 'an uppercase letter', sentencePrefix: 'Your password must contain', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Incorrect password. You have 1 attempt remaining.', + password_confirmation_attempts_remaining: 'Incorrect password. You have {{remainingAttempts}} attempts remaining.', + password_confirmation_session_ended: + 'Your session has ended because you reached the password confirmation attempt limit. Sign in again to continue.', phone_number_exists: 'This phone number is taken. Please try another.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index f7e67c75103..9b817edcf6f 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -2117,9 +2117,10 @@ export const esCR: LocalizationResource = { requireUppercase: 'al menos una letra mayúscula', sentencePrefix: 'Tu contraseña debe contener', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Contraseña incorrecta. Te queda 1 intento.', + password_confirmation_attempts_remaining: 'Contraseña incorrecta. Te quedan {{remainingAttempts}} intentos.', + password_confirmation_session_ended: + 'Tu sesión finalizó porque alcanzaste el límite de intentos de confirmación de contraseña. Vuelve a iniciar sesión para continuar.', phone_number_exists: 'Este número de telefónico ya está en uso.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index 2559a7ec9d4..14f55f3da79 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -2118,9 +2118,10 @@ export const esES: LocalizationResource = { requireUppercase: 'Al menos una letra mayúscula', sentencePrefix: 'Tu contraseña debe contener:', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Contraseña incorrecta. Te queda 1 intento.', + password_confirmation_attempts_remaining: 'Contraseña incorrecta. Te quedan {{remainingAttempts}} intentos.', + password_confirmation_session_ended: + 'Tu sesión ha finalizado porque has alcanzado el límite de intentos de confirmación de contraseña. Vuelve a iniciar sesión para continuar.', phone_number_exists: 'Este número de teléfono ya está en uso. Por favor, inténtelo con otro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index bd26067b450..4671aa8e3d9 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -2118,9 +2118,10 @@ export const esMX: LocalizationResource = { requireUppercase: 'al menos una letra mayúscula', sentencePrefix: 'Tu contraseña debe contener', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Contraseña incorrecta. Te queda 1 intento.', + password_confirmation_attempts_remaining: 'Contraseña incorrecta. Te quedan {{remainingAttempts}} intentos.', + password_confirmation_session_ended: + 'Tu sesión finalizó porque alcanzaste el límite de intentos de confirmación de contraseña. Vuelve a iniciar sesión para continuar.', phone_number_exists: 'Este número de telefónico ya está en uso.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index a6b6e1cac79..578414aa7d3 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -2119,9 +2119,10 @@ export const esUY: LocalizationResource = { requireUppercase: 'una letra mayúscula', sentencePrefix: 'Tu contraseña debe contener', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Contraseña incorrecta. Te queda 1 intento.', + password_confirmation_attempts_remaining: 'Contraseña incorrecta. Te quedan {{remainingAttempts}} intentos.', + password_confirmation_session_ended: + 'Tu sesión finalizó porque alcanzaste el límite de intentos de confirmación de contraseña. Vuelve a iniciar sesión para continuar.', phone_number_exists: 'Este número de teléfono ya está en uso. Por favor, probá con otro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index eaf6bb0941f..c3077f00221 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -2116,9 +2116,10 @@ export const faIR: LocalizationResource = { requireUppercase: 'یک حرف بزرگ', sentencePrefix: 'رمز عبور شما باید حاوی', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'رمز عبور نادرست است. ۱ تلاش دیگر باقی مانده است.', + password_confirmation_attempts_remaining: 'رمز عبور نادرست است. {{remainingAttempts}} تلاش دیگر باقی مانده است.', + password_confirmation_session_ended: + 'جلسه شما به پایان رسید زیرا به حد مجاز تلاش‌های تأیید رمز عبور رسیدید. برای ادامه دوباره وارد شوید.', phone_number_exists: 'این شماره تلفن قبلاً استفاده شده است.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index 84e6694cc3a..66e1f27b9b8 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -2132,9 +2132,10 @@ export const fiFI: LocalizationResource = { requireUppercase: 'iso kirjain', sentencePrefix: 'Salasanan on sisällettävä', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Väärä salasana. Sinulla on 1 yritys jäljellä.', + password_confirmation_attempts_remaining: 'Väärä salasana. Sinulla on {{remainingAttempts}} yritystä jäljellä.', + password_confirmation_session_ended: + 'Istuntosi päättyi, koska saavutit salasanan vahvistusyritysten rajan. Kirjaudu uudelleen sisään jatkaaksesi.', phone_number_exists: 'Tämä puhelinnumero on jo käytössä. Kokeile toista.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index acaae492d8c..35f4a1b4779 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -2127,9 +2127,10 @@ export const frFR: LocalizationResource = { requireUppercase: 'une lettre majuscule', sentencePrefix: 'Votre mot de passe doit contenir', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Mot de passe incorrect. Il vous reste 1 tentative.', + password_confirmation_attempts_remaining: 'Mot de passe incorrect. Il vous reste {{remainingAttempts}} tentatives.', + password_confirmation_session_ended: + 'Votre session a pris fin, car vous avez atteint la limite de tentatives de confirmation du mot de passe. Reconnectez-vous pour continuer.', phone_number_exists: 'Ce numéro de téléphone est déjà utilisé. Veuillez essayer un autre.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 9a51eebe602..df023ffa7bb 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -2093,9 +2093,10 @@ export const heIL: LocalizationResource = { requireUppercase: 'אות גדולה', sentencePrefix: 'הסיסמה שלך חייבת להכיל', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'סיסמה שגויה. נותר לך ניסיון אחד.', + password_confirmation_attempts_remaining: 'סיסמה שגויה. נותרו לך {{remainingAttempts}} ניסיונות.', + password_confirmation_session_ended: + 'הסשן שלך הסתיים מכיוון שהגעת למגבלת ניסיונות אימות הסיסמה. התחבר שוב כדי להמשיך.', phone_number_exists: 'מספר הטלפון הזה כבר בשימוש. אנא נסה מספר אחר.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index 410aa6a6a84..42a504b7b99 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -2131,9 +2131,10 @@ export const hiIN: LocalizationResource = { requireUppercase: 'एक अपरकेस अक्षर', sentencePrefix: 'आपके पासवर्ड में होना चाहिए', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'गलत पासवर्ड। आपके पास 1 प्रयास शेष है।', + password_confirmation_attempts_remaining: 'गलत पासवर्ड। आपके पास {{remainingAttempts}} प्रयास शेष हैं।', + password_confirmation_session_ended: + 'पासवर्ड पुष्टि प्रयासों की सीमा पूरी होने के कारण आपका सत्र समाप्त हो गया है। जारी रखने के लिए फिर से साइन इन करें।', phone_number_exists: 'यह फोन नंबर पहले से लिया गया है। कृपया दूसरा प्रयास करें।', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index 6fecaae949b..e769a96b233 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -2136,9 +2136,10 @@ export const hrHR: LocalizationResource = { requireUppercase: 'veliko slovo', sentencePrefix: 'Vaša lozinka mora sadržavati', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Neispravna lozinka. Preostao vam je 1 pokušaj.', + password_confirmation_attempts_remaining: 'Neispravna lozinka. Preostalo pokušaja: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Vaša sesija je završena jer ste dosegnuli ograničenje pokušaja potvrde lozinke. Ponovno se prijavite za nastavak.', phone_number_exists: 'Ovaj telefonski broj je zauzet. Molimo pokušajte s drugim.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index 82b650c0078..0cb008bcca9 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -2135,9 +2135,10 @@ export const huHU: LocalizationResource = { requireUppercase: 'egy nagybetű', sentencePrefix: 'A jelszavadnak tartalmaznia kell', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Helytelen jelszó. 1 próbálkozásod maradt.', + password_confirmation_attempts_remaining: 'Helytelen jelszó. {{remainingAttempts}} próbálkozásod maradt.', + password_confirmation_session_ended: + 'A munkameneted véget ért, mert elérted a jelszó-megerősítési kísérletek korlátját. A folytatáshoz jelentkezz be újra.', phone_number_exists: 'Ez a telefonszám már foglalt. Kérlek próbálj meg egy másikat.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 066071a1fef..bb2efb2ed1d 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -2115,9 +2115,11 @@ export const idID: LocalizationResource = { requireUppercase: 'huruf besar', sentencePrefix: 'Kata sandi Anda harus mengandung', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Kata sandi salah. Anda memiliki 1 percobaan tersisa.', + password_confirmation_attempts_remaining: + 'Kata sandi salah. Anda memiliki {{remainingAttempts}} percobaan tersisa.', + password_confirmation_session_ended: + 'Sesi Anda telah berakhir karena Anda mencapai batas percobaan konfirmasi kata sandi. Masuk kembali untuk melanjutkan.', phone_number_exists: 'Nomor telepon ini sudah digunakan. Silakan coba yang lain.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 5c214fe3e6f..f772dea8f4f 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -2133,9 +2133,10 @@ export const isIS: LocalizationResource = { requireUppercase: 'hástaf', sentencePrefix: 'Lykilorðið þitt verður að innihalda', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Rangt lykilorð. Þú átt 1 tilraun eftir.', + password_confirmation_attempts_remaining: 'Rangt lykilorð. Þú átt {{remainingAttempts}} tilraunir eftir.', + password_confirmation_session_ended: + 'Lotunni þinni lauk vegna þess að þú náðir hámarksfjölda tilrauna til að staðfesta lykilorð. Skráðu þig inn aftur til að halda áfram.', phone_number_exists: 'Þetta símanúmer er þegar í notkun. Vinsamlegast reyndu annað.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index aa32a8196e6..8edc0278903 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -2114,9 +2114,10 @@ export const itIT: LocalizationResource = { requireUppercase: undefined, sentencePrefix: undefined, }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Password errata. Hai ancora 1 tentativo.', + password_confirmation_attempts_remaining: 'Password errata. Hai ancora {{remainingAttempts}} tentativi.', + password_confirmation_session_ended: + 'La sessione è terminata perché hai raggiunto il limite di tentativi di conferma della password. Accedi di nuovo per continuare.', phone_number_exists: 'Questo numero di telefono è già in uso. Per favore, prova con un altro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index bd91689b260..18a7f6c5b29 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -2129,9 +2129,10 @@ export const jaJP: LocalizationResource = { requireUppercase: '大文字を含む', sentencePrefix: 'パスワードは次の条件を満たす必要があります:', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'パスワードが正しくありません。残り1回試行できます。', + password_confirmation_attempts_remaining: 'パスワードが正しくありません。残り{{remainingAttempts}}回試行できます。', + password_confirmation_session_ended: + 'パスワード確認の試行回数の上限に達したため、セッションが終了しました。続行するには再度サインインしてください。', phone_number_exists: 'この電話番号は既に使用されています。別のものをお試しください。', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index 1f2c6a00ac8..00d2d9d4590 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -2095,9 +2095,10 @@ export const kkKZ: LocalizationResource = { requireUppercase: 'бас әріп', sentencePrefix: 'Құпия сөзде болуы керек:', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Құпиясөз қате. Сізде 1 әрекет қалды.', + password_confirmation_attempts_remaining: 'Құпиясөз қате. Сізде {{remainingAttempts}} әрекет қалды.', + password_confirmation_session_ended: + 'Құпиясөзді растау әрекеттерінің шегіне жеткендіктен, сеансыңыз аяқталды. Жалғастыру үшін қайта кіріңіз.', phone_number_exists: 'Бұл телефон нөмірі тіркелген. Басқасын қолданыңыз.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 26ee32f1eb0..9fe4be36a77 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -2102,9 +2102,10 @@ export const koKR: LocalizationResource = { requireUppercase: '대문자', sentencePrefix: '비밀번호에는 다음이 꼭 포함돼야 해요', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: '비밀번호가 올바르지 않습니다. 남은 시도 횟수: 1회', + password_confirmation_attempts_remaining: '비밀번호가 올바르지 않습니다. 남은 시도 횟수: {{remainingAttempts}}회', + password_confirmation_session_ended: + '비밀번호 확인 시도 횟수 한도에 도달하여 세션이 종료되었습니다. 계속하려면 다시 로그인하세요.', phone_number_exists: '이 전화번호는 이미 사용 중이에요. 다른 번호를 시도해 주세요.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 352c12f4823..0d4630b3de6 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -2107,9 +2107,10 @@ export const mnMN: LocalizationResource = { requireUppercase: 'том үсэг', sentencePrefix: 'таны нууц үг агуулсан байх ёстой', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Нууц үг буруу байна. Танд 1 оролдлого үлдлээ.', + password_confirmation_attempts_remaining: 'Нууц үг буруу байна. Танд {{remainingAttempts}} оролдлого үлдлээ.', + password_confirmation_session_ended: + 'Нууц үг баталгаажуулах оролдлогын хязгаарт хүрсэн тул таны сесс дууслаа. Үргэлжлүүлэхийн тулд дахин нэвтэрнэ үү.', phone_number_exists: 'Энэ утасны дугаарыг авсан. Өөр оролдоно уу.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index 0136e20861d..3d73aaa6708 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -2140,9 +2140,10 @@ export const msMY: LocalizationResource = { requireUppercase: 'huruf besar', sentencePrefix: 'Kata laluan anda mesti mengandungi', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Kata laluan salah. Anda mempunyai 1 percubaan lagi.', + password_confirmation_attempts_remaining: 'Kata laluan salah. Anda mempunyai {{remainingAttempts}} percubaan lagi.', + password_confirmation_session_ended: + 'Sesi anda telah tamat kerana anda telah mencapai had percubaan pengesahan kata laluan. Log masuk semula untuk meneruskan.', phone_number_exists: 'Nombor telefon ini telah diambil. Sila cuba yang lain.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index 657b58f0ed2..0be6281614e 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -2133,9 +2133,10 @@ export const nbNO: LocalizationResource = { requireUppercase: 'en stor bokstav', sentencePrefix: 'Passordet ditt må inneholde', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Feil passord. Du har 1 forsøk igjen.', + password_confirmation_attempts_remaining: 'Feil passord. Du har {{remainingAttempts}} forsøk igjen.', + password_confirmation_session_ended: + 'Økten din er avsluttet fordi du nådde grensen for forsøk på passordbekreftelse. Logg inn på nytt for å fortsette.', phone_number_exists: 'Dette telefonnummeret er allerede i bruk. Vennligst bruk et annet telefonnummer.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index 02abb0a5eb7..ad1e2633326 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -2106,9 +2106,10 @@ export const nlBE: LocalizationResource = { requireUppercase: 'Wachtwoord moet minstens 1 hoofdletter bevatten.', sentencePrefix: 'Wachtwoord moet minstens 1 speciaal teken bevatten.', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Onjuist wachtwoord. Je hebt nog 1 poging.', + password_confirmation_attempts_remaining: 'Onjuist wachtwoord. Je hebt nog {{remainingAttempts}} pogingen.', + password_confirmation_session_ended: + 'Je sessie is beëindigd omdat je het maximale aantal pogingen voor wachtwoordbevestiging hebt bereikt. Log opnieuw in om door te gaan.', phone_number_exists: 'Dit telefoonnummer is al in gebruik. Probeer een ander nummer.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index aabf45ee430..45d8f9ebba9 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -2106,9 +2106,10 @@ export const nlNL: LocalizationResource = { requireUppercase: 'Wachtwoord moet minstens 1 hoofdletter bevatten.', sentencePrefix: 'Wachtwoord moet minstens 1 speciaal teken bevatten.', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Onjuist wachtwoord. Je hebt nog 1 poging.', + password_confirmation_attempts_remaining: 'Onjuist wachtwoord. Je hebt nog {{remainingAttempts}} pogingen.', + password_confirmation_session_ended: + 'Je sessie is beëindigd omdat je het maximale aantal pogingen voor wachtwoordbevestiging hebt bereikt. Log opnieuw in om door te gaan.', phone_number_exists: 'Dit telefoonnummer is al in gebruik. Probeer een ander nummer.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index de66899802b..eb73dd1838d 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -2114,9 +2114,10 @@ export const plPL: LocalizationResource = { requireUppercase: 'wielką literę', sentencePrefix: 'Twoje hasło musi zawierać', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Nieprawidłowe hasło. Pozostała 1 próba.', + password_confirmation_attempts_remaining: 'Nieprawidłowe hasło. Pozostałe próby: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Twoja sesja została zakończona, ponieważ osiągnięto limit prób potwierdzenia hasła. Zaloguj się ponownie, aby kontynuować.', phone_number_exists: 'Numer telefonu jest już zajęty. Proszę spróbować innego.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index dc987f0c9e8..b99c8567b9e 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -2124,9 +2124,10 @@ export const ptBR: LocalizationResource = { requireUppercase: 'uma letra maiúscula', sentencePrefix: 'Sua senha deve conter', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Senha incorreta. Você tem mais 1 tentativa.', + password_confirmation_attempts_remaining: 'Senha incorreta. Você tem mais {{remainingAttempts}} tentativas.', + password_confirmation_session_ended: + 'Sua sessão foi encerrada porque você atingiu o limite de tentativas de confirmação de senha. Faça login novamente para continuar.', phone_number_exists: 'Este número de telefone já está em uso. Por favor, tente outro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 764bd22a21a..dcf1843086b 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -2130,9 +2130,10 @@ export const ptPT: LocalizationResource = { requireUppercase: 'uma letra maiúscula', sentencePrefix: 'A sua palavra-passe deve conter', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Palavra-passe incorreta. Tem mais 1 tentativa.', + password_confirmation_attempts_remaining: 'Palavra-passe incorreta. Tem mais {{remainingAttempts}} tentativas.', + password_confirmation_session_ended: + 'A sua sessão terminou porque atingiu o limite de tentativas de confirmação da palavra-passe. Inicie sessão novamente para continuar.', phone_number_exists: 'Este número de telemóvel já está em uso. Por favor, tente outro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index e944000e011..6b4fc156d31 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -2125,9 +2125,10 @@ export const roRO: LocalizationResource = { requireUppercase: 'o literă mare', sentencePrefix: 'Parola ta trebuie să conțină', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Parolă incorectă. Mai aveți 1 încercare.', + password_confirmation_attempts_remaining: 'Parolă incorectă. Încercări rămase: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Sesiunea dvs. s-a încheiat deoarece ați atins limita de încercări de confirmare a parolei. Autentificați-vă din nou pentru a continua.', phone_number_exists: undefined, protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 16307b3094e..94eaeffb0bd 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -2122,9 +2122,10 @@ export const ruRU: LocalizationResource = { requireUppercase: 'букву в верхнем регистре', sentencePrefix: 'Ваш пароль должен содержать', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Неверный пароль. У вас осталась 1 попытка.', + password_confirmation_attempts_remaining: 'Неверный пароль. Осталось попыток: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Ваш сеанс завершён, так как вы достигли лимита попыток подтверждения пароля. Войдите снова, чтобы продолжить.', phone_number_exists: 'Этот номер телефона уже занят. Пожалуйста, попробуйте другой.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index 6b28f7702c3..d48f9ea2236 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -2113,9 +2113,10 @@ export const skSK: LocalizationResource = { requireUppercase: 'veľké písmeno', sentencePrefix: 'Vaše heslo musí obsahovať', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Nesprávne heslo. Zostáva vám 1 pokus.', + password_confirmation_attempts_remaining: 'Nesprávne heslo. Počet zostávajúcich pokusov: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Vaša relácia bola ukončená, pretože ste dosiahli limit pokusov o potvrdenie hesla. Ak chcete pokračovať, znova sa prihláste.', phone_number_exists: 'Toto telefónne číslo je už obsadené. Skúste prosím iné.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index 8cf9e73366e..14a2f20e0ab 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -2105,9 +2105,10 @@ export const srRS: LocalizationResource = { requireUppercase: 'veliko slovo', sentencePrefix: 'Tvoja lozinka mora sadržati', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Pogrešna lozinka. Preostao vam je 1 pokušaj.', + password_confirmation_attempts_remaining: 'Pogrešna lozinka. Preostalo pokušaja: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Vaša sesija je završena jer ste dostigli ograničenje pokušaja potvrde lozinke. Ponovo se prijavite da biste nastavili.', phone_number_exists: 'Ovaj telefonski broj je zauzet. Molimo pokušaj sa drugim.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index 0fc0ec75a9e..37800657369 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -2108,9 +2108,10 @@ export const svSE: LocalizationResource = { requireUppercase: 'Kräver stora bokstäver', sentencePrefix: 'Lösenordet måste innehålla', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Felaktigt lösenord. Du har 1 försök kvar.', + password_confirmation_attempts_remaining: 'Felaktigt lösenord. Du har {{remainingAttempts}} försök kvar.', + password_confirmation_session_ended: + 'Din session har avslutats eftersom du nådde gränsen för försök att bekräfta lösenordet. Logga in igen för att fortsätta.', phone_number_exists: 'Detta telefonnummer är taget. Vänligen prova ett annat.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index a9f55a7b2de..8ab5196e7df 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -2141,9 +2141,11 @@ export const taIN: LocalizationResource = { requireUppercase: 'ஒரு பெரிய எழுத்து', sentencePrefix: 'உங்கள் கடவுச்சொல்லில் இருக்க வேண்டும்', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'தவறான கடவுச்சொல். உங்களுக்கு 1 முயற்சி மீதமுள்ளது.', + password_confirmation_attempts_remaining: + 'தவறான கடவுச்சொல். உங்களுக்கு {{remainingAttempts}} முயற்சிகள் மீதமுள்ளன.', + password_confirmation_session_ended: + 'கடவுச்சொல் உறுதிப்படுத்தல் முயற்சிகளின் வரம்பை அடைந்ததால் உங்கள் அமர்வு முடிந்தது. தொடர மீண்டும் உள்நுழையவும்.', phone_number_exists: 'இந்த தொலைபேசி எண் எடுக்கப்பட்டுள்ளது. வேறொன்றை முயற்சிக்கவும்.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index ec0ead246ca..4b728fd4bbe 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -2135,9 +2135,10 @@ export const teIN: LocalizationResource = { requireUppercase: 'ఒక పెద్ద అక్షరం', sentencePrefix: 'మీ పాస్‌వర్డ్‌లో ఉండాలి', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'తప్పు పాస్‌వర్డ్. మీకు 1 ప్రయత్నం మిగిలి ఉంది.', + password_confirmation_attempts_remaining: 'తప్పు పాస్‌వర్డ్. మీకు {{remainingAttempts}} ప్రయత్నాలు మిగిలి ఉన్నాయి.', + password_confirmation_session_ended: + 'పాస్‌వర్డ్ నిర్ధారణ ప్రయత్నాల పరిమితిని చేరుకున్నందున మీ సెషన్ ముగిసింది. కొనసాగించడానికి మళ్లీ సైన్ ఇన్ చేయండి.', phone_number_exists: 'ఈ ఫోన్ నంబర్ తీసుకోబడింది. దయచేసి మరొకదాన్ని ప్రయత్నించండి.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index 69e9b589523..2610e25b48e 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -2103,9 +2103,10 @@ export const thTH: LocalizationResource = { requireUppercase: 'ตัวอักษรพิมพ์ใหญ่', sentencePrefix: 'รหัสผ่านของคุณต้องมี', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'รหัสผ่านไม่ถูกต้อง คุณเหลือโอกาสอีก 1 ครั้ง', + password_confirmation_attempts_remaining: 'รหัสผ่านไม่ถูกต้อง คุณเหลือโอกาสอีก {{remainingAttempts}} ครั้ง', + password_confirmation_session_ended: + 'เซสชันของคุณสิ้นสุดลงเนื่องจากคุณยืนยันรหัสผ่านครบจำนวนครั้งที่กำหนดแล้ว โปรดลงชื่อเข้าใช้อีกครั้งเพื่อดำเนินการต่อ', phone_number_exists: undefined, protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index 0edc84c435a..598298a5e7d 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -2109,9 +2109,10 @@ export const trTR: LocalizationResource = { requireUppercase: 'bir büyük harf içermeli', sentencePrefix: 'Şifreniz;', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Hatalı şifre. 1 deneme hakkınız kaldı.', + password_confirmation_attempts_remaining: 'Hatalı şifre. {{remainingAttempts}} deneme hakkınız kaldı.', + password_confirmation_session_ended: + 'Şifre doğrulama deneme sınırına ulaştığınız için oturumunuz sona erdi. Devam etmek için tekrar giriş yapın.', phone_number_exists: 'Bu telefon numarası zaten kullanılıyor. Lütfen başka bir numara deneyin.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index bef156a2163..c6174ee108f 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -2103,9 +2103,10 @@ export const ukUA: LocalizationResource = { requireUppercase: 'букву у верхньому регістрі', sentencePrefix: 'Ваш пароль повинен містити', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Неправильний пароль. У вас залишилася 1 спроба.', + password_confirmation_attempts_remaining: 'Неправильний пароль. Залишилося спроб: {{remainingAttempts}}.', + password_confirmation_session_ended: + 'Ваш сеанс завершено, оскільки ви досягли ліміту спроб підтвердження пароля. Увійдіть знову, щоб продовжити.', phone_number_exists: 'Цей номер телефону вже використовується. Спробуйте інший.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index c072877a322..cc216ec209f 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -2126,9 +2126,10 @@ export const viVN: LocalizationResource = { requireUppercase: 'một chữ cái viết hoa', sentencePrefix: 'Mật khẩu của bạn phải chứa', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: 'Mật khẩu không đúng. Bạn còn 1 lần thử.', + password_confirmation_attempts_remaining: 'Mật khẩu không đúng. Bạn còn {{remainingAttempts}} lần thử.', + password_confirmation_session_ended: + 'Phiên của bạn đã kết thúc vì bạn đã đạt đến giới hạn số lần xác nhận mật khẩu. Hãy đăng nhập lại để tiếp tục.', phone_number_exists: undefined, protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index fd2a5d59349..76dd0670066 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -2086,9 +2086,9 @@ export const zhCN: LocalizationResource = { requireUppercase: '一个大写字母', sentencePrefix: '您的密码必须包含', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: '密码错误。您还有 1 次尝试机会。', + password_confirmation_attempts_remaining: '密码错误。您还有 {{remainingAttempts}} 次尝试机会。', + password_confirmation_session_ended: '由于密码确认尝试次数已达上限,您的会话已结束。请重新登录以继续。', phone_number_exists: '该电话号码已被使用,请尝试其他号码。', protect_check_aborted: undefined, protect_check_already_resolved: undefined, diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index ec1788ab4da..f6fcc1f3879 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -2091,9 +2091,9 @@ export const zhTW: LocalizationResource = { requireUppercase: '一個大寫字母', sentencePrefix: '您的密碼必須包含:', }, - password_confirmation_attempt_remaining: undefined, - password_confirmation_attempts_remaining: undefined, - password_confirmation_session_ended: undefined, + password_confirmation_attempt_remaining: '密碼錯誤。您還有 1 次嘗試機會。', + password_confirmation_attempts_remaining: '密碼錯誤。您還有 {{remainingAttempts}} 次嘗試機會。', + password_confirmation_session_ended: '由於密碼確認嘗試次數已達上限,您的工作階段已結束。請重新登入以繼續。', phone_number_exists: '此電話號碼已被使用,請嘗試其他號碼。', protect_check_aborted: undefined, protect_check_already_resolved: undefined, From 4e52b62dfc84e83eba479f1725e2a77d353c30e6 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 24 Sep 2026 15:25:18 -0400 Subject: [PATCH 05/10] chore(repo): Drop opt-in wording from password confirmation changeset --- .changeset/password-confirmation-attempts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/password-confirmation-attempts.md b/.changeset/password-confirmation-attempts.md index 445fdcc764d..dc973b8baf5 100644 --- a/.changeset/password-confirmation-attempts.md +++ b/.changeset/password-confirmation-attempts.md @@ -4,7 +4,7 @@ '@clerk/ui': minor --- -Show how many password attempts remain when a user enters the wrong current password while changing their password or during reverification, and explain that the session has ended once the attempts run out. This only applies to instances that enable password confirmation protection, which is off by default. +Show how many password attempts remain when a user enters the wrong current password while changing their password or during reverification, and explain that the session has ended once the attempts run out. - `ClerkAPIError.meta.remainingAttempts` holds the attempts left in the current session. `0` means the session has ended. - Customize the messages with the `unstable__errors.password_confirmation_attempt_remaining`, `unstable__errors.password_confirmation_attempts_remaining` (receives `{{remainingAttempts}}`), and `unstable__errors.password_confirmation_session_ended` localization keys. From a1a921583049809ee46256d5fba0bbe3cf7a6215 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 24 Sep 2026 17:49:47 -0400 Subject: [PATCH 06/10] fix(localizations): Clarify Thai password confirmation limit message --- packages/localizations/src/th-TH.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index 2610e25b48e..1906112ceb7 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -2106,7 +2106,7 @@ export const thTH: LocalizationResource = { password_confirmation_attempt_remaining: 'รหัสผ่านไม่ถูกต้อง คุณเหลือโอกาสอีก 1 ครั้ง', password_confirmation_attempts_remaining: 'รหัสผ่านไม่ถูกต้อง คุณเหลือโอกาสอีก {{remainingAttempts}} ครั้ง', password_confirmation_session_ended: - 'เซสชันของคุณสิ้นสุดลงเนื่องจากคุณยืนยันรหัสผ่านครบจำนวนครั้งที่กำหนดแล้ว โปรดลงชื่อเข้าใช้อีกครั้งเพื่อดำเนินการต่อ', + 'เซสชันของคุณสิ้นสุดลงเนื่องจากคุณพยายามยืนยันรหัสผ่านครบตามจำนวนครั้งที่อนุญาตแล้ว โปรดลงชื่อเข้าใช้อีกครั้งเพื่อดำเนินการต่อ', phone_number_exists: undefined, protect_check_aborted: undefined, protect_check_already_resolved: undefined, From be1ab0721ff68b99905a5fdf35db63fde61fb606 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 24 Sep 2026 21:11:05 -0400 Subject: [PATCH 07/10] fix(localizations): Use voseo in es-UY password confirmation limit message --- packages/localizations/src/es-UY.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index 578414aa7d3..bada58202a4 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -2122,7 +2122,7 @@ export const esUY: LocalizationResource = { password_confirmation_attempt_remaining: 'Contraseña incorrecta. Te queda 1 intento.', password_confirmation_attempts_remaining: 'Contraseña incorrecta. Te quedan {{remainingAttempts}} intentos.', password_confirmation_session_ended: - 'Tu sesión finalizó porque alcanzaste el límite de intentos de confirmación de contraseña. Vuelve a iniciar sesión para continuar.', + 'Tu sesión finalizó porque alcanzaste el límite de intentos de confirmación de contraseña. Volvé a iniciar sesión para continuar.', phone_number_exists: 'Este número de teléfono ya está en uso. Por favor, probá con otro.', protect_check_aborted: undefined, protect_check_already_resolved: undefined, From ac2541122ebc3de60084f6343cba89361bb86958 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Tue, 29 Sep 2026 17:00:39 -0400 Subject: [PATCH 08/10] Sign out when a password confirmation ends the session --- .changeset/password-confirmation-attempts.md | 3 +- .../clerk-js/src/core/__tests__/clerk.test.ts | 30 +++++++++++++ packages/clerk-js/src/core/clerk.ts | 22 ++++++++-- packages/clerk-js/src/core/resources/Base.ts | 3 ++ .../src/core/resources/__tests__/Base.test.ts | 42 +++++++++++++++++++ 5 files changed, 95 insertions(+), 5 deletions(-) diff --git a/.changeset/password-confirmation-attempts.md b/.changeset/password-confirmation-attempts.md index dc973b8baf5..489bad323b7 100644 --- a/.changeset/password-confirmation-attempts.md +++ b/.changeset/password-confirmation-attempts.md @@ -1,4 +1,5 @@ --- +'@clerk/clerk-js': minor '@clerk/localizations': minor '@clerk/shared': minor '@clerk/ui': minor @@ -6,5 +7,5 @@ Show how many password attempts remain when a user enters the wrong current password while changing their password or during reverification, and explain that the session has ended once the attempts run out. -- `ClerkAPIError.meta.remainingAttempts` holds the attempts left in the current session. `0` means the session has ended. +- `ClerkAPIError.meta.remainingAttempts` holds the attempts left in the current session. `0` means the session has ended, and Clerk signs the user out of the current tab and notifies other tabs. - Customize the messages with the `unstable__errors.password_confirmation_attempt_remaining`, `unstable__errors.password_confirmation_attempts_remaining` (receives `{{remainingAttempts}}`), and `unstable__errors.password_confirmation_session_ended` localization keys. diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index 0a2a7f40aaa..05c1a264574 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -3815,6 +3815,36 @@ describe('Clerk singleton', () => { }); }); + describe('__internal_handleSessionEnded', () => { + afterEach(() => { + (window as any).__internal_onAfterSetActive = null; + }); + + it('signs out and notifies other tabs after the server ended the session', async () => { + const mockOnAfterSetActive = vi.fn().mockReturnValue(Promise.resolve()); + (window as any).__internal_onAfterSetActive = mockOnAfterSetActive; + const eventBusSpy = vi.spyOn(eventBus, 'emit'); + const mockSession = { + id: 'session_1', + status: 'active', + user: { id: 'user_1' }, + lastActiveToken: { getRawString: () => 'token_1' }, + }; + + const sut = new Clerk(productionPublishableKey); + sut.updateClient({ sessions: [mockSession], signedInSessions: [mockSession] } as any); + // The failed confirmation's response carries the client without the ended session. + sut.updateClient({ sessions: [], signedInSessions: [] } as any); + + await sut.__internal_handleSessionEnded(); + + expect(sut.session).toBeNull(); + expect(eventBusSpy).toHaveBeenCalledWith(events.UserSignOut, null); + expect(mockOnAfterSetActive).toHaveBeenCalled(); + eventBusSpy.mockRestore(); + }); + }); + describe('__internal_attemptToEnableEnvironmentSetting', () => { afterEach(() => { mockEnvironmentFetch.mockReset(); diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index f5eaa05aa92..6a41b3b2b9a 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -2992,10 +2992,7 @@ export class Clerk implements ClerkInterface { if (this.session) { return; } - if (opts.broadcast) { - eventBus.emit(events.UserSignOut, null); - } - return this.setActive({ session: null }); + return this.#completeSignOut(opts); } catch (err) { // `/client` can fail with either a 401, a 403, 500 or network errors. // 401 is already handled internally in our fetcher. @@ -3009,6 +3006,23 @@ export class Clerk implements ClerkInterface { } }; + /** + * Completes a sign-out that FAPI already performed, such as when a failed password + * confirmation ended the session and the piggybacked client no longer contains it. + * + * @internal + */ + public __internal_handleSessionEnded = (): Promise => { + return this.#completeSignOut({ broadcast: true }); + }; + + #completeSignOut = (opts: { broadcast: boolean }): Promise => { + if (opts.broadcast) { + eventBus.emit(events.UserSignOut, null); + } + return this.setActive({ session: null }); + }; + public __internal_handleUnauthenticatedDevBrowser = async (): Promise => { if (this.#authService) { await this.#authService.handleUnauthenticatedDevBrowser(); diff --git a/packages/clerk-js/src/core/resources/Base.ts b/packages/clerk-js/src/core/resources/Base.ts index 4ad63d5d01c..f4827878580 100644 --- a/packages/clerk-js/src/core/resources/Base.ts +++ b/packages/clerk-js/src/core/resources/Base.ts @@ -157,6 +157,9 @@ export abstract class BaseResource { await BaseResource.clerk.__internal_handleUnauthenticatedDevBrowser(); } else if (status === 401 && code !== 'requires_captcha') { await BaseResource.clerk.handleUnauthenticated(); + } else if (errors?.[0]?.meta?.remaining_attempts === 0) { + // The password confirmation limit ended the session and invalidated this client. + await BaseResource.clerk.__internal_handleSessionEnded(); } assertProductionKeysOnDev(status, errors); diff --git a/packages/clerk-js/src/core/resources/__tests__/Base.test.ts b/packages/clerk-js/src/core/resources/__tests__/Base.test.ts index c67026ffee5..7c40968fb0f 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Base.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Base.test.ts @@ -100,4 +100,46 @@ describe('BaseResource', () => { }), ); }); + it.each([ + [0, 1], + [1, 0], + ])( + 'completes the sign-out when a password confirmation ends the session (remaining attempts: %i)', + async (remainingAttempts, expectedCalls) => { + const updateClient = vi.fn(); + const handleSessionEnded = vi.fn().mockResolvedValue(undefined); + + BaseResource.clerk = { + // @ts-expect-error - We're not about to mock the entire FapiClient + getFapiClient: () => ({ + request: vi.fn().mockResolvedValue({ + payload: { + client: { id: 'client_1', sessions: [] }, + errors: [ + { + code: 'form_password_validation_failed', + message: 'Password is incorrect', + meta: { remaining_attempts: remainingAttempts }, + }, + ], + }, + status: 422, + statusText: 'Unprocessable Entity', + headers: new Headers(), + }), + }), + __internal_setCountry: vi.fn(), + __internal_handleSessionEnded: handleSessionEnded, + updateClient, + } as any; + + const resource = new TestResource(); + await expect(resource.mutate()).rejects.toMatchObject({ + errors: [expect.objectContaining({ meta: expect.objectContaining({ remainingAttempts }) })], + }); + + expect(updateClient).toHaveBeenCalledTimes(1); + expect(handleSessionEnded).toHaveBeenCalledTimes(expectedCalls); + }, + ); }); From 8c3a66f9730ed5f5384e486cecd95ba225a9130e Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Tue, 29 Sep 2026 17:22:09 -0400 Subject: [PATCH 09/10] Only complete the sign-out for 422 password confirmation errors --- packages/clerk-js/src/core/resources/Base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/Base.ts b/packages/clerk-js/src/core/resources/Base.ts index f4827878580..6e5baffb446 100644 --- a/packages/clerk-js/src/core/resources/Base.ts +++ b/packages/clerk-js/src/core/resources/Base.ts @@ -157,7 +157,7 @@ export abstract class BaseResource { await BaseResource.clerk.__internal_handleUnauthenticatedDevBrowser(); } else if (status === 401 && code !== 'requires_captcha') { await BaseResource.clerk.handleUnauthenticated(); - } else if (errors?.[0]?.meta?.remaining_attempts === 0) { + } else if (status === 422 && errors?.[0]?.meta?.remaining_attempts === 0) { // The password confirmation limit ended the session and invalidated this client. await BaseResource.clerk.__internal_handleSessionEnded(); } From faaa07e502b7c3c30ef3ed7cc064762493f9f4de Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Tue, 29 Sep 2026 17:23:34 -0400 Subject: [PATCH 10/10] Drop unused ts-expect-error directives and raise the clerk.native.js budget --- packages/clerk-js/src/core/resources/__tests__/Base.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/clerk-js/src/core/resources/__tests__/Base.test.ts b/packages/clerk-js/src/core/resources/__tests__/Base.test.ts index 7c40968fb0f..299116c47c8 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Base.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Base.test.ts @@ -70,7 +70,6 @@ describe('BaseResource', () => { const updateClient = vi.fn(); BaseResource.clerk = { - // @ts-expect-error - We're not about to mock the entire FapiClient getFapiClient: () => ({ request: vi.fn().mockResolvedValue({ payload: { @@ -110,7 +109,6 @@ describe('BaseResource', () => { const handleSessionEnded = vi.fn().mockResolvedValue(undefined); BaseResource.clerk = { - // @ts-expect-error - We're not about to mock the entire FapiClient getFapiClient: () => ({ request: vi.fn().mockResolvedValue({ payload: {