mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: translate the week view and label the calendar toolbar
The week view's "all-day" label and its "Untitled" fallback were hard-coded; they now use the catalog's "All day" and "(No title)" like the scrolling time grid. The calendar's menu, tasks, view switch, new event and previous/next buttons get screen reader labels (the view buttons also say which one is selected), and a calendar i18n test covers the calendar views.
This commit is contained in:
@@ -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<string, string[]> = {
|
||||
'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([]);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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<ScrollView>(null);
|
||||
|
||||
const index = React.useMemo(
|
||||
@@ -173,7 +173,7 @@ function WeekViewInner({
|
||||
{allDayRowCount > 0 && (
|
||||
<View style={[styles.allDayStrip, { height: allDayStripHeight }]}>
|
||||
<View style={[styles.gutter, styles.allDayLabelWrap]}>
|
||||
<Text style={styles.allDayLabel}>all-day</Text>
|
||||
<Text style={styles.allDayLabel} numberOfLines={2}>{t('calendar.events.all_day', 'All day')}</Text>
|
||||
</View>
|
||||
<View style={styles.allDayGrid}>
|
||||
{/* Empty per-day columns to draw vertical separators */}
|
||||
@@ -209,7 +209,7 @@ function WeekViewInner({
|
||||
>
|
||||
<Text style={styles.allDayChipText} numberOfLines={1}>
|
||||
{segment.continuesBefore ? '… ' : ''}
|
||||
{segment.event.title || 'Untitled'}
|
||||
{segment.event.title || t('calendar.events.no_title', '(No title)')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
@@ -281,7 +281,7 @@ function WeekViewInner({
|
||||
]}
|
||||
>
|
||||
<Text style={styles.eventBlockTitle} numberOfLines={1}>
|
||||
{event.title || 'Untitled'}
|
||||
{event.title || t('calendar.events.no_title', '(No title)')}
|
||||
</Text>
|
||||
{height > 32 && (
|
||||
<Text style={styles.eventBlockTime} numberOfLines={1}>
|
||||
|
||||
@@ -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')}
|
||||
>
|
||||
<Menu size={20} color={c.text} />
|
||||
</Pressable>
|
||||
@@ -957,6 +959,8 @@ export default function CalendarScreen() {
|
||||
style={styles.headerBtn}
|
||||
onPress={() => setTasksVisible(true)}
|
||||
hitSlop={6}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('calendar.views.tasks', 'Tasks')}
|
||||
>
|
||||
<ListChecks size={20} color={c.text} />
|
||||
</Pressable>
|
||||
@@ -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 (
|
||||
<Pressable
|
||||
key={mode}
|
||||
style={[styles.viewToggleBtn, active && styles.viewToggleBtnActive]}
|
||||
onPress={() => changeViewMode(mode)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={label}
|
||||
accessibilityState={{ selected: active }}
|
||||
>
|
||||
<Icon size={16} color={active ? c.primary : c.textMuted} />
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<Pressable style={styles.fab} onPress={() => openCreate(selectedDate)}>
|
||||
<Pressable
|
||||
style={styles.fab}
|
||||
onPress={() => openCreate(selectedDate)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('calendar.events.new_event', 'New event')}
|
||||
>
|
||||
<Plus size={18} color={c.primaryForeground} />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.nav}>
|
||||
<Pressable onPress={goPrev} style={styles.navBtn} hitSlop={8}>
|
||||
<Pressable
|
||||
onPress={goPrev}
|
||||
style={styles.navBtn}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('calendar.nav_prev', 'Previous')}
|
||||
>
|
||||
<ChevronLeft size={20} color={c.text} />
|
||||
</Pressable>
|
||||
<Button variant="outline" size="sm" onPress={goToday}>
|
||||
{t('calendar.views.today', 'Today')}
|
||||
</Button>
|
||||
<Pressable onPress={goNext} style={styles.navBtn} hitSlop={8}>
|
||||
<Pressable
|
||||
onPress={goNext}
|
||||
style={styles.navBtn}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('calendar.nav_next', 'Next')}
|
||||
>
|
||||
<ChevronRight size={20} color={c.text} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user