"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": "Zarządzaj językiem, wyglądem i przygotowaniem powiadomień o ostrzeżeniach meteorologicznych IMGW.",
    "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, które dodatkowe sekcje mają być widoczne na dashboardzie.",
    "settings.dashboardSections.weatherBrief.title": "Brief dnia",
    "settings.dashboardSections.weatherBrief.description": "Pokazuje deterministyczne podsumowanie dnia i krótką prognozę na jutro.",
    "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.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": "Subskrypcja zostanie zapisana dla wybranego województwa i użyta przez serwerowy sprawdzacz ostrzeżeń.",
    "settings.notifications.enableGlobalDescription": "Subskrypcja zostanie zapisana dla wybranej lokalizacji i użyta do briefów opartych o prognozę modelową.",
    "settings.notifications.morningBrief": "Brief poranny o 7:00",
    "settings.notifications.morningBriefDescription": "Używa wybranej lokalizacji i prognozy modelowej, aby wysłać krótkie podsumowanie dnia na to urządzenie.",
    "settings.notifications.tomorrowBrief": "Brief na jutro o 18:00",
    "settings.notifications.tomorrowBriefDescription": "Wysyła wieczorem krótką prognozę na kolejny dzień, w tym sygnały burz i opadów z modelu.",
    "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": "Serwer nie ma jeszcze skonfigurowanych kluczy Web Push VAPID.",
    "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": "Serwerowy sprawdzacz wysyła nowe ostrzeżenia meteo IMGW oraz briefy przez Web Push. Subskrypcje i historia wysyłek są przechowywane w SQLite.",
    "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": "Automatyczne podsumowanie dla lokalizacji: {location}. Bez zewnętrznego modelu AI, wyłącznie z prognozy i ostrzeżeń.",
    "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} km.",
    "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} 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ść 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 {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 wyświetlana jako modelowa prognoza 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": "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 pogodowe są obecnie obsługiwane tylko dla Polski przez IMGW. Dla tej lokalizacji możesz korzystać z prognozy modelowej Open-Meteo, ale nie traktujemy jej jako oficjalnego alertu.",
    "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 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.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": "Manage language, appearance and preparation for IMGW meteorological warning 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 which additional sections are visible on the dashboard.",
    "settings.dashboardSections.weatherBrief.title": "Daily brief",
    "settings.dashboardSections.weatherBrief.description": "Shows the deterministic daily summary and a short forecast for tomorrow.",
    "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.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": "The subscription will be saved for the selected province and used by the server warning checker.",
    "settings.notifications.enableGlobalDescription": "The subscription will be saved for the selected location and used for model forecast briefs.",
    "settings.notifications.morningBrief": "Morning brief at 7:00",
    "settings.notifications.morningBriefDescription": "Uses the selected location and model forecast to send a short daily summary to this device.",
    "settings.notifications.tomorrowBrief": "Tomorrow brief at 18:00",
    "settings.notifications.tomorrowBriefDescription": "Sends a short evening forecast for the next day, including storm and rain signals from the model.",
    "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": "The server does not have Web Push VAPID keys configured yet.",
    "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": "The server checker sends new IMGW meteorological warnings and briefs through Web Push. Subscriptions and delivery history are stored in SQLite.",
    "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": "Automatic summary for: {location}. No external AI model, only forecast data and warnings.",
    "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} km 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} 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 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}. 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 shown as an Open-Meteo model forecast.",
    "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 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 weather warnings are currently supported only for Poland through IMGW. For this location you can use the Open-Meteo model forecast, but it is not treated as an official alert.",
    "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 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;
}
