diff --git a/locales/rn/en.json b/locales/rn/en.json index a0e73917..0f49d84c 100644 --- a/locales/rn/en.json +++ b/locales/rn/en.json @@ -226,6 +226,8 @@ "editor_unavailable": "Could not read the message content. Copy your text, then close and reopen the composer.", "format_plain_text": "Switch to plain text", "format_rich_text": "Switch to rich text (HTML)", + "from_options": "Sender options", + "group_chip": "{name} ({count})", "identity_unavailable": "Identity unavailable", "image_load_failed": "Could not load image", "no_recipient": "(no recipient)", @@ -236,6 +238,7 @@ "recipient_copy": "Copy address", "recipient_move_to": "Move to {field}", "recipient_remove": "Remove", + "remove_attachment": "Remove attachment", "save_draft": "Save Draft", "save_failed_no_drafts": "There is no Drafts folder to save this draft to.", "save_failed_no_identity": "Your sender identities could not be loaded, so this draft cannot be saved. Check your connection, or copy your text before closing.", @@ -260,7 +263,9 @@ "remove_background_color": "Remove background color" }, "undo_send_label": "Sending in {seconds}s…", + "upload_failed_short": "Upload failed", "uploading": "Uploading...", + "uploading_pct": "Uploading {pct}%", "validation": { "subject_required": "Add a subject" } diff --git a/src/components/__tests__/screens-i18n.test.ts b/src/components/__tests__/screens-i18n.test.ts index 0ad50169..45ef0a83 100644 --- a/src/components/__tests__/screens-i18n.test.ts +++ b/src/components/__tests__/screens-i18n.test.ts @@ -14,6 +14,9 @@ const TRANSLATED_FILES: Record = { 'components/files/FilePreviewModal.tsx': [], // The empty date filter shows the input format. 'screens/EmailListScreen.tsx': ['YYYY-MM-DD'], + // The link prompt shows an example URL. + 'screens/ComposeScreen.tsx': ['https://example.com'], + 'components/RichTextEditor.tsx': [], 'screens/ScheduledScreen.tsx': [], 'screens/EmailSourceScreen.tsx': [], 'components/OfflineBanner.tsx': [], diff --git a/src/screens/ComposeScreen.tsx b/src/screens/ComposeScreen.tsx index e489750c..ad1898e5 100644 --- a/src/screens/ComposeScreen.tsx +++ b/src/screens/ComposeScreen.tsx @@ -192,8 +192,12 @@ function RecipientChip({ }) { const c = useColors(); const styles = React.useMemo(() => makeStyles(c), [c]); + const t = useLocaleStore((s) => s.t); const label = recipient.group - ? `${recipient.name || 'Group'} (${recipient.group.members.length})` + ? t('email_composer.group_chip', '{name} ({count})', { + name: recipient.name || t('contacts.group', 'Group'), + count: recipient.group.members.length, + }) : recipient.name || recipient.email; return ( @@ -201,7 +205,12 @@ function RecipientChip({ {label} - + @@ -225,6 +234,7 @@ function SuggestionList({ }) { const c = useColors(); const styles = React.useMemo(() => makeStyles(c), [c]); + const t = useLocaleStore((s) => s.t); return ( {suggestions.map((s, i) => ( @@ -244,7 +254,9 @@ function SuggestionList({ {s.name || s.email} {s.group ? ( - {`${s.group.memberCount} ✉`} + + {t('contacts.groups.member_count', '{count, plural, =0 {No members} one {1 member} other {# members}}', { count: s.group.memberCount })} + ) : !!s.name && ( {s.email} )} @@ -256,16 +268,15 @@ function SuggestionList({ } function AttachmentChip({ - attachment, onRemove, onPress, uploadingLabel, cancelLabel, + attachment, onRemove, onPress, }: { attachment: AttachmentEntry; onRemove: () => void; onPress: () => void; - uploadingLabel: string; - cancelLabel: string; }) { const c = useColors(); const styles = React.useMemo(() => makeStyles(c), [c]); + const t = useLocaleStore((s) => s.t); const pct = attachment.progress != null ? Math.round(attachment.progress * 100) : null; return ( @@ -286,7 +297,9 @@ function AttachmentChip({ {attachment.error ? attachment.error : attachment.uploading - ? pct != null ? `${uploadingLabel} ${pct}%` : uploadingLabel + ? pct != null + ? t('email_composer.uploading_pct', 'Uploading {pct}%', { pct }) + : t('email_composer.uploading', 'Uploading...') : formatBytes(attachment.size)} {attachment.uploading && pct != null && ( @@ -299,7 +312,10 @@ function AttachmentChip({ onPress={onRemove} hitSlop={8} style={styles.attachmentRemove} - accessibilityLabel={attachment.uploading ? cancelLabel : undefined} + accessibilityRole="button" + accessibilityLabel={attachment.uploading + ? t('email_composer.upload_cancel', 'Cancel upload') + : t('email_composer.remove_attachment', 'Remove attachment')} > @@ -789,7 +805,7 @@ export default function ComposeScreen({ route, navigation }: Props) { if (!cancelled) { updateAttachment(localId, { uploading: false, - error: e instanceof Error ? e.message : 'Upload failed', + error: e instanceof Error ? e.message : t('email_composer.upload_failed_short', 'Upload failed'), }); } } @@ -1543,7 +1559,7 @@ export default function ComposeScreen({ route, navigation }: Props) { }); } catch (e) { if (e instanceof Error && e.name === 'AbortError') return; // removed by the user - const message = e instanceof Error ? e.message : 'Upload failed'; + const message = e instanceof Error ? e.message : t('email_composer.upload_failed_short', 'Upload failed'); updateAttachment(entry.localId, { uploading: false, progress: undefined, error: message }); Alert.alert( t('email_composer.upload_failed', 'Failed to upload {filename}', { filename: entry.name }), @@ -2177,7 +2193,7 @@ export default function ComposeScreen({ route, navigation }: Props) { } Alert.alert( t('email_composer.send_failed', 'Send failed'), - e instanceof Error ? e.message : 'Failed to send email', + e instanceof Error ? e.message : t('notifications.error_sending', 'Failed to send email'), ); } finally { setSending(false); @@ -2280,7 +2296,13 @@ export default function ComposeScreen({ route, navigation }: Props) { return ( - + {savingDraft ? : } @@ -2296,6 +2318,8 @@ export default function ComposeScreen({ route, navigation }: Props) { onPress={() => setAttachMenuOpen(true)} style={styles.headerBtn} hitSlop={8} + accessibilityRole="button" + accessibilityLabel={t('email_composer.attach', 'Attach')} > @@ -2306,6 +2330,8 @@ export default function ComposeScreen({ route, navigation }: Props) { style={styles.headerBtn} hitSlop={8} disabled={!canSend} + accessibilityRole="button" + accessibilityLabel={t('email_composer.schedule_send', 'Schedule send')} > @@ -2352,7 +2378,13 @@ export default function ComposeScreen({ route, navigation }: Props) { {identities.length > 1 && } {!!primaryIdentity && ( - + )} @@ -2381,8 +2413,6 @@ export default function ComposeScreen({ route, navigation }: Props) { attachment={a} onRemove={() => removeAttachment(a.localId)} onPress={() => { void previewAttachment(a); }} - uploadingLabel={t('email_composer.uploading', 'Uploading...')} - cancelLabel={t('email_composer.upload_cancel', 'Cancel upload')} /> ))} @@ -2790,7 +2820,11 @@ export default function ComposeScreen({ route, navigation }: Props) { )} {t('email_composer.from_override.toggle_off', 'Override')} - + {overrideEnabled && ( <>