421 lines
24 KiB
TypeScript
421 lines
24 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.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",
|
|
"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.",
|
|
"location.label": "Twoja lokalizacja",
|
|
"location.searchLabel": "Szukaj miejscowości w Polsce",
|
|
"location.placeholder": "Wpisz miejscowość, np. Piaseczno…",
|
|
"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 w Polsce.",
|
|
"location.nearest": "Najbliższa stacja IMGW",
|
|
"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} km.",
|
|
"location.heroHybridSource": "Analiza IMGW Hybrid 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} km",
|
|
"location.heroNearestStation": "Najbliższa stacja pomiarowa IMGW: {station} · około {distance} km",
|
|
"location.heroStationFallback": "Dane zastępcze ze stacji IMGW: {station}",
|
|
"location.heroStationFallbackWithDistance": "Dane zastępcze ze stacji IMGW: {station} · około {distance} km",
|
|
"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ść, najbliższą stację IMGW i prognozę. 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": "Wybrane stacje IMGW",
|
|
"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.pressure": "Ciśnienie",
|
|
"weather.feelsLike": "Odczuwalna",
|
|
"weather.measurement": "pomiar",
|
|
"weather.windDirection": "Kierunek wiatru",
|
|
"weather.calm": "Spokojne warunki",
|
|
"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 {location}. Bieżące warunki powyżej pochodzą z IMGW, a poniższe wartości są prognozą modelową preferującą IMGW.",
|
|
"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 dostarcza dostępne godziny prognozy, a Open-Meteo uzupełnia prawdopodobieństwo opadu i dalszy horyzont do 7 dni.",
|
|
"forecast.sourceFallbackDescription": "Prognoza jest obecnie wyświetlana zastępczo z 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 publikowane przez IMGW. Szczegóły obszaru i czasu obowiązywania pochodzą bezpośrednio z API.",
|
|
"warnings.myProvince": "Moje województwo",
|
|
"warnings.myProvinceDescription": "Najpierw pokazujemy komunikaty IMGW dotyczące województwa {province}, zgodnie z lokalizacją wybraną w pogodzie.",
|
|
"warnings.myProvinceEmptyTitle": "Brak ostrzeżeń dla Twojego województwa",
|
|
"warnings.myProvinceEmptyDescription": "IMGW nie publikuje obecnie aktywnych ostrzeżeń dotyczących województwa {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.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",
|
|
"hydro.section": "Monitoring wód IMGW",
|
|
"hydro.title": "Hydro",
|
|
"hydro.description": "Najnowsze dostępne pomiary poziomu wody, temperatury i przepływu. Każdy parametr może mieć własny czas aktualizacji.",
|
|
"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",
|
|
},
|
|
en: {
|
|
"nav.weather": "Weather",
|
|
"nav.warnings": "Warnings",
|
|
"nav.hydro": "Hydro",
|
|
"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",
|
|
"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.",
|
|
"location.label": "Your location",
|
|
"location.searchLabel": "Search places in Poland",
|
|
"location.placeholder": "Enter a place, e.g. Piaseczno…",
|
|
"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 in Poland.",
|
|
"location.nearest": "Nearest IMGW station",
|
|
"location.currentSource": "{location}: the place coordinates are used for local IMGW Hybrid analysis. Nearest IMGW measurement station: {station} · approximately {distance} km away.",
|
|
"location.heroHybridSource": "IMGW Hybrid analysis 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} km away",
|
|
"location.heroNearestStation": "Nearest IMGW measurement station: {station} · approximately {distance} km away",
|
|
"location.heroStationFallback": "Fallback data from IMGW station: {station}",
|
|
"location.heroStationFallbackWithDistance": "Fallback data from IMGW station: {station} · approximately {distance} km 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, the nearest IMGW station and the forecast. 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": "Selected IMGW stations",
|
|
"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.pressure": "Pressure",
|
|
"weather.feelsLike": "Feels like",
|
|
"weather.measurement": "measurement",
|
|
"weather.windDirection": "Wind direction",
|
|
"weather.calm": "Calm conditions",
|
|
"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}. The current conditions above come from IMGW. The values below are a model forecast preferring IMGW.",
|
|
"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 provides the available forecast hours. Open-Meteo supplements precipitation probability and extends the horizon to 7 days.",
|
|
"forecast.sourceFallbackDescription": "The forecast is currently displayed using Open-Meteo fallback data.",
|
|
"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 meteorological and hydrological warnings published by IMGW. Area and validity details come directly from the API.",
|
|
"warnings.myProvince": "My province",
|
|
"warnings.myProvinceDescription": "IMGW notices for the {province} province are shown first, based on the location selected in weather.",
|
|
"warnings.myProvinceEmptyTitle": "No warnings for your province",
|
|
"warnings.myProvinceEmptyDescription": "IMGW is not currently publishing active warnings for the {province} 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.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",
|
|
"hydro.section": "IMGW water monitoring",
|
|
"hydro.title": "Hydro",
|
|
"hydro.description": "Latest available water level, temperature and flow readings. Each parameter may have a different update time.",
|
|
"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",
|
|
},
|
|
} 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;
|
|
}
|