diff --git a/src/components/__tests__/screens-i18n.test.ts b/src/components/__tests__/screens-i18n.test.ts index 45ef0a83..effbec40 100644 --- a/src/components/__tests__/screens-i18n.test.ts +++ b/src/components/__tests__/screens-i18n.test.ts @@ -17,6 +17,17 @@ const TRANSLATED_FILES: Record = { // The link prompt shows an example URL. 'screens/ComposeScreen.tsx': ['https://example.com'], 'components/RichTextEditor.tsx': [], + 'screens/LoginScreen.tsx': [], + // Product name and example host. + 'screens/login/ChooseStep.tsx': ['Bulwark Mail'], + 'screens/login/ConfirmStep.tsx': [], + 'screens/login/EmailStep.tsx': [], + 'screens/login/LoginNotice.tsx': [], + 'screens/login/LoginShell.tsx': ['Bulwark Mobile v'], + 'screens/login/OptionTile.tsx': [], + 'screens/login/PasswordStep.tsx': [], + 'screens/login/ServerStep.tsx': ['mail.example.com'], + 'screens/login/SigningInStep.tsx': [], 'screens/ScheduledScreen.tsx': [], 'screens/EmailSourceScreen.tsx': [], 'components/OfflineBanner.tsx': [], diff --git a/src/lib/__tests__/login-errors.test.ts b/src/lib/__tests__/login-errors.test.ts index cef5c52b..d455b081 100644 --- a/src/lib/__tests__/login-errors.test.ts +++ b/src/lib/__tests__/login-errors.test.ts @@ -14,6 +14,11 @@ describe('describeLoginError', () => { expect(copy.detail).toMatch(/app password/i); }); + it('explains the auth store\'s "Session expired" notice', () => { + expect(describeLoginError('Session expired').title).toBe('Your session has expired. Please sign in again.'); + expect(describeLoginError('Session expired for this account').title).toBe('Your session has expired. Please sign in again.'); + }); + it('names the host it could not reach', () => { const copy = describeLoginError(named('NetworkError', 'Network request failed'), { serverUrl: 'https://mail.example.com', diff --git a/src/lib/login-errors.ts b/src/lib/login-errors.ts index fd7e357f..70646643 100644 --- a/src/lib/login-errors.ts +++ b/src/lib/login-errors.ts @@ -84,6 +84,12 @@ export function describeLoginError(err: unknown, context: LoginErrorContext = {} }; } + // A saved session the server no longer accepts (the auth store's + // "Session expired" notice when the app lands back on sign-in). + if (lower.includes('session expired')) { + return { title: t('login.session_expired', 'Your session has expired. Please sign in again.') }; + } + if (lower.includes('certificate') || lower.includes('ssl') || lower.includes('tls')) { return { title: t('login.mobile.err_cert_title', "Couldn't verify {host}", { host }), diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx index 9c6ee4c2..12d28cae 100644 --- a/src/screens/LoginScreen.tsx +++ b/src/screens/LoginScreen.tsx @@ -333,7 +333,8 @@ export default function LoginScreen({ onLogin, isAddMode = false, onCancel }: Lo if (step === 'choose') { // A leftover store error ("Session expired") is the reason some people // land back here, so it belongs on this screen. - const chooseNotice = notice ?? (storeError ? { title: storeError } : null); + // The store keeps the raw English message; show the translated copy. + const chooseNotice = notice ?? (storeError ? describeLoginError(storeError, { t }) : null); return ( <> diff --git a/src/screens/login/EmailStep.tsx b/src/screens/login/EmailStep.tsx index 0581e9a8..14627ec7 100644 --- a/src/screens/login/EmailStep.tsx +++ b/src/screens/login/EmailStep.tsx @@ -49,7 +49,7 @@ export default function EmailStep({ makeStyles(c), [c]); + const t = useLocaleStore((s) => s.t); return ( @@ -50,14 +52,26 @@ export default function LoginShell({ > {onBack ? ( - + ) : ( )} {onClose ? ( - + ) : ( diff --git a/src/screens/login/PasswordStep.tsx b/src/screens/login/PasswordStep.tsx index 8fcc5ddb..a77e8193 100644 --- a/src/screens/login/PasswordStep.tsx +++ b/src/screens/login/PasswordStep.tsx @@ -55,7 +55,7 @@ export default function PasswordStep({