From b7f711050e6d4b6d46322a41961a9aa5fe7745a4 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 24 Sep 2026 20:31:00 +0200 Subject: [PATCH] fix: translate the calendar import sheet --- locales/rn/en.json | 10 ++- src/components/calendar/ICalImportSheet.tsx | 89 +++++++++++++++------ 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/locales/rn/en.json b/locales/rn/en.json index 5304b29c..a698f13d 100644 --- a/locales/rn/en.json +++ b/locales/rn/en.json @@ -29,7 +29,15 @@ "time": "Time" }, "import": { - "refused_count": "{count, plural, one {# event could not be imported:} other {# events could not be imported:}}" + "file_too_large_max": "That file is too large (max {size} MB).", + "imported_count": "{count, plural, one {Imported # event.} other {Imported # events.}}", + "no_calendar": "Create a calendar before importing events.", + "no_calendar_title": "No calendar", + "picker_error": "Could not open the file picker", + "picker_unavailable": "The file picker is not installed in this build. Rebuild the app to enable importing.", + "picker_unavailable_title": "Import unavailable", + "refused_count": "{count, plural, one {# event could not be imported:} other {# events could not be imported:}}", + "selected_count": "{count} selected" }, "invitation": { "accept": "Yes", diff --git a/src/components/calendar/ICalImportSheet.tsx b/src/components/calendar/ICalImportSheet.tsx index b21deef6..a84304f5 100644 --- a/src/components/calendar/ICalImportSheet.tsx +++ b/src/components/calendar/ICalImportSheet.tsx @@ -15,6 +15,8 @@ import { getCalendarColor } from '../../lib/calendar-utils'; import { isWritableCalendar } from '../../lib/calendar-editability'; import { ImportRefusedError, type ImportResult } from '../../stores/calendar-store'; import { useLocaleStore } from '../../stores/locale-store'; +import { useCalendarLocale } from '../../lib/calendar-locale'; +import type { Locale } from 'date-fns'; // expo-document-picker is loaded lazily on first use; its native module is not // linked into every build (matches the FilesScreen / ContactImportSheet flow). @@ -42,11 +44,13 @@ interface Props { onImported?: (count: number) => void; } -function eventDateLabel(event: Partial): string | null { +function eventDateLabel(event: Partial, locale: Locale): string | null { if (!event.start) return null; const d = parseISO(event.start); if (isNaN(d.getTime())) return null; - return event.showWithoutTime ? format(d, 'EEE, MMM d, yyyy') : format(d, 'EEE, MMM d · HH:mm'); + return event.showWithoutTime + ? format(d, 'EEE, MMM d, yyyy', { locale }) + : format(d, 'EEE, MMM d · HH:mm', { locale }); } export function ICalImportSheet({ visible, onClose, calendars, onImport, onImported }: Props) { @@ -54,6 +58,7 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor const styles = React.useMemo(() => makeStyles(c), [c]); const insets = useSafeAreaInsets(); const t = useLocaleStore((s) => s.t); + const { locale } = useCalendarLocale(); const writable = React.useMemo( () => calendars.filter((cal) => isWritableCalendar(cal)), @@ -101,8 +106,11 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor const picker = loadDocumentPicker(); if (!picker) { Alert.alert( - 'Import unavailable', - 'The file picker is not installed in this build. Rebuild the app (expo run:android) to enable importing.', + t('calendar.import.picker_unavailable_title', 'Import unavailable'), + t( + 'calendar.import.picker_unavailable', + 'The file picker is not installed in this build. Rebuild the app to enable importing.', + ), ); return; } @@ -111,13 +119,13 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor try { res = await picker.getDocumentAsync({ copyToCacheDirectory: true, multiple: false }); } catch (e) { - setError(e instanceof Error ? e.message : 'Could not open the file picker'); + setError(e instanceof Error ? e.message : t('calendar.import.picker_error', 'Could not open the file picker')); return; } if (res.canceled || res.assets.length === 0) return; const asset = res.assets[0]; if (asset.size && asset.size > 5 * 1024 * 1024) { - setError('That file is too large (max 5 MB).'); + setError(t('calendar.import.file_too_large_max', 'That file is too large (max {size} MB).', { size: 5 })); return; } @@ -128,13 +136,13 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor const events = await parseCalendarBlob(blobId); const usable = events.filter((e) => !!e.start || e['@type'] === 'Task'); if (usable.length === 0) { - setError('No events were found in that file.'); + setError(t('calendar.import.no_events', 'No events found in file')); return; } setParsed(usable); setSelected(new Set(usable.map((_, i) => i))); } catch (e) { - setError(e instanceof Error ? e.message : 'Could not read or parse that file.'); + setError(e instanceof Error ? e.message : t('calendar.import.invalid_format', 'Invalid calendar file format')); } finally { setLoading(false); } @@ -153,7 +161,10 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor const doImport = async () => { if (!calendarId) { - Alert.alert('No calendar', 'Create a calendar before importing events.'); + Alert.alert( + t('calendar.import.no_calendar_title', 'No calendar'), + t('calendar.import.no_calendar', 'Create a calendar before importing events.'), + ); return; } const toImport = parsed.filter((_, i) => selected.has(i)); @@ -166,7 +177,7 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor } catch (e) { // Everything was refused: list what and why, like a partial import. if (e instanceof ImportRefusedError) setResult({ imported: 0, refused: e.refused }); - else setError(e instanceof Error ? e.message : 'Import failed.'); + else setError(e instanceof Error ? e.message : t('calendar.import.error', 'Failed to import calendar')); } finally { setImporting(false); } @@ -180,7 +191,7 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor - Import calendar + {t('calendar.import.title', 'Import Calendar')} @@ -201,7 +212,11 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor {result.imported > 0 && ( - Imported {result.imported} event{result.imported === 1 ? '' : 's'}. + {t( + 'calendar.import.imported_count', + '{count, plural, one {Imported # event.} other {Imported # events.}}', + { count: result.imported }, + )} )} @@ -215,12 +230,14 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor {result.refused.map(({ event, reason }, idx) => { - const date = eventDateLabel(event); + const date = eventDateLabel(event, locale); return ( - {event.title || 'Untitled event'} + + {event.title || t('calendar.events.no_title', '(No title)')} + {!!date && {date}} {reason} @@ -229,7 +246,7 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor })} - + ) : result !== null ? ( @@ -238,9 +255,13 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor - Imported {result.imported} event{result.imported === 1 ? '' : 's'}. + {t( + 'calendar.import.imported_count', + '{count, plural, one {Imported # event.} other {Imported # events.}}', + { count: result.imported }, + )} - + ) : parsed.length === 0 ? ( @@ -250,8 +271,14 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor ) : ( )} - {loading ? 'Parsing…' : 'Choose an .ics file'} - iCalendar — single or multiple events + + {loading + ? t('calendar.import.parsing', 'Parsing calendar file...') + : t('calendar.import.select_file', 'Select .ics file')} + + + {t('calendar.import.supported_formats', 'Supports iCalendar (.ics) files')} + {!!error && ( @@ -279,18 +306,24 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor - {parsed.length} found · {selected.size} selected + {t('calendar.import.parsed_events', '{count} events found', { count: parsed.length })} + {' · '} + {t('calendar.import.selected_count', '{count} selected', { count: selected.size })} - All - None + + {t('calendar.import.select_all', 'Select all')} + + + {t('calendar.import.deselect_all', 'Deselect all')} + {parsed.map((event, idx) => { const isSelected = selected.has(idx); - const date = eventDateLabel(event); + const date = eventDateLabel(event, locale); return ( - {event.title || 'Untitled event'} + + {event.title || t('calendar.events.no_title', '(No title)')} + {!!date && {date}} @@ -318,13 +353,15 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor )} - +