diff --git a/src/components/__tests__/calendar-i18n.test.ts b/src/components/__tests__/calendar-i18n.test.ts new file mode 100644 index 00000000..9c6119c0 --- /dev/null +++ b/src/components/__tests__/calendar-i18n.test.ts @@ -0,0 +1,27 @@ +// Calendar screens and sheets whose every user-visible string goes through +// t(). A literal English label, alert or JSX text sneaking back in stays +// English in every locale, so this scans the source for them. +import { describe, it, expect } from 'vitest'; +import { join } from 'node:path'; +import { findUntranslatedLiterals } from './untranslated-literals'; + +const SRC = join(__dirname, '..', '..'); + +// File (relative to src/) → texts that stay as they are on purpose. +const TRANSLATED_FILES: Record = { + 'screens/CalendarScreen.tsx': [], + 'components/calendar/AgendaView.tsx': [], + 'components/calendar/EventCard.tsx': [], + 'components/calendar/MonthScrollView.tsx': [], + 'components/calendar/MonthView.tsx': [], + 'components/calendar/TimeGridScrollView.tsx': [], + 'components/calendar/WeekView.tsx': [], +}; + +describe('translated calendar screens', () => { + for (const [file, allow] of Object.entries(TRANSLATED_FILES)) { + it(`${file} has no hard-coded English text`, () => { + expect(findUntranslatedLiterals(join(SRC, file), allow)).toEqual([]); + }); + } +}); diff --git a/src/components/calendar/WeekView.tsx b/src/components/calendar/WeekView.tsx index 89eaf519..a35bb40f 100644 --- a/src/components/calendar/WeekView.tsx +++ b/src/components/calendar/WeekView.tsx @@ -59,7 +59,7 @@ function WeekViewInner({ }: WeekViewProps) { const c = useColors(); const styles = React.useMemo(() => makeStyles(c), [c]); - const { locale } = useCalendarLocale(); + const { locale, t } = useCalendarLocale(); const scrollRef = React.useRef(null); const index = React.useMemo( @@ -173,7 +173,7 @@ function WeekViewInner({ {allDayRowCount > 0 && ( - all-day + {t('calendar.events.all_day', 'All day')} {/* Empty per-day columns to draw vertical separators */} @@ -209,7 +209,7 @@ function WeekViewInner({ > {segment.continuesBefore ? '… ' : ''} - {segment.event.title || 'Untitled'} + {segment.event.title || t('calendar.events.no_title', '(No title)')} ); @@ -281,7 +281,7 @@ function WeekViewInner({ ]} > - {event.title || 'Untitled'} + {event.title || t('calendar.events.no_title', '(No title)')} {height > 32 && ( diff --git a/src/screens/CalendarScreen.tsx b/src/screens/CalendarScreen.tsx index 2f5142f3..aca6e297 100644 --- a/src/screens/CalendarScreen.tsx +++ b/src/screens/CalendarScreen.tsx @@ -931,6 +931,8 @@ export default function CalendarScreen() { onPress={() => setSidebarVisible(true)} hitSlop={8} style={styles.headerBtn} + accessibilityRole="button" + accessibilityLabel={t('calendar.nav_open_menu', 'Open menu')} > @@ -957,6 +959,8 @@ export default function CalendarScreen() { style={styles.headerBtn} onPress={() => setTasksVisible(true)} hitSlop={6} + accessibilityRole="button" + accessibilityLabel={t('calendar.views.tasks', 'Tasks')} > @@ -969,31 +973,56 @@ export default function CalendarScreen() { : mode === 'day' ? Calendar1 : ListIcon; const active = viewMode === mode; + const label = + mode === 'month' ? t('calendar.views.month', 'Month') + : mode === 'week' ? t('calendar.views.week', 'Week') + : mode === 'day' ? t('calendar.views.day', 'Day') + : t('calendar.views.agenda', 'Agenda'); return ( changeViewMode(mode)} + accessibilityRole="button" + accessibilityLabel={label} + accessibilityState={{ selected: active }} > ); })} - openCreate(selectedDate)}> + openCreate(selectedDate)} + accessibilityRole="button" + accessibilityLabel={t('calendar.events.new_event', 'New event')} + > - + - +