mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: label the remaining settings icon buttons for screen readers
The filter rule reorder, delete and remove-condition/action buttons, the filter, Sieve and folder editor close buttons and the attachment keyword remove buttons had no label. The Sieve editor's line count now uses an ICU plural, and the settings i18n test covers every pane.
This commit is contained in:
@@ -640,6 +640,13 @@
|
||||
"experimental": {
|
||||
"title": "Experimental Feature"
|
||||
},
|
||||
"filters": {
|
||||
"remove_action": "Remove action",
|
||||
"remove_condition": "Remove condition",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# line} other {# lines}}"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
"account": "Account",
|
||||
"cannot_delete": "Cannot delete",
|
||||
|
||||
@@ -5,27 +5,49 @@ import { describe, it, expect } from 'vitest';
|
||||
import { join } from 'node:path';
|
||||
import { findUntranslatedLiterals } from './untranslated-literals';
|
||||
|
||||
// Pane file → texts that stay as they are on purpose.
|
||||
// Pane file (relative to src/components/) → texts that stay as they are on
|
||||
// purpose: product names, units, format patterns and sample data.
|
||||
const TRANSLATED_PANES: Record<string, string[]> = {
|
||||
'ReadingSettings.tsx': [],
|
||||
'ContactsSettings.tsx': [],
|
||||
'ContentSendersSettings.tsx': [],
|
||||
'LayoutSettings.tsx': [],
|
||||
'CalendarSettings.tsx': [],
|
||||
'IdentitySettings.tsx': [
|
||||
// Example signatures in the placeholders: sample data, not UI text.
|
||||
'settings/AboutDataSettings.tsx': ['MB', 'GitHub'],
|
||||
'settings/AccountSecuritySettings.tsx': ['AES-128', 'AES-256'],
|
||||
'settings/AccountSettings.tsx': [],
|
||||
'settings/AppearanceSettings.tsx': [],
|
||||
'settings/CalendarSettings.tsx': [],
|
||||
// Sub-address delimiter samples ("user+tag").
|
||||
'settings/ComposingSettings.tsx': ['user tag'],
|
||||
'settings/ContactsSettings.tsx': [],
|
||||
'settings/ContentSendersSettings.tsx': [],
|
||||
'settings/DownloadsSettings.tsx': [],
|
||||
'settings/FilesSettings.tsx': [],
|
||||
'settings/FilterSettings.tsx': [],
|
||||
'settings/FolderSettings.tsx': [],
|
||||
'settings/IdentitySettings.tsx': [
|
||||
// Example signatures in the placeholders.
|
||||
'-- Jane Doe Bulwark Mail',
|
||||
'<p><b>Jane Doe</b><br>Bulwark Mail</p>',
|
||||
],
|
||||
'FilterSettings.tsx': [],
|
||||
'SidebarAppsSettings.tsx': ['https://example.com'],
|
||||
'TemplateSettings.tsx': ['JSON', '{"templates": [...]}'],
|
||||
// The raw keyword prefix shown next to a tag id.
|
||||
'settings/KeywordSettings.tsx': ['$label:'],
|
||||
'settings/LanguageSettings.tsx': [],
|
||||
'settings/LayoutSettings.tsx': [],
|
||||
'settings/NotificationSettings.tsx': [],
|
||||
'settings/PluginsSettings.tsx': [],
|
||||
'settings/ReadingSettings.tsx': [],
|
||||
'settings/SidebarAppsSettings.tsx': ['https://example.com'],
|
||||
'settings/SmimeSettings.tsx': [],
|
||||
'settings/TemplateSettings.tsx': ['JSON', '{"templates": [...]}'],
|
||||
'settings/ThemesSettings.tsx': [],
|
||||
'settings/UpdatesSettings.tsx': [],
|
||||
'settings/VacationSettings.tsx': ['YYYY-MM-DD HH:MM'],
|
||||
'settings/settings-section.tsx': [],
|
||||
'filters/FilterRuleModal.tsx': [],
|
||||
'filters/SieveEditorSheet.tsx': [],
|
||||
};
|
||||
|
||||
describe('translated settings panes', () => {
|
||||
for (const [file, allow] of Object.entries(TRANSLATED_PANES)) {
|
||||
it(`${file} has no hard-coded English text`, () => {
|
||||
const literals = findUntranslatedLiterals(join(__dirname, '..', 'settings', file), allow);
|
||||
const literals = findUntranslatedLiterals(join(__dirname, '..', file), allow);
|
||||
expect(literals).toEqual([]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -229,7 +229,13 @@ export function FilterRuleModal({ visible, rule, mailboxes, onSave, onClose }: F
|
||||
<Modal visible={visible} animationType="slide" onRequestClose={onClose} statusBarTranslucent>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<View style={styles.header}>
|
||||
<Pressable onPress={onClose} hitSlop={8} style={styles.headerClose}>
|
||||
<Pressable
|
||||
onPress={onClose}
|
||||
hitSlop={8}
|
||||
style={styles.headerClose}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('common.close', 'Close')}
|
||||
>
|
||||
<X size={20} color={c.text} />
|
||||
</Pressable>
|
||||
<Text style={styles.headerTitle}>
|
||||
@@ -295,6 +301,8 @@ export function FilterRuleModal({ visible, rule, mailboxes, onSave, onClose }: F
|
||||
disabled={conditions.length <= 1}
|
||||
hitSlop={8}
|
||||
style={[styles.removeBtn, conditions.length <= 1 && styles.removeBtnDisabled]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('settings.filters.remove_condition', 'Remove condition')}
|
||||
>
|
||||
<Trash2 size={16} color={c.mutedForeground} />
|
||||
</Pressable>
|
||||
@@ -361,6 +369,8 @@ export function FilterRuleModal({ visible, rule, mailboxes, onSave, onClose }: F
|
||||
disabled={actions.length <= 1}
|
||||
hitSlop={8}
|
||||
style={[styles.removeBtn, actions.length <= 1 && styles.removeBtnDisabled]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('settings.filters.remove_action', 'Remove action')}
|
||||
>
|
||||
<Trash2 size={16} color={c.mutedForeground} />
|
||||
</Pressable>
|
||||
|
||||
@@ -65,7 +65,13 @@ export function SieveEditorSheet({ visible, content, onSave, onClose, onValidate
|
||||
<Modal visible={visible} animationType="slide" onRequestClose={onClose} statusBarTranslucent>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<View style={styles.header}>
|
||||
<Pressable onPress={onClose} hitSlop={8} style={styles.headerClose}>
|
||||
<Pressable
|
||||
onPress={onClose}
|
||||
hitSlop={8}
|
||||
style={styles.headerClose}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('common.close', 'Close')}
|
||||
>
|
||||
<X size={20} color={c.text} />
|
||||
</Pressable>
|
||||
<Text style={styles.headerTitle}>{t('settings.filters.sieve_editor.title', 'Sieve Script Editor')}</Text>
|
||||
@@ -104,7 +110,9 @@ export function SieveEditorSheet({ visible, content, onSave, onClose, onValidate
|
||||
/>
|
||||
</ScrollView>
|
||||
|
||||
<Text style={styles.lineCount}>{lineCount} {lineCount === 1 ? 'line' : 'lines'}</Text>
|
||||
<Text style={styles.lineCount}>
|
||||
{t('settings.filters.sieve_editor.line_count', '{count, plural, one {# line} other {# lines}}', { count: lineCount })}
|
||||
</Text>
|
||||
|
||||
{validationResult && (
|
||||
<View style={[styles.resultBanner, validationResult.isValid ? styles.resultOk : styles.resultErr]}>
|
||||
|
||||
@@ -268,7 +268,7 @@ export function AboutDataSettings() {
|
||||
: sync.phase === 'scanning'
|
||||
? t('settings.offline.scanning', 'Scanning recent mail…')
|
||||
: sync.phase === 'error'
|
||||
? t('settings.offline.last_sync_failed', 'Last sync failed: {message}', { message: sync.message ?? 'unknown error' })
|
||||
? t('settings.offline.last_sync_failed', 'Last sync failed: {message}', { message: sync.message ?? t('identities.validation_errors.unknown_error', 'Unknown error') })
|
||||
: t('settings.offline.last_sync', 'Last sync {when}', { when: formatRelativeTime(sync.finishedAt) })}
|
||||
</Text>
|
||||
{queuedChanges > 0 && (
|
||||
|
||||
@@ -205,7 +205,12 @@ export function ComposingSettings() {
|
||||
{attachmentReminderKeywords.map((kw) => (
|
||||
<View key={kw} style={styles.chip}>
|
||||
<Text style={styles.chipText}>{kw}</Text>
|
||||
<Pressable onPress={() => removeKeyword(kw)} hitSlop={6}>
|
||||
<Pressable
|
||||
onPress={() => removeKeyword(kw)}
|
||||
hitSlop={6}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('settings.email_behavior.attachment_reminder.remove', 'Remove')}
|
||||
>
|
||||
<X size={12} color={c.textSecondary} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -408,6 +408,8 @@ export function FilterSettings({ onOpenVacation }: FilterSettingsProps = {}) {
|
||||
disabled={editIdx <= 0}
|
||||
hitSlop={6}
|
||||
style={[styles.reorderBtn, editIdx <= 0 && styles.reorderBtnDisabled]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('settings.appearance.message_list_order.move_up', 'Move up')}
|
||||
>
|
||||
<ChevronUp size={16} color={c.mutedForeground} />
|
||||
</Pressable>
|
||||
@@ -416,12 +418,20 @@ export function FilterSettings({ onOpenVacation }: FilterSettingsProps = {}) {
|
||||
disabled={editIdx === editableIds.length - 1}
|
||||
hitSlop={6}
|
||||
style={[styles.reorderBtn, editIdx === editableIds.length - 1 && styles.reorderBtnDisabled]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('settings.appearance.message_list_order.move_down', 'Move down')}
|
||||
>
|
||||
<ChevronDown size={16} color={c.mutedForeground} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Pressable onPress={() => handleDelete(rule)} hitSlop={6} style={styles.deleteBtn}>
|
||||
<Pressable
|
||||
onPress={() => handleDelete(rule)}
|
||||
hitSlop={6}
|
||||
style={styles.deleteBtn}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('settings.filters.delete_rule', 'Delete Rule')}
|
||||
>
|
||||
<X size={16} color={c.mutedForeground} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -306,7 +306,12 @@ export function FolderSettings() {
|
||||
? t('settings.folders.edit_folder', `Edit "${editor.mailbox.name}"`, { name: editor.mailbox.name })
|
||||
: t('settings.folders.new_folder', 'New folder')}
|
||||
</Text>
|
||||
<Pressable onPress={closeEditor} hitSlop={8}>
|
||||
<Pressable
|
||||
onPress={closeEditor}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('common.close', 'Close')}
|
||||
>
|
||||
<X size={20} color={c.text} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -232,6 +232,8 @@ export function ReadingSettings() {
|
||||
)
|
||||
}
|
||||
hitSlop={6}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('settings.email_behavior.attachment_reminder.remove', 'Remove')}
|
||||
>
|
||||
<X size={12} color={c.mutedForeground} />
|
||||
</Pressable>
|
||||
|
||||
Reference in New Issue
Block a user