Files
wtr/lib/i18n.tsx
zv 2c2515abba
All checks were successful
CI / Lint, test, typecheck and build (push) Successful in 11m21s
fix: show offline status in pwa
2026-07-04 20:53:13 +02:00

710 lines
41 KiB
TypeScript

"use client";
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type PropsWithChildren } from "react";
export type Language = "pl" | "en";
type TranslationParams = Record<string, string | number>;
const translations = {
pl: {
"nav.weather": "Pogoda",
"nav.warnings": "Ostrzeżenia",
"nav.hydro": "Hydro",
"nav.settings": "Ustawienia",
"nav.main": "Główna nawigacja",
"nav.mobile": "Mobilna nawigacja",
"language.label": "Wybierz język",
"language.polish": "Polski",
"language.english": "English",
"theme.change": "Zmień motyw",
"theme.light": "Włącz jasny motyw",
"theme.dark": "Włącz ciemny motyw",
"settings.section": "Preferencje",
"settings.title": "Ustawienia",
"settings.description": "Dostosuj wygląd, język, sekcje i powiadomienia.",
"settings.language.title": "Język",
"settings.language.description":
"Zmieniaj język interfejsu. Nazwy stacji i treści IMGW pozostają bez automatycznego tłumaczenia.",
"settings.theme.title": "Motyw",
"settings.theme.description": "Przełącz jasny lub ciemny wygląd aplikacji na tym urządzeniu.",
"settings.dashboardSections.title": "Strona główna",
"settings.dashboardSections.description": "Wybierz widoczność i kolejność sekcji pod główną kartą pogody.",
"settings.dashboardSections.moveUp": "Przenieś sekcję {section} wyżej",
"settings.dashboardSections.moveDown": "Przenieś sekcję {section} niżej",
"settings.dashboardSections.warnings.title": "Ostrzeżenia",
"settings.dashboardSections.warnings.description":
"Pokazuje aktywne i nadchodzące ostrzeżenia dla wybranej lokalizacji. Na stronie głównej sekcja pojawia się tylko wtedy, gdy jest aktywne ostrzeżenie meteo.",
"settings.dashboardSections.weatherBrief.title": "Brief dnia",
"settings.dashboardSections.weatherBrief.description":
"Pokazuje deterministyczne podsumowanie dnia i krótką prognozę na jutro.",
"settings.dashboardSections.forecast.title": "Prognoza 7-dniowa",
"settings.dashboardSections.forecast.description":
"Pokazuje najbliższe godziny, prognozę tygodniową i wykresy dnia.",
"settings.dashboardSections.favorites.title": "Ulubione",
"settings.dashboardSections.favorites.description": "Pokazuje zapisane lokalizacje i stacje na stronie głównej.",
"settings.dashboardSections.featuredStations.title": "Szybki wybór",
"settings.dashboardSections.featuredStations.description":
"Pokazuje listę popularnych lokalizacji na dole strony głównej.",
"settings.appSections.title": "Sekcje aplikacji",
"settings.appSections.description": "Wybierz, które dodatkowe widoki mają być widoczne w nawigacji.",
"settings.appSections.warnings.title": "Ostrzeżenia",
"settings.appSections.warnings.description": "Pokazuje widok oficjalnych ostrzeżeń IMGW w głównej nawigacji.",
"settings.appSections.hydro.title": "Hydro",
"settings.appSections.hydro.description": "Pokazuje widok monitoringu hydrologicznego IMGW w głównej nawigacji.",
"settings.units.temperature.title": "Jednostka temperatury",
"settings.units.temperature.description":
"Stosowana w prognozie, briefach, powiadomieniach i bieżących pomiarach na tym urządzeniu.",
"settings.units.temperature.label": "Wybierz jednostkę temperatury",
"settings.units.temperature.c": "°C",
"settings.units.temperature.f": "°F",
"settings.units.wind.title": "Jednostka wiatru",
"settings.units.wind.description":
"Stosowana w prognozie, briefach, powiadomieniach i bieżących pomiarach na tym urządzeniu.",
"settings.units.wind.label": "Wybierz jednostkę wiatru",
"settings.units.wind.ms": "m/s",
"settings.units.wind.kmh": "km/h",
"settings.units.wind.mph": "mph",
"settings.units.wind.kt": "kt",
"settings.units.wind.bft": "Bft",
"settings.units.precipitation.title": "Jednostka opadu",
"settings.units.precipitation.description":
"Stosowana w prognozie, briefach, powiadomieniach i bieżących pomiarach na tym urządzeniu.",
"settings.units.precipitation.label": "Wybierz jednostkę opadu",
"settings.units.precipitation.mm": "mm",
"settings.units.precipitation.cm": "cm",
"settings.units.precipitation.in": "in",
"settings.units.pressure.title": "Jednostka ciśnienia",
"settings.units.pressure.description": "Stosowana w bieżących pomiarach na tym urządzeniu.",
"settings.units.pressure.label": "Wybierz jednostkę ciśnienia",
"settings.units.pressure.hpa": "hPa",
"settings.units.pressure.kpa": "kPa",
"settings.units.pressure.inhg": "inHg",
"settings.units.pressure.mmhg": "mm Hg",
"settings.units.distance.title": "Jednostka dystansu",
"settings.units.distance.description": "Stosowana przy odległości do najbliższej stacji.",
"settings.units.distance.label": "Wybierz jednostkę dystansu",
"settings.units.distance.km": "km",
"settings.units.distance.mi": "mi",
"settings.notifications.title": "Powiadomienia o ostrzeżeniach meteo",
"settings.notifications.description":
"Przygotuj alerty dla nowych ostrzeżeń meteorologicznych IMGW, takich jak burze, upał, silny wiatr lub intensywny deszcz.",
"settings.notifications.regionTitle": "Obszar alertów",
"settings.notifications.regionDescription": "Aktualnie wybrany obszar dla przyszłych powiadomień: {province}.",
"settings.notifications.regionSelected": "Używaj lokalizacji wybranej w aplikacji",
"settings.notifications.regionManual": "Wybierz województwo ręcznie",
"settings.notifications.noProvince": "brak wybranego województwa",
"settings.notifications.globalRegion": "lokalizacja poza Polską",
"settings.notifications.globalAlertsUnavailable":
"Oficjalne ostrzeżenia IMGW są obecnie obsługiwane tylko dla Polski. Dla tej lokalizacji możesz używać briefów opartych o prognozę modelową Open-Meteo.",
"settings.notifications.deviceTitle": "To urządzenie",
"settings.notifications.enable": "Alerty IMGW",
"settings.notifications.enableDescription": "Otrzymuj alerty dla wybranego obszaru.",
"settings.notifications.enableGlobalDescription": "Otrzymuj briefy dla wybranej lokalizacji.",
"settings.notifications.morningBrief": "Brief poranny o 7:00",
"settings.notifications.morningBriefDescription": "Krótkie podsumowanie pogody na dziś.",
"settings.notifications.tomorrowBrief": "Brief na jutro o 18:00",
"settings.notifications.tomorrowBriefDescription": "Wieczorna prognoza na kolejny dzień.",
"settings.notifications.enableDevice": "Włącz na tym urządzeniu",
"settings.notifications.disable": "Wyłącz na tym urządzeniu",
"settings.notifications.enabledStatus": "Włączone",
"settings.notifications.disabledStatus": "Wyłączone",
"settings.notifications.saving": "Zapisuję…",
"settings.notifications.saveSuccess": "Powiadomienia są włączone dla tego urządzenia.",
"settings.notifications.disableSuccess": "Powiadomienia są wyłączone dla tego urządzenia.",
"settings.notifications.saveError": "Nie udało się zapisać subskrypcji powiadomień.",
"settings.notifications.sendTest": "Wyślij test",
"settings.notifications.sendingTest": "Wysyłam…",
"settings.notifications.testSuccess": "Wysłano powiadomienie testowe.",
"settings.notifications.testError": "Nie udało się wysłać powiadomienia testowego.",
"settings.notifications.requestPermission": "Sprawdź zgodę",
"settings.notifications.statusChecking": "Sprawdzam obsługę powiadomień w tej przeglądarce.",
"settings.notifications.statusUnconfigured": "Powiadomienia są niedostępne.",
"settings.notifications.statusUnsupported": "Ta przeglądarka nie udostępnia Web Push dla tej aplikacji.",
"settings.notifications.statusNeedsInstall":
"Na iPhonie dodaj aplikację do ekranu początkowego przez Udostępnij → Dodaj do ekranu początkowego, a potem otwórz ją z ikony.",
"settings.notifications.statusDenied": "Powiadomienia są zablokowane w ustawieniach systemu lub przeglądarki.",
"settings.notifications.statusGranted": "To urządzenie ma zgodę na powiadomienia.",
"settings.notifications.statusReady":
"Możesz poprosić system o zgodę na powiadomienia. Android i desktop działają bez instalowania PWA, jeśli przeglądarka obsługuje Web Push.",
"settings.notifications.implementationNote": "",
"pwa.install": "Zainstaluj",
"common.noData": "Brak danych",
"common.loading": "Ładowanie danych",
"common.retry": "Spróbuj ponownie",
"error.title": "Nie udało się pobrać danych",
"error.description": "Sprawdź połączenie i spróbuj ponownie.",
"dashboard.error": "Nie udało się pobrać listy stacji synoptycznych IMGW.",
"brief.label": "Brief dnia",
"brief.description": "Podsumowanie dla lokalizacji: {location}.",
"brief.tomorrowLabel": "Jutro",
"brief.pushHint":
"Krótkie wersje mogą być wysyłane jako powiadomienia o 7:00 i 18:00 po włączeniu ich w ustawieniach.",
"brief.error": "Nie udało się przygotować briefu dnia.",
"brief.emptyTitle": "Brak briefu",
"brief.emptyDescription": "Prognoza nie zawiera wystarczających danych dla najbliższej doby.",
"location.label": "Twoja lokalizacja",
"location.searchLabel": "Szukaj miejscowości",
"location.placeholder": "Wpisz miejscowość",
"location.clear": "Wyczyść wyszukiwanie",
"location.error": "Nie udało się wyszukać miejscowości. Spróbuj ponownie.",
"location.preparing": "Przygotowuję listę najbliższych stacji IMGW…",
"location.empty": "Nie znaleziono pasującej miejscowości.",
"location.nearest": "Najbliższa stacja IMGW",
"location.modelSource": "Źródło modelowe",
"location.currentSource":
"{location}: współrzędne miejscowości są używane dla lokalnej analizy IMGW Hybrid. Najbliższa stacja pomiarowa IMGW: {station} · około {distance}.",
"location.currentSourceGlobal":
"{location}: współrzędne są używane dla modelowych warunków bieżących i prognozy Open-Meteo.",
"location.heroHybridSource": "Analiza IMGW Hybrid dla lokalizacji: {location}",
"location.heroGlobalModelSource": "Modelowe warunki bieżące Open-Meteo dla lokalizacji: {location}",
"location.heroHybridLoading": "Pobieram lokalną analizę IMGW Hybrid. Tymczasowo pokazuję odczyt stacji: {station}.",
"location.heroHybridPartial":
"Lokalna analiza opadu IMGW Hybrid. Pozostałe parametry zastępczo ze stacji IMGW: {station} · około {distance}",
"location.heroNearestStation": "Najbliższa stacja pomiarowa IMGW: {station} · około {distance}",
"location.heroStationFallback": "Dane zastępcze ze stacji IMGW: {station}",
"location.heroStationFallbackWithDistance": "Dane zastępcze ze stacji IMGW: {station} · około {distance}",
"location.heroDistantFallback": "Stacja jest oddalona od lokalizacji. Lokalne warunki mogą się różnić.",
"location.attribution": "Wyszukiwanie miejscowości:",
"location.gpsUse": "Użyj mojej lokalizacji",
"location.gpsLocating": "Ustalam lokalizację…",
"location.gpsPromptTitle": "Pokazać pogodę dla Twojej lokalizacji?",
"location.gpsPromptDescription":
"Po Twojej zgodzie wtr. użyje GPS, aby wybrać miejscowość i prognozę. W Polsce dobierze też najbliższą stację IMGW. Pozycja zostanie zaokrąglona do około 100 metrów.",
"location.gpsAllow": "Użyj GPS",
"location.gpsNotNow": "Nie teraz",
"location.gpsSecureContext":
"GPS wymaga HTTPS. Na iPhonie lokalny adres HTTP z adresem IP nie może wyświetlić systemowego pytania o położenie. Użyj wdrożonej wersji HTTPS.",
"location.gpsUnavailable": "Ta przeglądarka nie udostępnia lokalizacji GPS.",
"location.gpsDenied":
"Dostęp do lokalizacji został odrzucony. Możesz zmienić uprawnienia witryny w ustawieniach przeglądarki.",
"location.gpsTimeout": "Nie udało się ustalić lokalizacji w wymaganym czasie. Spróbuj ponownie.",
"location.gpsPositionUnavailable":
"Nie udało się ustalić lokalizacji GPS. Sprawdź ustawienia urządzenia i spróbuj ponownie.",
"location.gpsStationsPending": "Lista stacji IMGW nie jest jeszcze gotowa. Spróbuj ponownie za chwilę.",
"location.gpsFallbackName": "Bieżąca lokalizacja",
"location.gpsSelected": "Wybrano lokalizację: {location}.",
"location.gpsAttribution": "Nazwy miejsc dla GPS:",
"featured.label": "Szybki wybór",
"featured.title": "Popularne lokalizacje",
"favorites.title": "Ulubione lokalizacje",
"favorites.addStation": "Dodaj {name} do ulubionych",
"favorites.removeStation": "Usuń {name} z ulubionych",
"favorites.add": "Dodaj do ulubionych",
"favorites.remove": "Usuń z ulubionych",
"weather.humidity": "Wilgotność",
"weather.wind": "Wiatr",
"weather.rainfall": "Suma opadu",
"weather.rainfall10m": "Opad 10 min",
"weather.currentPrecipitation": "Opad",
"weather.pressure": "Ciśnienie",
"weather.feelsLike": "Odczuwalna",
"weather.measurement": "pomiar",
"weather.modelUpdate": "aktualizacja modelu",
"weather.windDirection": "Kierunek wiatru",
"weather.calm": "Pogodnie",
"weather.humid": "Wilgotno",
"weather.strongWind": "Silny wiatr",
"weather.currentRain": "Opady deszczu",
"weather.currentSnow": "Opady śniegu",
"weather.thunderstorm": "Burza",
"weather.airTemperature": "Temperatura",
"weather.windSpeed": "Prędkość wiatru",
"weather.rainfallTotal": "Suma opadu",
"weather.feelsLikeDetail": "Wartość obliczana, gdy warunki na to pozwalają",
"weather.humidityDetail": "Wilgotność względna powietrza",
"weather.pressureDetail": "Ciśnienie atmosferyczne",
"weather.windSpeedDetail": "Bieżący odczyt IMGW",
"weather.windDirectionDetail": "Kierunek napływu wiatru",
"weather.rainfallDetail": "Akumulowana suma opadu z pomiaru IMGW. Nie oznacza, że pada w tej chwili.",
"weather.temperatureDetail": "Temperatura powietrza",
"forecast.label": "Prognoza modelowa",
"forecast.title": "Najbliższe godziny i dni",
"forecast.description": "Prognoza dla lokalizacji: {location}.",
"forecast.hourly": "Najbliższe 24 godziny",
"forecast.daily": "Prognoza 7-dniowa",
"forecast.today": "Dzisiaj",
"forecast.openDayDetails": "Otwórz szczegółową prognozę dla: {day}",
"forecast.dayDetails": "Prognoza szczegółowa",
"forecast.closeDetails": "Zamknij prognozę szczegółową",
"forecast.hourlyForDay": "Przebieg godzinowy",
"forecast.temperatureChart": "Temperatura w ciągu dnia",
"forecast.temperatureChartDescription": "Temperatura powietrza i temperatura odczuwalna według modelu.",
"forecast.rainfallChart": "Opad w ciągu dnia",
"forecast.rainfallChartDescription": "Przewidywana suma opadu oraz prawdopodobieństwo opadu.",
"forecast.temperature": "Temperatura",
"forecast.apparentTemperature": "Odczuwalna",
"forecast.precipitation": "Opad",
"forecast.precipitationProbability": "Prawdopodobieństwo",
"forecast.sunrise": "Wschód słońca",
"forecast.sunset": "Zachód słońca",
"forecast.maxWind": "Maks. wiatr",
"forecast.nextHoursOverview": "Najbliższe 24 godziny w skrócie",
"forecast.temperatureRange": "Zakres temperatur",
"forecast.rainfallTotal": "Suma opadu",
"forecast.maxProbability": "Maks. szansa opadu",
"forecast.pastHour": "Miniona godzina",
"forecast.source": "Źródło prognozy:",
"forecast.sourceCombinedDescription": "IMGW ALARO i Open-Meteo.",
"forecast.sourceFallbackDescription": "Open-Meteo.",
"forecast.error": "Nie udało się pobrać prognozy modelowej.",
"forecast.emptyTitle": "Brak prognozy",
"forecast.emptyDescription": "Źródła prognozy nie zwróciły teraz kompletnej prognozy dla tej lokalizacji.",
"forecast.condition.clear": "Bezchmurnie",
"forecast.condition.partlyCloudy": "Częściowe zachmurzenie",
"forecast.condition.cloudy": "Pochmurno",
"forecast.condition.fog": "Mgła",
"forecast.condition.drizzle": "Mżawka",
"forecast.condition.rain": "Opady deszczu",
"forecast.condition.snow": "Opady śniegu",
"forecast.condition.thunderstorm": "Burza",
"forecast.condition.unknown": "Brak opisu",
"stations.emptyTitle": "Brak pasujących stacji",
"station.all": "Wszystkie stacje",
"station.label": "Stacja {name}",
"station.parameters": "Aktualne parametry",
"station.parametersDescription":
"Najnowszy pomiar udostępniony przez IMGW. Brakujące wartości są oznaczone bez uzupełniania ich danymi szacunkowymi.",
"station.error": "Nie udało się pobrać danych wybranej stacji IMGW.",
"station.quality": "Jakość danych",
"station.lastMeasurementImgw": "Ostatni pomiar IMGW",
"station.qualityDescription": "Czas poniżej pochodzi bezpośrednio z najnowszego odczytu udostępnionego przez IMGW.",
"station.lastMeasurement": "Ostatni pomiar",
"station.source": "Źródło",
"station.publicApi": "Publiczne API IMGW",
"snapshot.label": "Snapshot pomiarowy",
"snapshot.title": "Aktualne proporcje parametrów",
"snapshot.description":
"Wizualizacja bieżącego odczytu. API synoptyczne IMGW nie udostępnia historii ani prognozy godzinowej.",
"warnings.section": "Komunikaty IMGW",
"warnings.title": "Ostrzeżenia",
"warnings.description": "Aktualne ostrzeżenia meteorologiczne i hydrologiczne IMGW.",
"warnings.myProvince": "Mój obszar",
"warnings.myProvinceDescription":
"Najpierw pokazujemy komunikaty IMGW dotyczące obszaru {province}, zgodnie z lokalizacją wybraną w pogodzie.",
"warnings.myProvinceEmptyTitle": "Brak ostrzeżeń dla Twojego obszaru",
"warnings.myProvinceEmptyDescription":
"IMGW nie publikuje obecnie aktywnych ostrzeżeń dotyczących obszaru {province}.",
"warnings.otherRegions": "Pozostałe regiony",
"warnings.otherRegionsDescription": "Aktywne komunikaty IMGW dla pozostałych obszarów Polski.",
"warnings.error": "Nie udało się pobrać ostrzeżeń meteorologicznych ani hydrologicznych.",
"warnings.emptyTitle": "Brak aktywnych ostrzeżeń",
"warnings.emptyDescription": "IMGW nie publikuje obecnie ostrzeżeń meteorologicznych ani hydrologicznych.",
"warnings.globalUnavailableTitle": "Oficjalne ostrzeżenia tylko dla Polski",
"warnings.globalUnavailableDescription": "Oficjalne ostrzeżenia są obecnie dostępne tylko dla Polski.",
"warnings.drought": "Susza hydrologiczna",
"warnings.levelUnknown": "Poziom nieokreślony",
"warnings.level": "Stopień {level}",
"warnings.hydro": "Hydrologiczne",
"warnings.meteo": "Meteorologiczne",
"warnings.untilCancelled": "do odwołania",
"warnings.areaUnknown": "Obszar nieokreślony",
"warnings.moreAreas": "i {count} więcej",
"warnings.probability": "Prawdopodobieństwo: {value}%",
"warnings.genericHydro": "Ostrzeżenie hydrologiczne",
"warnings.genericMeteo": "Ostrzeżenie meteorologiczne",
"warnings.dashboard.title": "Ostrzeżenia meteo dla Twojego regionu",
"warnings.dashboard.active": "Aktywne ostrzeżenie",
"warnings.dashboard.upcoming": "Nadchodzące ostrzeżenie",
"warnings.dashboard.validUntil": "Do {date}",
"warnings.dashboard.validFrom": "Od {date}",
"warnings.dashboard.more": "+{count} kolejne ostrzeżenia",
"warnings.dashboard.viewAll": "Zobacz wszystkie",
"hydro.section": "Monitoring wód IMGW",
"hydro.title": "Hydro",
"hydro.description": "Najnowsze pomiary poziomu, temperatury i przepływu wody.",
"hydro.error": "Nie udało się pobrać stacji hydrologicznych IMGW.",
"hydro.searchLabel": "Szukaj stacji hydrologicznej",
"hydro.searchPlaceholder": "Szukaj stacji, rzeki lub województwa…",
"hydro.results": "Znaleziono {total} stacji. Wyświetlono {visible}.",
"hydro.emptyDescription": "Zmień wyszukiwaną nazwę stacji, rzeki lub województwa.",
"hydro.more": "Pokaż więcej stacji",
"hydro.riverUnavailable": "Rzeka: brak danych",
"hydro.level": "Poziom",
"hydro.water": "Woda",
"hydro.flow": "Przepływ",
"hydro.levelMeasurement": "Pomiar poziomu: {date}",
"offline.title": "Brak połączenia",
"offline.description":
"wtr. nie może teraz pobrać aktualnych danych IMGW. Ostatnio odwiedzone widoki mogą być dostępne z pamięci urządzenia.",
"offline.back": "Wróć do aplikacji",
"offline.banner": "Brak połączenia · dane mogą być nieaktualne",
},
en: {
"nav.weather": "Weather",
"nav.warnings": "Warnings",
"nav.hydro": "Hydro",
"nav.settings": "Settings",
"nav.main": "Main navigation",
"nav.mobile": "Mobile navigation",
"language.label": "Select language",
"language.polish": "Polski",
"language.english": "English",
"theme.change": "Change theme",
"theme.light": "Enable light theme",
"theme.dark": "Enable dark theme",
"settings.section": "Preferences",
"settings.title": "Settings",
"settings.description": "Customize appearance, language, sections and notifications.",
"settings.language.title": "Language",
"settings.language.description":
"Change the interface language. Station names and IMGW content are not translated automatically.",
"settings.theme.title": "Theme",
"settings.theme.description": "Switch the light or dark appearance on this device.",
"settings.dashboardSections.title": "Home screen",
"settings.dashboardSections.description": "Choose visibility and order for sections below the main weather card.",
"settings.dashboardSections.moveUp": "Move {section} up",
"settings.dashboardSections.moveDown": "Move {section} down",
"settings.dashboardSections.warnings.title": "Warnings",
"settings.dashboardSections.warnings.description":
"Shows active and upcoming warnings for the selected location. On the home screen, this section appears only when an active weather warning exists.",
"settings.dashboardSections.weatherBrief.title": "Daily brief",
"settings.dashboardSections.weatherBrief.description":
"Shows the deterministic daily summary and a short forecast for tomorrow.",
"settings.dashboardSections.forecast.title": "7-day forecast",
"settings.dashboardSections.forecast.description": "Shows the next hours, weekly forecast and daily charts.",
"settings.dashboardSections.favorites.title": "Favorites",
"settings.dashboardSections.favorites.description": "Shows saved places and stations on the home screen.",
"settings.dashboardSections.featuredStations.title": "Quick select",
"settings.dashboardSections.featuredStations.description":
"Shows the popular locations list at the bottom of the home screen.",
"settings.appSections.title": "App sections",
"settings.appSections.description": "Choose which additional views are visible in navigation.",
"settings.appSections.warnings.title": "Warnings",
"settings.appSections.warnings.description": "Shows the official IMGW warnings view in main navigation.",
"settings.appSections.hydro.title": "Hydro",
"settings.appSections.hydro.description": "Shows the IMGW hydrological monitoring view in main navigation.",
"settings.units.temperature.title": "Temperature unit",
"settings.units.temperature.description":
"Used in forecasts, briefs, notifications and current readings on this device.",
"settings.units.temperature.label": "Choose temperature unit",
"settings.units.temperature.c": "°C",
"settings.units.temperature.f": "°F",
"settings.units.wind.title": "Wind unit",
"settings.units.wind.description": "Used in forecasts, briefs, notifications and current readings on this device.",
"settings.units.wind.label": "Choose wind unit",
"settings.units.wind.ms": "m/s",
"settings.units.wind.kmh": "km/h",
"settings.units.wind.mph": "mph",
"settings.units.wind.kt": "kt",
"settings.units.wind.bft": "Bft",
"settings.units.precipitation.title": "Precipitation unit",
"settings.units.precipitation.description":
"Used in forecasts, briefs, notifications and current readings on this device.",
"settings.units.precipitation.label": "Choose precipitation unit",
"settings.units.precipitation.mm": "mm",
"settings.units.precipitation.cm": "cm",
"settings.units.precipitation.in": "in",
"settings.units.pressure.title": "Pressure unit",
"settings.units.pressure.description": "Used in current readings on this device.",
"settings.units.pressure.label": "Choose pressure unit",
"settings.units.pressure.hpa": "hPa",
"settings.units.pressure.kpa": "kPa",
"settings.units.pressure.inhg": "inHg",
"settings.units.pressure.mmhg": "mm Hg",
"settings.units.distance.title": "Distance unit",
"settings.units.distance.description": "Used for the distance to the nearest station.",
"settings.units.distance.label": "Choose distance unit",
"settings.units.distance.km": "km",
"settings.units.distance.mi": "mi",
"settings.notifications.title": "Weather warning notifications",
"settings.notifications.description":
"Prepare alerts for new IMGW meteorological warnings, such as thunderstorms, heat, strong wind or heavy rain.",
"settings.notifications.regionTitle": "Alert area",
"settings.notifications.regionDescription": "Current area for future notifications: {province}.",
"settings.notifications.regionSelected": "Use the location selected in the app",
"settings.notifications.regionManual": "Choose province manually",
"settings.notifications.noProvince": "no province selected",
"settings.notifications.globalRegion": "location outside Poland",
"settings.notifications.globalAlertsUnavailable":
"Official IMGW warnings are currently supported only for Poland. For this location you can use briefs based on the Open-Meteo model forecast.",
"settings.notifications.deviceTitle": "This device",
"settings.notifications.enable": "IMGW alerts",
"settings.notifications.enableDescription": "Receive alerts for the selected area.",
"settings.notifications.enableGlobalDescription": "Receive briefs for the selected location.",
"settings.notifications.morningBrief": "Morning brief at 7:00",
"settings.notifications.morningBriefDescription": "A short weather summary for today.",
"settings.notifications.tomorrowBrief": "Tomorrow brief at 18:00",
"settings.notifications.tomorrowBriefDescription": "An evening forecast for the next day.",
"settings.notifications.enableDevice": "Enable on this device",
"settings.notifications.disable": "Disable on this device",
"settings.notifications.enabledStatus": "Enabled",
"settings.notifications.disabledStatus": "Disabled",
"settings.notifications.saving": "Saving…",
"settings.notifications.saveSuccess": "Notifications are enabled for this device.",
"settings.notifications.disableSuccess": "Notifications are disabled for this device.",
"settings.notifications.saveError": "Unable to save the notification subscription.",
"settings.notifications.sendTest": "Send test",
"settings.notifications.sendingTest": "Sending…",
"settings.notifications.testSuccess": "Test notification sent.",
"settings.notifications.testError": "Unable to send the test notification.",
"settings.notifications.requestPermission": "Check permission",
"settings.notifications.statusChecking": "Checking notification support in this browser.",
"settings.notifications.statusUnconfigured": "Notifications are unavailable.",
"settings.notifications.statusUnsupported": "This browser does not provide Web Push for this app.",
"settings.notifications.statusNeedsInstall":
"On iPhone, add the app to the Home Screen using Share → Add to Home Screen, then open it from the icon.",
"settings.notifications.statusDenied": "Notifications are blocked in system or browser settings.",
"settings.notifications.statusGranted": "This device has notification permission.",
"settings.notifications.statusReady":
"You can ask the system for notification permission. Android and desktop work without installing the PWA when the browser supports Web Push.",
"settings.notifications.implementationNote": "",
"pwa.install": "Install",
"common.noData": "No data",
"common.loading": "Loading data",
"common.retry": "Try again",
"error.title": "Unable to load data",
"error.description": "Check your connection and try again.",
"dashboard.error": "Unable to load the IMGW synoptic station list.",
"brief.label": "Daily brief",
"brief.description": "Summary for: {location}.",
"brief.tomorrowLabel": "Tomorrow",
"brief.pushHint": "Short versions can be sent as 7:00 and 18:00 notifications after enabling them in settings.",
"brief.error": "Unable to prepare the daily brief.",
"brief.emptyTitle": "Brief unavailable",
"brief.emptyDescription": "The forecast does not contain enough data for the next day.",
"location.label": "Your location",
"location.searchLabel": "Search places",
"location.placeholder": "Enter a place",
"location.clear": "Clear search",
"location.error": "Unable to search for places. Try again.",
"location.preparing": "Preparing the nearest IMGW stations…",
"location.empty": "No matching place was found.",
"location.nearest": "Nearest IMGW station",
"location.modelSource": "Model source",
"location.currentSource":
"{location}: the place coordinates are used for local IMGW Hybrid analysis. Nearest IMGW measurement station: {station} · approximately {distance} away.",
"location.heroHybridSource": "IMGW Hybrid analysis for: {location}",
"location.currentSourceGlobal":
"{location}: coordinates are used for Open-Meteo model current conditions and forecast.",
"location.heroGlobalModelSource": "Open-Meteo model current conditions for: {location}",
"location.heroHybridLoading":
"Loading local IMGW Hybrid analysis. Temporarily showing the station reading: {station}.",
"location.heroHybridPartial":
"Local IMGW Hybrid rainfall analysis. Other parameters use fallback data from IMGW station: {station} · approximately {distance} away",
"location.heroNearestStation": "Nearest IMGW measurement station: {station} · approximately {distance} away",
"location.heroStationFallback": "Fallback data from IMGW station: {station}",
"location.heroStationFallbackWithDistance":
"Fallback data from IMGW station: {station} · approximately {distance} away",
"location.heroDistantFallback": "The station is far from this place. Local conditions may differ.",
"location.attribution": "Place search:",
"location.gpsUse": "Use my location",
"location.gpsLocating": "Finding your location…",
"location.gpsPromptTitle": "Show weather for your location?",
"location.gpsPromptDescription":
"With your permission, wtr. will use GPS to select your place and forecast. In Poland it will also match the nearest IMGW station. Your position will be rounded to approximately 100 metres.",
"location.gpsAllow": "Use GPS",
"location.gpsNotNow": "Not now",
"location.gpsSecureContext":
"GPS requires HTTPS. On iPhone, a local HTTP address using an IP cannot display the system location prompt. Use the deployed HTTPS version.",
"location.gpsUnavailable": "This browser does not provide GPS location access.",
"location.gpsDenied": "Location access was denied. You can change the website permission in your browser settings.",
"location.gpsTimeout": "Your location could not be determined in time. Try again.",
"location.gpsPositionUnavailable":
"Your GPS location could not be determined. Check your device settings and try again.",
"location.gpsStationsPending": "The IMGW station list is not ready yet. Try again in a moment.",
"location.gpsFallbackName": "Current location",
"location.gpsSelected": "Selected location: {location}.",
"location.gpsAttribution": "GPS place names:",
"featured.label": "Quick select",
"featured.title": "Popular locations",
"favorites.title": "Favourite locations",
"favorites.addStation": "Add {name} to favourites",
"favorites.removeStation": "Remove {name} from favourites",
"favorites.add": "Add to favourites",
"favorites.remove": "Remove from favourites",
"weather.humidity": "Humidity",
"weather.wind": "Wind",
"weather.rainfall": "Rainfall total",
"weather.rainfall10m": "Rainfall 10 min",
"weather.currentPrecipitation": "Precipitation",
"weather.pressure": "Pressure",
"weather.feelsLike": "Feels like",
"weather.measurement": "measurement",
"weather.modelUpdate": "model update",
"weather.windDirection": "Wind direction",
"weather.calm": "Fair",
"weather.humid": "Humid",
"weather.strongWind": "Strong wind",
"weather.currentRain": "Rain",
"weather.currentSnow": "Snow",
"weather.thunderstorm": "Thunderstorm",
"weather.airTemperature": "Temperature",
"weather.windSpeed": "Wind speed",
"weather.rainfallTotal": "Rainfall total",
"weather.feelsLikeDetail": "Calculated when the available conditions allow it",
"weather.humidityDetail": "Relative air humidity",
"weather.pressureDetail": "Atmospheric pressure",
"weather.windSpeedDetail": "Current IMGW reading",
"weather.windDirectionDetail": "Direction the wind is coming from",
"weather.rainfallDetail":
"Accumulated rainfall total from the IMGW reading. It does not mean that it is raining right now.",
"weather.temperatureDetail": "Air temperature",
"forecast.label": "Model forecast",
"forecast.title": "Upcoming hours and days",
"forecast.description": "Forecast for: {location}.",
"forecast.hourly": "Next 24 hours",
"forecast.daily": "7-day forecast",
"forecast.today": "Today",
"forecast.openDayDetails": "Open detailed forecast for: {day}",
"forecast.dayDetails": "Detailed forecast",
"forecast.closeDetails": "Close detailed forecast",
"forecast.hourlyForDay": "Hourly conditions",
"forecast.temperatureChart": "Temperature throughout the day",
"forecast.temperatureChartDescription": "Air temperature and apparent temperature according to the model.",
"forecast.rainfallChart": "Rainfall throughout the day",
"forecast.rainfallChartDescription": "Forecast rainfall total and precipitation probability.",
"forecast.temperature": "Temperature",
"forecast.apparentTemperature": "Feels like",
"forecast.precipitation": "Rainfall",
"forecast.precipitationProbability": "Probability",
"forecast.sunrise": "Sunrise",
"forecast.sunset": "Sunset",
"forecast.maxWind": "Max. wind",
"forecast.nextHoursOverview": "Next 24 hours at a glance",
"forecast.temperatureRange": "Temperature range",
"forecast.rainfallTotal": "Rainfall total",
"forecast.maxProbability": "Max. rain chance",
"forecast.pastHour": "Past hour",
"forecast.source": "Forecast source:",
"forecast.sourceCombinedDescription": "IMGW ALARO and Open-Meteo.",
"forecast.sourceFallbackDescription": "Open-Meteo.",
"forecast.error": "Unable to load the model forecast.",
"forecast.emptyTitle": "Forecast unavailable",
"forecast.emptyDescription": "The forecast sources did not return a complete forecast for this location.",
"forecast.condition.clear": "Clear sky",
"forecast.condition.partlyCloudy": "Partly cloudy",
"forecast.condition.cloudy": "Cloudy",
"forecast.condition.fog": "Fog",
"forecast.condition.drizzle": "Drizzle",
"forecast.condition.rain": "Rain",
"forecast.condition.snow": "Snow",
"forecast.condition.thunderstorm": "Thunderstorm",
"forecast.condition.unknown": "Description unavailable",
"stations.emptyTitle": "No matching stations",
"station.all": "All stations",
"station.label": "Station {name}",
"station.parameters": "Current parameters",
"station.parametersDescription":
"The latest measurement published by IMGW. Missing values are clearly marked and never replaced with estimates.",
"station.error": "Unable to load data for the selected IMGW station.",
"station.quality": "Data details",
"station.lastMeasurementImgw": "Latest IMGW measurement",
"station.qualityDescription": "The time below comes directly from the latest reading published by IMGW.",
"station.lastMeasurement": "Latest measurement",
"station.source": "Source",
"station.publicApi": "Public IMGW API",
"snapshot.label": "Measurement snapshot",
"snapshot.title": "Current parameter proportions",
"snapshot.description":
"Visualisation of the current reading. The IMGW synoptic API does not provide historical data or an hourly forecast.",
"warnings.section": "IMGW notices",
"warnings.title": "Warnings",
"warnings.description": "Current IMGW meteorological and hydrological warnings.",
"warnings.myProvince": "My area",
"warnings.myProvinceDescription":
"IMGW notices for {province} are shown first, based on the location selected in weather.",
"warnings.myProvinceEmptyTitle": "No warnings for your area",
"warnings.myProvinceEmptyDescription": "IMGW is not currently publishing active warnings for {province}.",
"warnings.otherRegions": "Other regions",
"warnings.otherRegionsDescription": "Active IMGW notices for the remaining areas of Poland.",
"warnings.error": "Unable to load meteorological or hydrological warnings.",
"warnings.emptyTitle": "No active warnings",
"warnings.emptyDescription": "IMGW is not currently publishing any meteorological or hydrological warnings.",
"warnings.globalUnavailableTitle": "Official warnings only for Poland",
"warnings.globalUnavailableDescription": "Official warnings are currently available only for Poland.",
"warnings.drought": "Hydrological drought",
"warnings.levelUnknown": "Level not specified",
"warnings.level": "Level {level}",
"warnings.hydro": "Hydrological",
"warnings.meteo": "Meteorological",
"warnings.untilCancelled": "until cancelled",
"warnings.areaUnknown": "Area not specified",
"warnings.moreAreas": "and {count} more",
"warnings.probability": "Probability: {value}%",
"warnings.genericHydro": "Hydrological warning",
"warnings.genericMeteo": "Meteorological warning",
"warnings.dashboard.title": "Weather warnings for your region",
"warnings.dashboard.active": "Active warning",
"warnings.dashboard.upcoming": "Upcoming warning",
"warnings.dashboard.validUntil": "Until {date}",
"warnings.dashboard.validFrom": "From {date}",
"warnings.dashboard.more": "+{count} more warnings",
"warnings.dashboard.viewAll": "View all",
"hydro.section": "IMGW water monitoring",
"hydro.title": "Hydro",
"hydro.description": "Latest water level, temperature and flow readings.",
"hydro.error": "Unable to load IMGW hydrological stations.",
"hydro.searchLabel": "Search hydrological stations",
"hydro.searchPlaceholder": "Search by station, river or province…",
"hydro.results": "Found {total} stations. Showing {visible}.",
"hydro.emptyDescription": "Adjust the station, river or province name in your search.",
"hydro.more": "Show more stations",
"hydro.riverUnavailable": "River: no data",
"hydro.level": "Level",
"hydro.water": "Water",
"hydro.flow": "Flow",
"hydro.levelMeasurement": "Level measurement: {date}",
"offline.title": "No connection",
"offline.description":
"wtr. cannot fetch current IMGW data right now. Recently visited views may still be available from your device cache.",
"offline.back": "Back to the app",
"offline.banner": "Offline · data may be out of date",
},
} as const;
export type TranslationKey = keyof typeof translations.pl;
function interpolate(value: string, params?: TranslationParams) {
if (!params) return value;
return value.replace(/\{(\w+)\}/g, (_, key: string) => String(params[key] ?? `{${key}}`));
}
export function translate(language: Language, key: TranslationKey, params?: TranslationParams) {
return interpolate(translations[language][key] ?? translations.en[key], params);
}
interface I18nContextValue {
language: Language;
locale: string;
setLanguage: (language: Language) => void;
t: (key: TranslationKey, params?: TranslationParams) => string;
}
const I18nContext = createContext<I18nContextValue | null>(null);
export function I18nProvider({ children }: PropsWithChildren) {
const [language, setLanguageState] = useState<Language>("pl");
useEffect(() => {
const stored = window.localStorage.getItem("wtr:language");
const nextLanguage: Language = stored === "en" ? "en" : "pl";
const animationFrame = window.requestAnimationFrame(() => setLanguageState(nextLanguage));
document.documentElement.lang = nextLanguage;
return () => window.cancelAnimationFrame(animationFrame);
}, []);
const setLanguage = useCallback((nextLanguage: Language) => {
window.localStorage.setItem("wtr:language", nextLanguage);
document.documentElement.lang = nextLanguage;
setLanguageState(nextLanguage);
}, []);
const value = useMemo<I18nContextValue>(
() => ({
language,
locale: language === "pl" ? "pl-PL" : "en-GB",
setLanguage,
t: (key, params) => translate(language, key, params),
}),
[language, setLanguage],
);
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
}
export function useI18n() {
const context = useContext(I18nContext);
if (!context) throw new Error("useI18n must be used within I18nProvider.");
return context;
}