From b240d638ef8f652c48bf163be400f0890eb7c7cc Mon Sep 17 00:00:00 2001 From: fegger Date: Mon, 18 May 2026 21:50:46 +0200 Subject: [PATCH] Translate UI text to English Translate mobile app UI text to English --- apps/mobile/src/__tests__/screens.test.tsx | 46 ++++++------- apps/mobile/src/components/BikeSection.tsx | 8 +-- apps/mobile/src/components/JourneyList.tsx | 34 +++++----- apps/mobile/src/components/NearbyStops.tsx | 6 +- apps/mobile/src/hooks/useWienerLinien.ts | 2 +- apps/mobile/src/screens/AddEventScreen.tsx | 34 +++++----- .../src/screens/CalendarImportScreen.tsx | 44 ++++++------- apps/mobile/src/screens/EventDetailScreen.tsx | 30 ++++----- apps/mobile/src/screens/EventListScreen.tsx | 56 +++++++--------- apps/mobile/src/screens/SettingsScreen.tsx | 66 +++++++++---------- 10 files changed, 159 insertions(+), 167 deletions(-) diff --git a/apps/mobile/src/__tests__/screens.test.tsx b/apps/mobile/src/__tests__/screens.test.tsx index 9a8f923..fd109ba 100644 --- a/apps/mobile/src/__tests__/screens.test.tsx +++ b/apps/mobile/src/__tests__/screens.test.tsx @@ -166,7 +166,7 @@ describe('EventListScreen', () => { ); await waitFor(() => { - expect(getByText('Keine kommenden Termine')).toBeTruthy(); + expect(getByText('No upcoming events')).toBeTruthy(); }); }); @@ -197,9 +197,9 @@ describe('EventListScreen', () => { expect(getByText('Test Destination')).toBeTruthy(); }); await waitFor(() => { - expect(getByText('Losgehen in')).toBeTruthy(); + expect(getByText('Leave in')).toBeTruthy(); expect(getByText('S1 -> Wien')).toBeTruthy(); - expect(getByText('Inkl. Fußweg zur Station: 10 min')).toBeTruthy(); + expect(getByText('Incl. walk to station: 10 min')).toBeTruthy(); }); }); @@ -241,12 +241,12 @@ describe('AddEventScreen', () => { ()} route={mockRouteAddEvent} /> ); - expect(getByPlaceholderText('z.B. Team Meeting')).toBeTruthy(); - expect(getByPlaceholderText('z.B. Technikum Wien')).toBeTruthy(); - expect(getByPlaceholderText('JJJJ-MM-TT')).toBeTruthy(); - expect(getByPlaceholderText('SS:MM')).toBeTruthy(); - expect(getByText('Speichern')).toBeTruthy(); - expect(getByText('Abbrechen')).toBeTruthy(); + expect(getByPlaceholderText('e.g. Team Meeting')).toBeTruthy(); + expect(getByPlaceholderText('e.g. Technikum Wien')).toBeTruthy(); + expect(getByPlaceholderText('YYYY-MM-DD')).toBeTruthy(); + expect(getByPlaceholderText('HH:MM')).toBeTruthy(); + expect(getByText('Save')).toBeTruthy(); + expect(getByText('Cancel')).toBeTruthy(); }); it('should show validation errors', () => { @@ -255,11 +255,11 @@ describe('AddEventScreen', () => { ); // Try to save without filling form - const saveButton = getByText('Speichern'); + const saveButton = getByText('Save'); fireEvent.press(saveButton); // Should show error text - expect(getByText('Titel erforderlich')).toBeTruthy(); + expect(getByText('Title required')).toBeTruthy(); }); it('should validate date format', () => { @@ -268,15 +268,15 @@ describe('AddEventScreen', () => { ); // Fill in all required fields except date format is invalid - fireEvent.changeText(getByPlaceholderText('z.B. Team Meeting'), 'Meeting'); - fireEvent.changeText(getByPlaceholderText('z.B. Technikum Wien'), 'Wien'); - fireEvent.changeText(getByPlaceholderText('JJJJ-MM-TT'), 'invalid-date'); - fireEvent.changeText(getByPlaceholderText('SS:MM'), '12:00'); + fireEvent.changeText(getByPlaceholderText('e.g. Team Meeting'), 'Meeting'); + fireEvent.changeText(getByPlaceholderText('e.g. Technikum Wien'), 'Wien'); + fireEvent.changeText(getByPlaceholderText('YYYY-MM-DD'), 'invalid-date'); + fireEvent.changeText(getByPlaceholderText('HH:MM'), '12:00'); - const saveButton = getByText('Speichern'); + const saveButton = getByText('Save'); fireEvent.press(saveButton); - expect(getByText('Ungültiges Datum')).toBeTruthy(); + expect(getByText('Invalid date')).toBeTruthy(); }); it('should validate future date', () => { @@ -285,14 +285,14 @@ describe('AddEventScreen', () => { ); // Fill in all required fields with a past date - fireEvent.changeText(getByPlaceholderText('z.B. Team Meeting'), 'Meeting'); - fireEvent.changeText(getByPlaceholderText('z.B. Technikum Wien'), 'Wien'); - fireEvent.changeText(getByPlaceholderText('JJJJ-MM-TT'), '2020-01-01'); - fireEvent.changeText(getByPlaceholderText('SS:MM'), '12:00'); + fireEvent.changeText(getByPlaceholderText('e.g. Team Meeting'), 'Meeting'); + fireEvent.changeText(getByPlaceholderText('e.g. Technikum Wien'), 'Wien'); + fireEvent.changeText(getByPlaceholderText('YYYY-MM-DD'), '2020-01-01'); + fireEvent.changeText(getByPlaceholderText('HH:MM'), '12:00'); - const saveButton = getByText('Speichern'); + const saveButton = getByText('Save'); fireEvent.press(saveButton); - expect(getByText('Datum muss in der Zukunft liegen')).toBeTruthy(); + expect(getByText('Date must be in the future')).toBeTruthy(); }); }); diff --git a/apps/mobile/src/components/BikeSection.tsx b/apps/mobile/src/components/BikeSection.tsx index 36627cf..185ebcf 100644 --- a/apps/mobile/src/components/BikeSection.tsx +++ b/apps/mobile/src/components/BikeSection.tsx @@ -19,12 +19,12 @@ interface Props { export function BikeSection({ bikeRoute, loading, origin, colors }: Props) { return ( - Radroute + Bike route {loading ? ( - Radroute wird geladen… + Loading bike route… ) : bikeRoute ? ( @@ -37,12 +37,12 @@ export function BikeSection({ bikeRoute, loading, origin, colors }: Props) { {formatDistance(bikeRoute.distance)} - 🗺 Karte (post-MVP) + 🗺 Map (post-MVP) ) : ( - {origin ? 'Keine Radroute verfügbar' : 'Ursprungstation festlegen'} + {origin ? 'No bike route available' : 'Set origin station'} )} diff --git a/apps/mobile/src/components/JourneyList.tsx b/apps/mobile/src/components/JourneyList.tsx index 0fc75cd..0814f44 100644 --- a/apps/mobile/src/components/JourneyList.tsx +++ b/apps/mobile/src/components/JourneyList.tsx @@ -45,25 +45,25 @@ export function JourneyList({ return ( - Zugverbindungen + Train connections {destStationLoading && ( - Ziel-Station wird aufgelöst… + Resolving destination station… )} {journeys.length === 0 && !destStationLoading ? ( - {origin ? 'Keine Verbindungen gefunden' : 'Ursprungstation festlegen'} + {origin ? 'No connections found' : 'Set origin station'} ) : ( 1 ? 0.75 : 1} onPress={() => rankedJourneys.length > 1 && setExpanded((current) => !current)} accessibilityRole={rankedJourneys.length > 1 ? 'button' : undefined} - accessibilityLabel={expanded ? 'Weniger Zugverbindungen anzeigen' : 'Alle Zugverbindungen anzeigen'} + accessibilityLabel={expanded ? 'Show fewer train connections' : 'Show all train connections'} > {visibleJourneys.map(({ journey: j }, index) => { const finalArrival = new Date(j.rA.getTime() + walkDurationMs); @@ -90,24 +90,24 @@ export function JourneyList({ +{j.delay} min )} {j.cancelled && ( - Storniert + Cancelled )} - Abfahrt: {new Date(j.sD).toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit' })} - {' '}(Plattform {j.platform || '—'}) + Departure: {new Date(j.sD).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })} + {' '}(Platform {j.platform || '—'}) - Ankunft: {new Date(j.sA).toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit' })} - {' '}({j.changes === 0 ? 'Direkt' : `${j.changes} Umst.`}) + Arrival: {new Date(j.sA).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })} + {' '}({j.changes === 0 ? 'Direct' : `${j.changes} tr.`}) - Dauer: {durationMinutes} min + Duration: {durationMinutes} min {walkDurationMs > 0 && ( - Ziel: {finalArrival.toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit' })} - {arrivesTooLate ? ' (zu spät)' : ''} + Arrive: {finalArrival.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })} + {arrivesTooLate ? ' (too late)' : ''} )} @@ -115,7 +115,7 @@ export function JourneyList({ })} {rankedJourneys.length > 1 && ( - {expanded ? 'Weniger Verbindungen anzeigen' : `${rankedJourneys.length - 1} weitere Verbindungen anzeigen`} + {expanded ? 'Show fewer connections' : `Show ${rankedJourneys.length - 1} more connections`} )} @@ -123,13 +123,13 @@ export function JourneyList({ {showWalkingOption && walkRoute && ( - 🚶 Finaler Fußweg + 🚶 Final walk - ⏱ Dauer + ⏱ Duration {formatDuration(walkRoute.duration)} - 📏 Distanz + 📏 Distance {formatDistance(walkRoute.distance)} @@ -138,7 +138,7 @@ export function JourneyList({ {showWalkingOption && loadingWalk && ( - Fußweg wird geladen… + Loading walk route… )} diff --git a/apps/mobile/src/components/NearbyStops.tsx b/apps/mobile/src/components/NearbyStops.tsx index 7c3e6c5..85e6db0 100644 --- a/apps/mobile/src/components/NearbyStops.tsx +++ b/apps/mobile/src/components/NearbyStops.tsx @@ -22,12 +22,12 @@ export function NearbyStops({ stops, departures, loading, error, colors }: Props return ( - 🚏 ÖPNV in der Nähe des Ziels + 🚏 Public transit near destination {loading ? ( - Haltestellen werden geladen… + Loading stops… ) : departures.length > 0 ? ( departures.slice(0, 8).map((dep, i) => ( @@ -43,7 +43,7 @@ export function NearbyStops({ stops, departures, loading, error, colors }: Props {dep.direction} - {dep.minutes === 0 ? 'jetzt' : `${dep.minutes} min`} + {dep.minutes === 0 ? 'now' : `${dep.minutes} min`} diff --git a/apps/mobile/src/hooks/useWienerLinien.ts b/apps/mobile/src/hooks/useWienerLinien.ts index 23c7a1c..3e41304 100644 --- a/apps/mobile/src/hooks/useWienerLinien.ts +++ b/apps/mobile/src/hooks/useWienerLinien.ts @@ -93,7 +93,7 @@ export function useWienerLinien( await fetchMonitor(ids); } catch (err) { if (cancelledRef.current) return; - setError(err instanceof Error ? err.message : 'Haltestellen konnten nicht geladen werden'); + setError(err instanceof Error ? err.message : 'Stops could not be loaded'); setLoading(false); } }, DEBOUNCE_MS); diff --git a/apps/mobile/src/screens/AddEventScreen.tsx b/apps/mobile/src/screens/AddEventScreen.tsx index d2e09be..6372e99 100644 --- a/apps/mobile/src/screens/AddEventScreen.tsx +++ b/apps/mobile/src/screens/AddEventScreen.tsx @@ -52,12 +52,12 @@ export function AddEventScreen({ navigation, route }: ScreenProps) { }, [route.params?.editEventId]); const validate = (): boolean => { - if (!title.trim()) { setError('Titel erforderlich'); return false; } - if (!destination.trim()) { setError('Ziel erforderlich'); return false; } - if (!dateStr || !timeStr) { setError('Datum und Zeit erforderlich'); return false; } + if (!title.trim()) { setError('Title required'); return false; } + if (!destination.trim()) { setError('Destination required'); return false; } + if (!dateStr || !timeStr) { setError('Date and time required'); return false; } const eventTime = new Date(`${dateStr}T${timeStr}`); - if (isNaN(eventTime.getTime())) { setError('Ungültiges Datum'); return false; } - if (eventTime <= new Date()) { setError('Datum muss in der Zukunft liegen'); return false; } + if (isNaN(eventTime.getTime())) { setError('Invalid date'); return false; } + if (eventTime <= new Date()) { setError('Date must be in the future'); return false; } setError(''); return true; }; @@ -96,40 +96,40 @@ export function AddEventScreen({ navigation, route }: ScreenProps) { return ( - Titel + Title - Ziel + Destination - Datum + Date - Zeit + Time {error} : null} - {route.params?.editEventId ? 'Aktualisieren' : 'Speichern'} + {route.params?.editEventId ? 'Update' : 'Save'} navigation.goBack()} > - Abbrechen + Cancel @@ -156,9 +156,9 @@ export function AddEventScreen({ navigation, route }: ScreenProps) { - Erfolg! + Success! - {route.params?.editEventId ? 'Termin aktualisiert' : 'Termin hinzugefügt'} + {route.params?.editEventId ? 'Event updated' : 'Event added'} diff --git a/apps/mobile/src/screens/CalendarImportScreen.tsx b/apps/mobile/src/screens/CalendarImportScreen.tsx index d0e4b1c..6308116 100644 --- a/apps/mobile/src/screens/CalendarImportScreen.tsx +++ b/apps/mobile/src/screens/CalendarImportScreen.tsx @@ -158,7 +158,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { const handleImport = async () => { if (!url.trim()) { - setError('Bitte ICS-URL eingeben'); + setError('Please enter ICS URL'); return; } @@ -188,7 +188,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { } setCount(added); } catch (err) { - setError(err instanceof Error ? err.message : 'Import fehlgeschlagen'); + setError(err instanceof Error ? err.message : 'Import failed'); } finally { setLoading(false); } @@ -228,7 +228,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { setCount(added); } catch (err) { - setError(err instanceof Error ? err.message : 'Sync fehlgeschlagen'); + setError(err instanceof Error ? err.message : 'Sync failed'); } finally { setLoading(false); } @@ -240,14 +240,14 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { return ( - Kalender-Import + Calendar Import - Importiere Termine über eine ICS-URL oder sync mit dem Geräte-Kalender. + Import events via an ICS URL or sync with the device calendar. {/* ── ICS URL Import ── */} - ICS-URL Import + ICS URL Import ) : ( - ICS Importieren + Import ICS )} @@ -275,33 +275,33 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { {/* ── Calendar Selection ── */} - Kalender auswählen + Select calendars - - Alle + + All · - - Keine + + None - Wähle die Kalender aus, die gesynct werden sollen. Ohne Auswahl werden alle Kalender verwendet. + Select the calendars to sync. If no selection is made, all calendars will be used. {'\n'} - CalDAV-Quellen (DAVx5, Apple Calendar, etc.) werden automatisch erkannt. + CalDAV sources (DAVx5, Apple Calendar, etc.) are detected automatically. {!calendarsLoaded ? ( - Kalender werden geladen… + Loading calendars… ) : availableCalendars.length === 0 ? ( - Keine Kalender auf diesem Gerät gefunden. + No calendars found on this device. ) : ( @@ -332,7 +332,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { {hasSelection && ( - {selectedCalendarIds.size} von {availableCalendars.length} Kalendern ausgewählt + {selectedCalendarIds.size} of {availableCalendars.length} calendars selected )} @@ -342,9 +342,9 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { {/* ── Device Calendar Sync ── */} - Geräte-Kalender Sync + Device Calendar Sync - Hole Termine der nächsten 30 Tage aus den Kalendern auf deinem Gerät. + Fetch events for the next 30 days from the calendars on your device. - 📅 Kalender Sync + 📅 Calendar Sync @@ -365,7 +365,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { {count !== null && ( - ✓ {count} Termin(e) erfolgreich importiert! + ✓ {count} event(s) successfully imported! )} @@ -374,7 +374,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) { style={styles.backBtn} onPress={() => navigation.goBack()} > - ← Zurück + ← Back diff --git a/apps/mobile/src/screens/EventDetailScreen.tsx b/apps/mobile/src/screens/EventDetailScreen.tsx index b285aa1..60ce587 100644 --- a/apps/mobile/src/screens/EventDetailScreen.tsx +++ b/apps/mobile/src/screens/EventDetailScreen.tsx @@ -89,7 +89,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) { setShowWalkingOption(settings.showWalkingOption); const found = events.find((e) => e.id === eventId); - if (!found) { setError('Termin nicht gefunden'); return; } + if (!found) { setError('Event not found'); return; } setEvent(found); if (originStation) { @@ -112,7 +112,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) { const results = await api.searchJourneys(originStation.extId, destExtId, target, { arriveBy: true }); setJourneys(results); } else if (destStation.error) { - setError(`Ziel-Station nicht auflösbar: ${destStation.error}`); + setError(`Destination station not resolvable: ${destStation.error}`); } try { @@ -131,7 +131,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) { } } } catch (err) { - setError(err instanceof Error ? err.message : 'Fehler beim Laden'); + setError(err instanceof Error ? err.message : 'Error loading'); } finally { setLoading(false); } @@ -170,7 +170,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) { - {destStation.loading && !event ? 'Ziel-Station wird aufgelöst…' : 'Termine werden geladen…'} + {destStation.loading && !event ? 'Resolving destination station…' : 'Loading events…'} ); @@ -200,9 +200,9 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) { {!origin && !error && ( - Keine Ursprungstation festgelegt.{' '} + No origin station set.{' '} navigation.navigate('Settings')}> - Einstellungen öffnen + Open settings @@ -218,15 +218,15 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) { onPress={() => setActiveMode('train')} > - 🚆 Zug + 🚆 Train {effectiveMode === 'train' && ( - Aktiv + Active )} - {showWalkingOption ? 'Bahn + finaler Fußweg' : 'Nur Bahn'} + {showWalkingOption ? 'Train + final walk' : 'Train only'} @@ -240,19 +240,19 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) { disabled={bikeDisabled} > - 🚲 Rad + 🚲 Bike {effectiveMode === 'bike' && ( - Aktiv + Active )} {bikeDisabled - ? 'In Einstellungen deaktiviert' + ? 'Disabled in settings' : loadingBike - ? 'Route wird berechnet...' - : 'Direktweg'} + ? 'Calculating route...' + : 'Direct route'} @@ -293,7 +293,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) { style={[styles.refreshBtn, { backgroundColor: colors.border }]} onPress={handleRefresh} > - 🔄 Neu laden + 🔄 Refresh diff --git a/apps/mobile/src/screens/EventListScreen.tsx b/apps/mobile/src/screens/EventListScreen.tsx index ca1ad56..2df02ca 100644 --- a/apps/mobile/src/screens/EventListScreen.tsx +++ b/apps/mobile/src/screens/EventListScreen.tsx @@ -145,7 +145,7 @@ export function EventListScreen({ navigation }: ScreenProps) { setJourneys(results); } catch (err) { if (!isMounted) return; - setJourneysError(err instanceof Error ? err.message : 'Verbindungen konnten nicht geladen werden'); + setJourneysError(err instanceof Error ? err.message : 'Connections could not be loaded'); } finally { if (isMounted) setJourneysLoading(false); } @@ -175,16 +175,16 @@ export function EventListScreen({ navigation }: ScreenProps) { const renderItem = ({ item }: { item: CalendarEvent }) => { const leaveBy = departureInfo.departureTime; const leaveCountdown = leaveBy ? calculateCountdown(leaveBy) : null; - const leaveCountdownLabel = leaveCountdown?.label === 'Now' ? 'Jetzt' : leaveCountdown?.label; + const leaveCountdownLabel = leaveCountdown?.label; const leaveByLabel = leaveBy ? formatTime(leaveBy) : null; - const trainLabel = selectedJourney?.trains.length ? selectedJourney.trains.join(', ') : 'Zugverbindung wird gesucht'; + const trainLabel = selectedJourney?.trains.length ? selectedJourney.trains.join(', ') : 'Searching for train connection'; const status = leaveCountdown ? leaveCountdown.label === 'Now' - ? 'Jetzt losgehen' - : `Losgehen in ${leaveCountdown.label}` + ? 'Leave now' + : `Leave in ${leaveCountdown.label}` : journeysLoading || destStation.loading - ? 'Losgehzeit wird berechnet' - : 'Keine Verbindung'; + ? 'Calculating departure time' + : 'No connection'; return ( @@ -197,12 +197,13 @@ export function EventListScreen({ navigation }: ScreenProps) { {item.title} - - {leaveByLabel ?? '--:--'} - {item.destination} - Losgehen in + Time To Leave + + {leaveByLabel ?? '--:--'} + + Leave in - {journeysError ? 'Zugverbindung nicht erreichbar' : trainLabel} + {journeysError ? 'Train connection unavailable' : trainLabel} {selectedJourney ? ( - Abfahrt {formatTime(selectedJourney.rD)} - {selectedJourney.platform ? ` · Gleis ${selectedJourney.platform}` : ''} + Departure {formatTime(selectedJourney.rD)} + {selectedJourney.platform ? ` · Platform ${selectedJourney.platform}` : ''} {' · '} - Ankunft {formatTime(selectedJourney.rA)} + Arrival {formatTime(selectedJourney.rA)} {' · '} - {selectedJourney.changes === 0 ? 'Direkt' : `${selectedJourney.changes} Umstiege`} + {selectedJourney.changes === 0 ? 'Direct' : `${selectedJourney.changes} transfers`} ) : ( - {journeysError ?? (origin ? 'Beste Verbindung für den nächsten Termin' : 'Ursprungstation festlegen')} + {journeysError ?? (origin ? 'Best connection for the next event' : 'Set origin station')} )} {originWalk.walkRoute && originWalk.walkRoute.duration > 30 && ( - Inkl. Fußweg zur Station: {Math.ceil(originWalk.walkRoute.duration / 60)} min + Incl. walk to station: {Math.ceil(originWalk.walkRoute.duration / 60)} min )} - - Termin: {item.eventTime.toLocaleString('de-AT', { - day: '2-digit', - month: '2-digit', - hour: '2-digit', - minute: '2-digit', - })} - - {status} @@ -256,12 +248,12 @@ export function EventListScreen({ navigation }: ScreenProps) { onPress={() => navigation.navigate('AddEvent', { editEventId: item.id })} style={styles.editBtn} > - Bearbeiten + edit {/* Delete button */} removeEvent(item.id, reload)} style={styles.deleteBtn}> - Entfernen + delete ); @@ -270,12 +262,12 @@ export function EventListScreen({ navigation }: ScreenProps) { if (!upcomingEvent) { return ( - Keine kommenden Termine + No upcoming events navigation.navigate('AddEvent')} > - + Termin hinzufügen + + Add event ); diff --git a/apps/mobile/src/screens/SettingsScreen.tsx b/apps/mobile/src/screens/SettingsScreen.tsx index 390b47e..c2dd7e8 100644 --- a/apps/mobile/src/screens/SettingsScreen.tsx +++ b/apps/mobile/src/screens/SettingsScreen.tsx @@ -73,10 +73,10 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) { try { const stations = await api.searchStation(q.trim()); setResults(stations); - if (stations.length === 0) setSearchError('Keine Stationen gefunden.'); + if (stations.length === 0) setSearchError('No stations found.'); } catch { setResults([]); - setSearchError('API nicht erreichbar. Läuft der Server auf deinem Gerät? Überprüfe EXPO_PUBLIC_API_BASE_URL in .env.'); + setSearchError('API not reachable. Is the server running on your device? Check EXPO_PUBLIC_API_BASE_URL in .env.'); } finally { setSearching(false); } @@ -97,9 +97,9 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) { try { await saveOriginStation(station); await rescheduleAllNotifications(); - Alert.alert('Station gespeichert', station.name); + Alert.alert('Station saved', station.name); } catch { - Alert.alert('Fehler', 'Station konnte nicht gespeichert werden.'); + Alert.alert('Error', 'Station could not be saved.'); } }; @@ -109,7 +109,7 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) { setLocPermission(status === 'granted' ? 'granted' : 'denied'); if (status !== 'granted') { - Alert.alert('Berechtigung erforderlich', 'Standortzugriff ist nötig für die automatische Stationssuche.'); + Alert.alert('Permission required', 'Location access is required for automatic station search.'); return; } @@ -149,7 +149,7 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) { try { const nearestStation = await api.findNearestStationByCoords(userLat, userLng); if (!nearestStation) { - Alert.alert('Keine Station gefunden', 'Kein ÖPNV-Halt in der Nähe gefunden.'); + Alert.alert('No station found', 'No public transport stop found nearby.'); return; } await selectStation({ @@ -164,14 +164,14 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) { if (!apiReachable) { Alert.alert( - 'API nicht erreichbar', - 'Der Server konnte nicht erreicht werden. Stelle sicher, dass EXPO_PUBLIC_API_BASE_URL in der .env-Datei auf die LAN-IP deines Entwicklungsrechners zeigt (z. B. http://192.168.1.x:3000) und nicht auf localhost.' + 'API not reachable', + 'The server could not be reached. Make sure EXPO_PUBLIC_API_BASE_URL in your .env file points to the LAN IP of your development machine (e.g. http://192.168.1.x:3000) and not localhost.' ); } else { - Alert.alert('Keine Station gefunden', 'Kein ÖPNV-Halt in der Nähe gefunden.'); + Alert.alert('No station found', 'No public transport stop found nearby.'); } } catch (_err) { - Alert.alert('Fehler', 'Standortermittlung fehlgeschlagen. Bitte überprüfe die Berechtigungen in den Systemeinstellungen.'); + Alert.alert('Error', 'Location detection failed. Please check the permissions in your system settings.'); } }; @@ -228,36 +228,36 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) { > {/* Appearance */} - Erscheinungsbild + Appearance - Dunkelmodus + Dark mode {/* Origin Station */} - Ursprungstation + Origin station {searching && } {searchError && ( {searchError} )} {origin && ( - Aktuell: {origin.name} + Current: {origin.name} )} {results.map((s) => ( @@ -267,72 +267,72 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) { ))} - 📍 Aktuelle Position verwenden + 📍 Use current location - Standort: {locPermission === 'granted' ? 'Erlaubt ✓' : locPermission === 'denied' ? 'Verweigert ✗' : 'Noch nicht angefragt'} + Location: {locPermission === 'granted' ? 'Granted ✓' : locPermission === 'denied' ? 'Denied ✗' : 'Not yet requested'} {/* Notification Settings */} - Benachrichtigungen + Notifications - Benachrichtigungen aktivieren + Enable notifications - Pufferzeit (Minuten) + Buffer time (minutes) - Du wirst {notifSettings.bufferMinutes} Minuten vor der geplanten Abfahrt erinnert. + You will be reminded {notifSettings.bufferMinutes} minutes before the scheduled departure. - {showAdvanced ? '↑ Weniger Optionen zeigen' : '↓ Mehr Optionen zeigen'} + {showAdvanced ? '↑ Show fewer options' : '↓ Show more options'} {showAdvanced && ( - Ankunfts-Puffer (Minuten) + Arrival buffer (minutes) - Wie viele Minuten vor der Event-Zeit du am Ziel ankommen möchtest + How many minutes before the event time you want to arrive at the destination - Zu Fuß-Option anzeigen + Show walking option - Fahrrad-Option anzeigen + Show bike option