From 455608f9f0f30860e964897c190055756a6cc3cd Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 24 Sep 2026 20:42:39 +0200 Subject: [PATCH] fix: translate push setup errors and the open-link prompt The Notifications pane shows a push setup failure as ": " with a translated phase but an English message, and the open-link confirmation and the new-mail notification title were English too. They now go through the locale store's t() (translate() in the background task), with the technical detail passed as an argument. --- locales/rn/en.json | 17 +++++++ src/lib/open-url.ts | Bin 2657 -> 2828 bytes src/lib/push-background-task.ts | 2 +- src/lib/push-notifications.ts | 80 ++++++++++++++++++++++++-------- 4 files changed, 79 insertions(+), 20 deletions(-) diff --git a/locales/rn/en.json b/locales/rn/en.json index 0f49d84c..1338e78e 100644 --- a/locales/rn/en.json +++ b/locales/rn/en.json @@ -105,6 +105,9 @@ "create": "Create", "dismiss": "Dismiss", "done": "Done", + "open": "Open", + "open_link_message": "This will open {host} outside the app.", + "open_link_title": "Open link?", "remove": "Remove", "retry": "Retry", "undo": "Undo", @@ -739,6 +742,20 @@ "push": { "confirm_disable_message_mobile": "This device will stop receiving new-mail alerts for this account.", "description_mobile": "Receive system notifications for new mail when the app is closed. Delivered via the Bulwark push relay; the relay never sees mail content.", + "err_android_only": "Push notifications are only available on Android.", + "err_fcm_empty": "Firebase returned an empty device token.", + "err_fcm_token": "Firebase could not issue a device token ({detail}). Push over FCM needs Google Play services on this device.", + "err_fcm_token_up": "Firebase could not issue a device token ({detail}). Push over FCM needs Google Play services on this device. This device has a UnifiedPush distributor installed - switch the delivery method to UnifiedPush instead.", + "err_jmap_refused": "The mail server refused the push subscription: {detail}", + "err_no_account": "No account loaded - cannot set up push.", + "err_permission": "Notification permission was not granted.", + "err_relay_https": "The relay URL must use https://.", + "err_relay_rejected": "The push relay rejected the registration: {detail}", + "err_relay_unreachable": "Could not reach the push relay at {url} ({detail}).", + "err_up_android_only": "UnifiedPush is only available on Android.", + "err_verify_rejected": "The mail server rejected the verification code: {detail}", + "err_verify_relay": "The relay never received the verification code from the mail server (last relay response: {detail}).", + "err_verify_timeout": "The mail server did not send a verification code to the relay within 75 s. Check that the server can reach the relay URL.", "phase_account": "Account", "phase_distributor": "UnifiedPush distributor", "phase_jmap": "Mail server subscription", diff --git a/src/lib/open-url.ts b/src/lib/open-url.ts index 4cd1aa7448632e52a1f22dbbc19507701732f6ba..5efd2a99a685c2027d2f801eaad2ab627cea3087 100644 GIT binary patch delta 304 zcmZvXu}TCn5Qe!z-LqD)u$V3zIdfoXvxT+TSlAs)oOOdqCTvD5mh)b~E-xbZ9=?Kz zk6~lNah`=!&HQ|PKl6F;a`gH&sf`OXFpR*kgl-@QLaBLV8Zgc+>DJ;|x!yh|&%38w z3r)_ER}|1Mtnm*EHf&MMKwRetO4JBBOHFl{R@L5WAAY9+3LKRt-=_rYbf~b^n6YJcXRhyli`Q9WDhZ$S)`<)=SRMOUo?E^-V1k$BC93;yC diff --git a/src/lib/push-background-task.ts b/src/lib/push-background-task.ts index 2684a190..dacd2778 100644 --- a/src/lib/push-background-task.ts +++ b/src/lib/push-background-task.ts @@ -472,7 +472,7 @@ async function processAccountForPush(accountId: string, payload: RelayPushData): const from = email.from?.[0]; const name = from?.name ?? ''; const address = from?.email ?? ''; - const title = name || address || 'New mail'; + const title = name || address || translate(locale, 'notifications.new_email', 'New email'); const body = email.subject || translate(locale, 'email_viewer.no_subject', '(No Subject)'); const initials = getEmailInitials(name, address); const bgColorHex = hslToHex(generateEmailAvatarColor(name, address)); diff --git a/src/lib/push-notifications.ts b/src/lib/push-notifications.ts index 93a713f5..e25a79b8 100644 --- a/src/lib/push-notifications.ts +++ b/src/lib/push-notifications.ts @@ -14,6 +14,7 @@ import { JMAPMethodError } from '../api/jmap-result'; import { CAPABILITIES } from '../api/types'; import type { EmailPushConfig, JMAPAccountInfo, Mailbox } from '../api/types'; import { generateAccountId } from './account-utils'; +import { t } from '../stores/locale-store'; import { getUnifiedPushDistributors, isUnifiedPushSupported, @@ -568,17 +569,23 @@ async function getFcmTokenOrThrow(native: BulwarkFcmNative): Promise { } catch (err) { const detail = err instanceof Error ? err.message : String(err); const distributors = await getUnifiedPushDistributors().catch(() => []); - const hint = - distributors.length > 0 - ? ' This device has a UnifiedPush distributor installed - switch the delivery method to UnifiedPush instead.' - : ''; throw new PushSetupError( 'token', - `Firebase could not issue a device token (${detail}). Push over FCM needs Google Play services on this device.${hint}`, + distributors.length > 0 + ? t( + 'settings.notifications.push.err_fcm_token_up', + 'Firebase could not issue a device token ({detail}). Push over FCM needs Google Play services on this device. This device has a UnifiedPush distributor installed - switch the delivery method to UnifiedPush instead.', + { detail }, + ) + : t( + 'settings.notifications.push.err_fcm_token', + 'Firebase could not issue a device token ({detail}). Push over FCM needs Google Play services on this device.', + { detail }, + ), ); } if (!token) { - throw new PushSetupError('token', 'Firebase returned an empty device token.'); + throw new PushSetupError('token', t('settings.notifications.push.err_fcm_empty', 'Firebase returned an empty device token.')); } return token; } @@ -655,10 +662,21 @@ async function registerWithRelay(params: { }); } catch (err) { const detail = err instanceof Error ? err.message : String(err); - throw new PushSetupError('relay', `Could not reach the push relay at ${params.relayBaseUrl} (${detail}).`); + throw new PushSetupError( + 'relay', + t('settings.notifications.push.err_relay_unreachable', 'Could not reach the push relay at {url} ({detail}).', { + url: params.relayBaseUrl, + detail, + }), + ); } if (!res.ok) { - throw new PushSetupError('relay', `The push relay rejected the registration: ${await readRelayError(res)}`); + throw new PushSetupError( + 'relay', + t('settings.notifications.push.err_relay_rejected', 'The push relay rejected the registration: {detail}', { + detail: await readRelayError(res), + }), + ); } } @@ -686,10 +704,21 @@ async function registerWithRelayUnifiedPush(params: { }); } catch (err) { const detail = err instanceof Error ? err.message : String(err); - throw new PushSetupError('relay', `Could not reach the push relay at ${params.relayBaseUrl} (${detail}).`); + throw new PushSetupError( + 'relay', + t('settings.notifications.push.err_relay_unreachable', 'Could not reach the push relay at {url} ({detail}).', { + url: params.relayBaseUrl, + detail, + }), + ); } if (!res.ok) { - throw new PushSetupError('relay', `The push relay rejected the registration: ${await readRelayError(res)}`); + throw new PushSetupError( + 'relay', + t('settings.notifications.push.err_relay_rejected', 'The push relay rejected the registration: {detail}', { + detail: await readRelayError(res), + }), + ); } } @@ -768,8 +797,15 @@ async function pollVerificationCode( throw new PushSetupError( 'verify', lastRelayError - ? `The relay never received the verification code from the mail server (last relay response: ${lastRelayError}).` - : 'The mail server did not send a verification code to the relay within 75 s. Check that the server can reach the relay URL.', + ? t( + 'settings.notifications.push.err_verify_relay', + 'The relay never received the verification code from the mail server (last relay response: {detail}).', + { detail: lastRelayError }, + ) + : t( + 'settings.notifications.push.err_verify_timeout', + 'The mail server did not send a verification code to the relay within 75 s. Check that the server can reach the relay URL.', + ), ); } @@ -812,22 +848,22 @@ async function setupPushNotificationsInner( const transport = await getEffectivePushTransport(); const native = getNative(); if (transport === 'fcm' && !native) { - throw new PushSetupError('platform', 'Push notifications are only available on Android.'); + throw new PushSetupError('platform', t('settings.notifications.push.err_android_only', 'Push notifications are only available on Android.')); } if (transport === 'unifiedpush' && !isUnifiedPushSupported()) { - throw new PushSetupError('platform', 'UnifiedPush is only available on Android.'); + throw new PushSetupError('platform', t('settings.notifications.push.err_up_android_only', 'UnifiedPush is only available on Android.')); } const relayBaseUrl = (params.relayBaseUrl ?? DEFAULT_RELAY_BASE_URL).replace(/\/+$/, ''); if (!relayBaseUrl) throw new PushSetupError('relay', 'relayBaseUrl is required'); if (!isValidRelayUrl(relayBaseUrl)) { - throw new PushSetupError('relay', 'The relay URL must use https://.'); + throw new PushSetupError('relay', t('settings.notifications.push.err_relay_https', 'The relay URL must use https://.')); } logPhase('permission'); const granted = await requestNotificationPermission(); if (!granted) { - throw new PushSetupError('permission', 'Notification permission was not granted.'); + throw new PushSetupError('permission', t('settings.notifications.push.err_permission', 'Notification permission was not granted.')); } logPhase('token', transport); @@ -844,7 +880,7 @@ async function setupPushNotificationsInner( const username = jmapClient.username; const serverUrl = jmapClient.serverUrl; if (!username || !serverUrl) { - throw new PushSetupError('account', 'No account loaded - cannot set up push.'); + throw new PushSetupError('account', t('settings.notifications.push.err_no_account', 'No account loaded - cannot set up push.')); } const accountId = generateAccountId(username, serverUrl); @@ -963,7 +999,10 @@ async function setupPushNotificationsInner( await rememberRefusedEmailPushAccounts(accountId, refusedBefore, emailPush, created.emailPush); } catch (err) { const detail = err instanceof Error ? err.message : String(err); - throw new PushSetupError('jmap', `The mail server refused the push subscription: ${detail}`); + throw new PushSetupError( + 'jmap', + t('settings.notifications.push.err_jmap_refused', 'The mail server refused the push subscription: {detail}', { detail }), + ); } logPhase('verify'); @@ -972,7 +1011,10 @@ async function setupPushNotificationsInner( await verifyPushSubscription(serverAssignedId, verificationCode); } catch (err) { const detail = err instanceof Error ? err.message : String(err); - throw new PushSetupError('verify', `The mail server rejected the verification code: ${detail}`); + throw new PushSetupError( + 'verify', + t('settings.notifications.push.err_verify_rejected', 'The mail server rejected the verification code: {detail}', { detail }), + ); } await AsyncStorage.setItem(subKey, serverAssignedId);