diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..e69de29 diff --git a/messages/de.json b/messages/de.json index 93e31f9..a691f99 100644 --- a/messages/de.json +++ b/messages/de.json @@ -207,5 +207,122 @@ "no_data_title": "Für dieses Modell gibt es hier keine Daten", "no_data_body": "Das gewählte Wettermodell deckt {location} nicht ab - regionale Modelle liefern nur Daten in ihrem eigenen Gebiet.", "no_data_try_city": "Ort auf {city} ändern", - "no_data_best_match": "Zu „Best match“ wechseln" + "no_data_best_match": "Zu „Best match“ wechseln", + "model_archive": "Reanalyse", + "model_seasonal": "Saisonmodell", + "nearby_cities_title": "Städte in der Nähe", + "nearby_cities_subtitle": "Höchst- und Tiefstwerte für den gewählten Tag", + "supporter_dialog_title": "Drizz.li Unterstützer", + "supporter_dialog_desc": "Fügen Sie den Zugangsschlüssel aus Ihrer Unterstützer-E-Mail ein, um die Extras freizuschalten.", + "supporter_extras_active": "Unterstützer-Extras sind aktiv", + "supporter_lifetime": "Lebenslanger Zugang", + "supporter_active_until": "Aktiv bis {date}", + "supporter_access_key": "Zugangsschlüssel", + "supporter_key_invalid": "Dieser Schlüssel ist ungültig oder abgelaufen.", + "supporter_server_unreachable": "Der Server ist nicht erreichbar. Prüfen Sie Ihre Verbindung und versuchen Sie es erneut.", + "supporter_verifying": "Wird geprüft…", + "supporter_unlock_button": "Freischalten", + "supporter_remove_key": "Schlüssel von diesem Gerät entfernen", + "supporter_no_key_yet": "Noch keinen Schlüssel?", + "supporter_support_from": "Das Projekt ab {price} unterstützen", + "supporter_checking": "Ihr Zugang wird geprüft…", + "supporter_gate_title": "{feature} ist ein Unterstützer-Extra", + "supporter_gate_body": "Drizz.li ist kostenlos und quelloffen. Mit einem Beitrag ab {price} halten Sie es am Laufen - als Dankeschön schalten Unterstützer die Extras frei.", + "supporter_become": "Unterstützer werden", + "supporter_have_key": "Ich habe einen Schlüssel", + "supporter_key_expired": "Ihr gespeicherter Schlüssel ist ungültig oder abgelaufen.", + "supporter_enter_key": "Geben Sie Ihren Zugangsschlüssel ein.", + "supporter_perk_historical": "Historisches Wetter und Vergleich mit Klimanormalen", + "supporter_perk_seasonal": "Saisonale Aussichten: kommende Monate gegen die Klimanormale", + "supporter_perk_future": "Neue Unterstützer-Extras, sobald sie erscheinen", + "action_close": "Schliessen", + "action_done": "Fertig", + "action_try_again": "Erneut versuchen", + "action_reset_defaults": "Auf Standard zurücksetzen", + "customize_meteograms": "Meteogramme anpassen", + "customizer_intro": "Ziehen Sie Variablen zwischen den Diagrammen, um Ihr eigenes Layout zu bauen.", + "customizer_chart_n": "Diagramm {number}", + "customizer_delete_chart": "Diagramm {number} löschen", + "customizer_add_chart": "+ Diagramm hinzufügen", + "customizer_available": "Verfügbare Variablen", + "customizer_drag": "{variable} ziehen", + "customizer_remove": "{variable} entfernen", + "customizer_drop_here": "Variablen hierher ziehen", + "customizer_all_in_use": "Alle Variablen sind in Verwendung", + "variables_aria": "Variablenauswahl", + "variables_close": "Variablenauswahl schliessen", + "variables_table_section": "Stundentabelle", + "variables_move_up": "{variable} nach oben verschieben", + "variables_move_down": "{variable} nach unten verschieben", + "variables_charts_hint_before": "Meteogramm-Variablen konfigurieren Sie über die Schaltfläche", + "variables_charts_hint_after": "über den Diagrammen.", + "table_customize": "Variablen anpassen", + "table_interval_aria": "Stundenintervall", + "table_now": "Jetzt", + "interval_toggle": "Zwischen 1- und 3-Stunden-Intervall wechseln", + "charts_loading": "Diagramme werden geladen…", + "chart_download": "Meteogramm als PNG-Bild herunterladen", + "chart_credit_viz": "Visualisierung von", + "chart_toggle_series": "{series} ein- oder ausblenden", + "legend_show": "Legende anzeigen", + "meteograms_none_before": "Keine Meteogramme konfiguriert,", + "meteograms_none_action": "fügen Sie Variablen hinzu", + "meteograms_none_historical": "Keine Meteogramme konfiguriert. Fügen Sie Variablen auf der 7-Tage-Seite hinzu.", + "search_searching": "Wird gesucht…", + "search_favorites": "Favoriten", + "search_recent": "Zuletzt", + "search_hint": "Tippen Sie los oder nutzen Sie GPS, um Ihren Standort zu bestimmen", + "search_no_results": "Keine Orte gefunden", + "search_remove_recent": "{location} aus den letzten Orten entfernen", + "search_remove_recent_short": "Aus den letzten entfernen", + "search_aria": "Ort suchen", + "search_gps": "GPS-Standort verwenden", + "model_automatic_selection": "Automatische Auswahl", + "model_selector_aria": "Auswahl: {label}", + "compare_models_heading": "Modelle", + "compare_variables_heading": "Stündliche Wettervariablen", + "compare_timeline_title": "Zeitleiste des Modellvergleichs", + "compare_model_label": "Modell", + "ensemble_trimmed": "Das Ensemble dieses Modells reicht nur etwa {days} Tage voraus, die Streuung ist auf den verfügbaren Zeitraum gekürzt.", + "seasonal_explainer_before": "Eine saisonale Vorhersage zeigt, wie stark ein ganzer Monat voraussichtlich", + "seasonal_explainer_strong": "von seiner Klimanormalen abweicht", + "seasonal_explainer_after": "- nicht das Wetter an einem einzelnen Tag. Lesen Sie den Monatstrend und die Übereinstimmung des Ensembles, nicht die täglichen Ausschläge.", + "seasonal_runs_to": "Diese Aussicht reicht bis {date}.", + "seasonal_range_aria": "Zeitraum der Aussichten", + "seasonal_no_normal": "keine Normale für diesen Monat", + "normals_loading": "Normale wird geladen…", + "normals_loading_period": "Normale 1991–2020 wird geladen…", + "normal_band_legend": "Normalband = Mittel 1991–2020", + "anomaly_vs_normal": "{value} gegenüber Normal", + "stat_avg_temperature": "Durchschnittstemperatur", + "stat_mean_temperature": "Mitteltemperatur", + "stat_total_precipitation": "Niederschlagssumme", + "stat_warmest_day": "Wärmster Tag", + "stat_coldest_day": "Kältester Tag", + "historical_daily_heading": "Täglich", + "historical_daily_hint": "– Tag wählen für Stundenwerte", + "historical_full_range": "– gesamter Zeitraum", + "historical_from": "Von", + "historical_to": "Bis", + "historical_quick_ranges": "Schnellauswahl", + "historical_last_days": "{days} Tage", + "historical_month_last_year": "Monat, Vorjahr", + "label_day": "Tag", + "label_night": "Nacht", + "daycards_past": "Letzte 3 Tage", + "daycards_load_15": "15 Tage laden", + "error_technical_details": "Technische Details", + "err_network_title": "Der Wetterdienst ist nicht erreichbar", + "err_network_hint": "Prüfen Sie Ihre Internetverbindung und versuchen Sie es erneut.", + "err_nodata_title": "Keine Daten für diesen Ort mit dem gewählten Modell", + "err_nodata_hint": "Regionale Wettermodelle decken nur ihr eigenes Gebiet ab - \"Best match\" wählt automatisch ein passendes Modell.", + "err_rejected_title": "Der Wetterdienst hat die Anfrage abgelehnt", + "err_rejected_hint": "Versuchen Sie andere Einstellungen oder stellen Sie das Modell auf \"Best match\" zurück.", + "err_generic_title": "Die Wetterdaten konnten nicht geladen werden", + "err_generic_hint": "Versuchen Sie es gleich noch einmal. Wenn es weiterhin auftritt, wechseln Sie zum Modell \"Best match\".", + "maps_iframe_title": "Interaktive Karte von Open-Meteo", + "page_maps_title": "Wetterkarte", + "page_week_title": "Wetter", + "search_favorite_add": "Zu Favoriten hinzufügen", + "search_favorite_remove": "Aus Favoriten entfernen" } diff --git a/messages/en.json b/messages/en.json index 3743a22..981a03c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -207,5 +207,122 @@ "no_data_title": "No forecast data for this model here", "no_data_body": "The selected weather model doesn't cover {location} - regional models only provide data inside their own area.", "no_data_try_city": "Change location to {city}", - "no_data_best_match": "Switch to Best match" + "no_data_best_match": "Switch to Best match", + "model_archive": "Reanalysis", + "model_seasonal": "Seasonal model", + "nearby_cities_title": "Nearby cities", + "nearby_cities_subtitle": "Highs and lows for the selected day", + "supporter_dialog_title": "Drizz.li Supporter", + "supporter_dialog_desc": "Paste the access key from your supporter email to unlock the extras.", + "supporter_extras_active": "Supporter extras are active", + "supporter_lifetime": "Lifetime access", + "supporter_active_until": "Active until {date}", + "supporter_access_key": "Access key", + "supporter_key_invalid": "That key is not valid or has expired.", + "supporter_server_unreachable": "Couldn't reach the server. Check your connection and try again.", + "supporter_verifying": "Verifying…", + "supporter_unlock_button": "Unlock", + "supporter_remove_key": "Remove key from this device", + "supporter_no_key_yet": "No key yet?", + "supporter_support_from": "Support the project from {price}", + "supporter_checking": "Checking your subscription…", + "supporter_gate_title": "{feature} is a supporter extra", + "supporter_gate_body": "Drizz.li is free and open source. Chip in from {price} to keep it running - as a thank-you, supporters unlock the extras.", + "supporter_become": "Become a supporter", + "supporter_have_key": "I have a key", + "supporter_key_expired": "Your saved key is no longer valid or has expired.", + "supporter_enter_key": "Enter your access key.", + "supporter_perk_historical": "Historical weather & climate-normal comparisons", + "supporter_perk_seasonal": "Seasonal outlook: months ahead vs the climate normal", + "supporter_perk_future": "New supporter extras as they land", + "action_close": "Close", + "action_done": "Done", + "action_try_again": "Try again", + "action_reset_defaults": "Reset to defaults", + "customize_meteograms": "Customize meteograms", + "customizer_intro": "Drag variables between charts to build your own layout.", + "customizer_chart_n": "Chart {number}", + "customizer_delete_chart": "Delete chart {number}", + "customizer_add_chart": "+ Add chart", + "customizer_available": "Available variables", + "customizer_drag": "Drag {variable}", + "customizer_remove": "Remove {variable}", + "customizer_drop_here": "Drop variables here", + "customizer_all_in_use": "All variables are in use", + "variables_aria": "Variable selection", + "variables_close": "Close variable selection", + "variables_table_section": "Hourly table", + "variables_move_up": "Move {variable} up", + "variables_move_down": "Move {variable} down", + "variables_charts_hint_before": "Meteogram variables are configured with the", + "variables_charts_hint_after": "button above the charts.", + "table_customize": "Customize variables", + "table_interval_aria": "Hourly interval", + "table_now": "Now", + "interval_toggle": "Toggle between 1-hour and 3-hour intervals", + "charts_loading": "Loading charts...", + "chart_download": "Download meteogram as PNG image", + "chart_credit_viz": "visualisation by", + "chart_toggle_series": "Toggle {series}", + "legend_show": "Show legend", + "meteograms_none_before": "No meteograms configured,", + "meteograms_none_action": "add some variables", + "meteograms_none_historical": "No meteograms configured. Add variables from the 7-day forecast page.", + "search_searching": "Searching...", + "search_favorites": "Favorites", + "search_recent": "Recent", + "search_hint": "Start typing to search or use GPS to detect your position", + "search_no_results": "No locations found", + "search_remove_recent": "Remove {location} from recent locations", + "search_remove_recent_short": "Remove from recent", + "search_aria": "Search location", + "search_gps": "Use GPS location", + "model_automatic_selection": "Automatic selection", + "model_selector_aria": "{label} selection", + "compare_models_heading": "Models", + "compare_variables_heading": "Hourly Weather Variables", + "compare_timeline_title": "Model Comparison Timeline", + "compare_model_label": "Model", + "ensemble_trimmed": "This model's ensemble only reaches about {days} days ahead, the spread is trimmed to its available range.", + "seasonal_explainer_before": "A seasonal forecast shows how a whole month is likely to", + "seasonal_explainer_strong": "depart from its climate normal", + "seasonal_explainer_after": "- not the weather on any given day. Read the monthly trend and the ensemble agreement, not the daily wiggles.", + "seasonal_runs_to": "This outlook runs to {date}.", + "seasonal_range_aria": "Outlook range", + "seasonal_no_normal": "no normal for this month", + "normals_loading": "normal loading…", + "normals_loading_period": "1991–2020 normal loading…", + "normal_band_legend": "normal band = 1991–2020 mean", + "anomaly_vs_normal": "{value} vs normal", + "stat_avg_temperature": "Average temperature", + "stat_mean_temperature": "Mean temperature", + "stat_total_precipitation": "Total precipitation", + "stat_warmest_day": "Warmest day", + "stat_coldest_day": "Coldest day", + "historical_daily_heading": "Daily", + "historical_daily_hint": "– select a day for hourly detail", + "historical_full_range": "– full range", + "historical_from": "From", + "historical_to": "To", + "historical_quick_ranges": "Quick ranges", + "historical_last_days": "{days} days", + "historical_month_last_year": "Month, last year", + "label_day": "day", + "label_night": "night", + "daycards_past": "Past 3 days", + "daycards_load_15": "Load 15 days", + "error_technical_details": "Technical details", + "err_network_title": "Couldn't reach the weather service", + "err_network_hint": "Check your internet connection and try again.", + "err_nodata_title": "No data for this location with the selected model", + "err_nodata_hint": "Regional weather models only cover their own area - \"Best match\" picks a suitable model automatically.", + "err_rejected_title": "The weather service rejected the request", + "err_rejected_hint": "Try different settings, or switch the model back to \"Best match\".", + "err_generic_title": "Loading the weather data failed", + "err_generic_hint": "Try again in a moment. If it keeps happening, switch the model to \"Best match\".", + "maps_iframe_title": "Open-Meteo interactive map", + "page_maps_title": "Weather map", + "page_week_title": "Weather", + "search_favorite_add": "Add to favorites", + "search_favorite_remove": "Remove from favorites" } diff --git a/messages/es.json b/messages/es.json index dc58924..c93b88b 100644 --- a/messages/es.json +++ b/messages/es.json @@ -207,5 +207,122 @@ "no_data_title": "Este modelo no tiene datos aquí", "no_data_body": "El modelo seleccionado no cubre {location}: los modelos regionales solo ofrecen datos dentro de su propia área.", "no_data_try_city": "Cambiar la ubicación a {city}", - "no_data_best_match": "Cambiar a «Best match»" + "no_data_best_match": "Cambiar a «Best match»", + "model_archive": "Reanálisis", + "model_seasonal": "Modelo estacional", + "nearby_cities_title": "Ciudades cercanas", + "nearby_cities_subtitle": "Máximas y mínimas del día seleccionado", + "supporter_dialog_title": "Colaborador de Drizz.li", + "supporter_dialog_desc": "Pega la clave de acceso de tu correo de colaborador para desbloquear los extras.", + "supporter_extras_active": "Los extras de colaborador están activos", + "supporter_lifetime": "Acceso de por vida", + "supporter_active_until": "Activo hasta el {date}", + "supporter_access_key": "Clave de acceso", + "supporter_key_invalid": "Esa clave no es válida o ha caducado.", + "supporter_server_unreachable": "No se pudo conectar con el servidor. Comprueba tu conexión e inténtalo de nuevo.", + "supporter_verifying": "Verificando…", + "supporter_unlock_button": "Desbloquear", + "supporter_remove_key": "Quitar la clave de este dispositivo", + "supporter_no_key_yet": "¿Aún no tienes clave?", + "supporter_support_from": "Apoya el proyecto desde {price}", + "supporter_checking": "Comprobando tu suscripción…", + "supporter_gate_title": "{feature} es un extra para colaboradores", + "supporter_gate_body": "Drizz.li es gratuito y de código abierto. Aporta desde {price} para mantenerlo en marcha; como agradecimiento, los colaboradores desbloquean los extras.", + "supporter_become": "Hazte colaborador", + "supporter_have_key": "Ya tengo una clave", + "supporter_key_expired": "Tu clave guardada ya no es válida o ha caducado.", + "supporter_enter_key": "Introduce tu clave de acceso.", + "supporter_perk_historical": "Clima histórico y comparación con las normales climáticas", + "supporter_perk_seasonal": "Perspectiva estacional: los próximos meses frente a la normal climática", + "supporter_perk_future": "Nuevos extras para colaboradores en cuanto lleguen", + "action_close": "Cerrar", + "action_done": "Listo", + "action_try_again": "Reintentar", + "action_reset_defaults": "Restablecer valores por defecto", + "customize_meteograms": "Personalizar meteogramas", + "customizer_intro": "Arrastra variables entre gráficos para crear tu propio diseño.", + "customizer_chart_n": "Gráfico {number}", + "customizer_delete_chart": "Eliminar el gráfico {number}", + "customizer_add_chart": "+ Añadir gráfico", + "customizer_available": "Variables disponibles", + "customizer_drag": "Arrastrar {variable}", + "customizer_remove": "Quitar {variable}", + "customizer_drop_here": "Suelta variables aquí", + "customizer_all_in_use": "Todas las variables están en uso", + "variables_aria": "Selección de variables", + "variables_close": "Cerrar la selección de variables", + "variables_table_section": "Tabla horaria", + "variables_move_up": "Subir {variable}", + "variables_move_down": "Bajar {variable}", + "variables_charts_hint_before": "Las variables del meteograma se configuran con el botón", + "variables_charts_hint_after": "situado encima de los gráficos.", + "table_customize": "Personalizar variables", + "table_interval_aria": "Intervalo horario", + "table_now": "Ahora", + "interval_toggle": "Alternar entre intervalos de 1 y 3 horas", + "charts_loading": "Cargando gráficos…", + "chart_download": "Descargar el meteograma como imagen PNG", + "chart_credit_viz": "visualización de", + "chart_toggle_series": "Mostrar u ocultar {series}", + "legend_show": "Mostrar leyenda", + "meteograms_none_before": "No hay meteogramas configurados,", + "meteograms_none_action": "añade algunas variables", + "meteograms_none_historical": "No hay meteogramas configurados. Añade variables desde la página de 7 días.", + "search_searching": "Buscando…", + "search_favorites": "Favoritos", + "search_recent": "Recientes", + "search_hint": "Empieza a escribir o usa el GPS para detectar tu posición", + "search_no_results": "No se encontraron ubicaciones", + "search_remove_recent": "Quitar {location} de las ubicaciones recientes", + "search_remove_recent_short": "Quitar de recientes", + "search_aria": "Buscar ubicación", + "search_gps": "Usar la ubicación GPS", + "model_automatic_selection": "Selección automática", + "model_selector_aria": "Selección de {label}", + "compare_models_heading": "Modelos", + "compare_variables_heading": "Variables meteorológicas horarias", + "compare_timeline_title": "Cronología de comparación de modelos", + "compare_model_label": "Modelo", + "ensemble_trimmed": "El ensemble de este modelo solo llega unos {days} días, así que la dispersión se recorta a su rango disponible.", + "seasonal_explainer_before": "Una previsión estacional muestra cuánto es probable que todo un mes", + "seasonal_explainer_strong": "se desvíe de su normal climática", + "seasonal_explainer_after": "- no el tiempo de un día concreto. Fíjate en la tendencia mensual y en el acuerdo del ensemble, no en los vaivenes diarios.", + "seasonal_runs_to": "Esta perspectiva llega hasta el {date}.", + "seasonal_range_aria": "Periodo de la perspectiva", + "seasonal_no_normal": "sin normal para este mes", + "normals_loading": "cargando la normal…", + "normals_loading_period": "cargando la normal 1991–2020…", + "normal_band_legend": "banda normal = media 1991–2020", + "anomaly_vs_normal": "{value} respecto a la normal", + "stat_avg_temperature": "Temperatura media", + "stat_mean_temperature": "Temperatura media", + "stat_total_precipitation": "Precipitación total", + "stat_warmest_day": "Día más cálido", + "stat_coldest_day": "Día más frío", + "historical_daily_heading": "Diario", + "historical_daily_hint": "– selecciona un día para el detalle horario", + "historical_full_range": "– rango completo", + "historical_from": "Desde", + "historical_to": "Hasta", + "historical_quick_ranges": "Rangos rápidos", + "historical_last_days": "{days} días", + "historical_month_last_year": "Este mes, el año pasado", + "label_day": "día", + "label_night": "noche", + "daycards_past": "3 días pasados", + "daycards_load_15": "Cargar 15 días", + "error_technical_details": "Detalles técnicos", + "err_network_title": "No se pudo conectar con el servicio meteorológico", + "err_network_hint": "Comprueba tu conexión a internet e inténtalo de nuevo.", + "err_nodata_title": "No hay datos para esta ubicación con el modelo seleccionado", + "err_nodata_hint": "Los modelos regionales solo cubren su propia área - \"Best match\" elige automáticamente un modelo adecuado.", + "err_rejected_title": "El servicio meteorológico rechazó la solicitud", + "err_rejected_hint": "Prueba otros ajustes o vuelve al modelo \"Best match\".", + "err_generic_title": "No se pudieron cargar los datos meteorológicos", + "err_generic_hint": "Vuelve a intentarlo en un momento. Si sigue ocurriendo, cambia el modelo a \"Best match\".", + "maps_iframe_title": "Mapa interactivo de Open-Meteo", + "page_maps_title": "Mapa meteorológico", + "page_week_title": "El tiempo", + "search_favorite_add": "Añadir a favoritos", + "search_favorite_remove": "Quitar de favoritos" } diff --git a/messages/fr.json b/messages/fr.json index 613f5a4..1c74a80 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -207,5 +207,122 @@ "no_data_title": "Ce modèle n'a pas de données ici", "no_data_body": "Le modèle sélectionné ne couvre pas {location} : les modèles régionaux ne fournissent des données que dans leur propre zone.", "no_data_try_city": "Changer de lieu pour {city}", - "no_data_best_match": "Passer à « Best match »" + "no_data_best_match": "Passer à « Best match »", + "model_archive": "Réanalyse", + "model_seasonal": "Modèle saisonnier", + "nearby_cities_title": "Villes à proximité", + "nearby_cities_subtitle": "Maximales et minimales du jour sélectionné", + "supporter_dialog_title": "Soutien Drizz.li", + "supporter_dialog_desc": "Collez la clé d'accès reçue par e-mail pour débloquer les bonus.", + "supporter_extras_active": "Les bonus contributeur sont actifs", + "supporter_lifetime": "Accès à vie", + "supporter_active_until": "Actif jusqu'au {date}", + "supporter_access_key": "Clé d'accès", + "supporter_key_invalid": "Cette clé n'est pas valide ou a expiré.", + "supporter_server_unreachable": "Serveur injoignable. Vérifiez votre connexion et réessayez.", + "supporter_verifying": "Vérification…", + "supporter_unlock_button": "Débloquer", + "supporter_remove_key": "Supprimer la clé de cet appareil", + "supporter_no_key_yet": "Pas encore de clé ?", + "supporter_support_from": "Soutenez le projet à partir de {price}", + "supporter_checking": "Vérification de votre abonnement…", + "supporter_gate_title": "{feature} est un bonus contributeur", + "supporter_gate_body": "Drizz.li est gratuit et open source. Contribuez à partir de {price} pour le faire vivre - en remerciement, les contributeurs débloquent les bonus.", + "supporter_become": "Devenir contributeur", + "supporter_have_key": "J'ai une clé", + "supporter_key_expired": "Votre clé enregistrée n'est plus valide ou a expiré.", + "supporter_enter_key": "Saisissez votre clé d'accès.", + "supporter_perk_historical": "Météo historique et comparaison aux normales climatiques", + "supporter_perk_seasonal": "Aperçu saisonnier : les mois à venir face à la normale climatique", + "supporter_perk_future": "Les nouveaux bonus dès leur sortie", + "action_close": "Fermer", + "action_done": "Terminé", + "action_try_again": "Réessayer", + "action_reset_defaults": "Rétablir les valeurs par défaut", + "customize_meteograms": "Personnaliser les météogrammes", + "customizer_intro": "Faites glisser les variables d'un graphique à l'autre pour composer votre mise en page.", + "customizer_chart_n": "Graphique {number}", + "customizer_delete_chart": "Supprimer le graphique {number}", + "customizer_add_chart": "+ Ajouter un graphique", + "customizer_available": "Variables disponibles", + "customizer_drag": "Déplacer {variable}", + "customizer_remove": "Retirer {variable}", + "customizer_drop_here": "Déposez des variables ici", + "customizer_all_in_use": "Toutes les variables sont utilisées", + "variables_aria": "Sélection des variables", + "variables_close": "Fermer la sélection des variables", + "variables_table_section": "Tableau horaire", + "variables_move_up": "Déplacer {variable} vers le haut", + "variables_move_down": "Déplacer {variable} vers le bas", + "variables_charts_hint_before": "Les variables des météogrammes se règlent avec le bouton", + "variables_charts_hint_after": "au-dessus des graphiques.", + "table_customize": "Personnaliser les variables", + "table_interval_aria": "Intervalle horaire", + "table_now": "Maintenant", + "interval_toggle": "Basculer entre les intervalles de 1 h et 3 h", + "charts_loading": "Chargement des graphiques…", + "chart_download": "Télécharger le météogramme en PNG", + "chart_credit_viz": "visualisation par", + "chart_toggle_series": "Afficher ou masquer {series}", + "legend_show": "Afficher la légende", + "meteograms_none_before": "Aucun météogramme configuré,", + "meteograms_none_action": "ajoutez des variables", + "meteograms_none_historical": "Aucun météogramme configuré. Ajoutez des variables depuis la page 7 jours.", + "search_searching": "Recherche…", + "search_favorites": "Favoris", + "search_recent": "Récents", + "search_hint": "Commencez à taper ou utilisez le GPS pour détecter votre position", + "search_no_results": "Aucun lieu trouvé", + "search_remove_recent": "Retirer {location} des lieux récents", + "search_remove_recent_short": "Retirer des récents", + "search_aria": "Rechercher un lieu", + "search_gps": "Utiliser la position GPS", + "model_automatic_selection": "Sélection automatique", + "model_selector_aria": "Sélection : {label}", + "compare_models_heading": "Modèles", + "compare_variables_heading": "Variables météo horaires", + "compare_timeline_title": "Chronologie de comparaison des modèles", + "compare_model_label": "Modèle", + "ensemble_trimmed": "L'ensemble de ce modèle ne va qu'à environ {days} jours, la dispersion est donc limitée à sa portée disponible.", + "seasonal_explainer_before": "Une prévision saisonnière montre dans quelle mesure un mois entier devrait", + "seasonal_explainer_strong": "s'écarter de sa normale climatique", + "seasonal_explainer_after": "- et non le temps qu'il fera un jour donné. Lisez la tendance mensuelle et l'accord de l'ensemble, pas les variations quotidiennes.", + "seasonal_runs_to": "Cet aperçu va jusqu'au {date}.", + "seasonal_range_aria": "Période de l'aperçu", + "seasonal_no_normal": "pas de normale pour ce mois", + "normals_loading": "chargement de la normale…", + "normals_loading_period": "chargement de la normale 1991–2020…", + "normal_band_legend": "bande normale = moyenne 1991–2020", + "anomaly_vs_normal": "{value} par rapport à la normale", + "stat_avg_temperature": "Température moyenne", + "stat_mean_temperature": "Température moyenne", + "stat_total_precipitation": "Précipitations totales", + "stat_warmest_day": "Jour le plus chaud", + "stat_coldest_day": "Jour le plus froid", + "historical_daily_heading": "Quotidien", + "historical_daily_hint": "– sélectionnez un jour pour le détail horaire", + "historical_full_range": "– période complète", + "historical_from": "Du", + "historical_to": "Au", + "historical_quick_ranges": "Plages rapides", + "historical_last_days": "{days} jours", + "historical_month_last_year": "Ce mois, l'an dernier", + "label_day": "jour", + "label_night": "nuit", + "daycards_past": "3 jours passés", + "daycards_load_15": "Charger 15 jours", + "error_technical_details": "Détails techniques", + "err_network_title": "Service météo injoignable", + "err_network_hint": "Vérifiez votre connexion internet et réessayez.", + "err_nodata_title": "Aucune donnée pour ce lieu avec le modèle sélectionné", + "err_nodata_hint": "Les modèles régionaux ne couvrent que leur propre zone - \"Best match\" choisit automatiquement un modèle adapté.", + "err_rejected_title": "Le service météo a refusé la requête", + "err_rejected_hint": "Essayez d'autres réglages ou revenez au modèle \"Best match\".", + "err_generic_title": "Le chargement des données météo a échoué", + "err_generic_hint": "Réessayez dans un instant. Si cela persiste, revenez au modèle \"Best match\".", + "maps_iframe_title": "Carte interactive Open-Meteo", + "page_maps_title": "Carte météo", + "page_week_title": "Météo", + "search_favorite_add": "Ajouter aux favoris", + "search_favorite_remove": "Retirer des favoris" } diff --git a/messages/it.json b/messages/it.json index c285940..2937961 100644 --- a/messages/it.json +++ b/messages/it.json @@ -207,5 +207,122 @@ "no_data_title": "Nessun dato per questo modello qui", "no_data_body": "Il modello selezionato non copre {location}: i modelli regionali forniscono dati solo nella propria area.", "no_data_try_city": "Cambia località in {city}", - "no_data_best_match": "Passa a «Best match»" + "no_data_best_match": "Passa a «Best match»", + "model_archive": "Rianalisi", + "model_seasonal": "Modello stagionale", + "nearby_cities_title": "Città nelle vicinanze", + "nearby_cities_subtitle": "Massime e minime del giorno selezionato", + "supporter_dialog_title": "Sostenitore Drizz.li", + "supporter_dialog_desc": "Incolla la chiave di accesso ricevuta via e-mail per sbloccare gli extra.", + "supporter_extras_active": "Gli extra per sostenitori sono attivi", + "supporter_lifetime": "Accesso a vita", + "supporter_active_until": "Attivo fino al {date}", + "supporter_access_key": "Chiave di accesso", + "supporter_key_invalid": "Questa chiave non è valida o è scaduta.", + "supporter_server_unreachable": "Server non raggiungibile. Controlla la connessione e riprova.", + "supporter_verifying": "Verifica in corso…", + "supporter_unlock_button": "Sblocca", + "supporter_remove_key": "Rimuovi la chiave da questo dispositivo", + "supporter_no_key_yet": "Non hai ancora una chiave?", + "supporter_support_from": "Sostieni il progetto da {price}", + "supporter_checking": "Verifica dell'abbonamento…", + "supporter_gate_title": "{feature} è un extra per sostenitori", + "supporter_gate_body": "Drizz.li è gratuito e open source. Contribuisci da {price} per tenerlo in vita - come ringraziamento, i sostenitori sbloccano gli extra.", + "supporter_become": "Diventa sostenitore", + "supporter_have_key": "Ho una chiave", + "supporter_key_expired": "La chiave salvata non è più valida o è scaduta.", + "supporter_enter_key": "Inserisci la tua chiave di accesso.", + "supporter_perk_historical": "Meteo storico e confronto con le normali climatiche", + "supporter_perk_seasonal": "Prospettive stagionali: i mesi a venire rispetto alla normale climatica", + "supporter_perk_future": "Nuovi extra non appena arrivano", + "action_close": "Chiudi", + "action_done": "Fatto", + "action_try_again": "Riprova", + "action_reset_defaults": "Ripristina i valori predefiniti", + "customize_meteograms": "Personalizza i meteogrammi", + "customizer_intro": "Trascina le variabili tra i grafici per costruire il tuo layout.", + "customizer_chart_n": "Grafico {number}", + "customizer_delete_chart": "Elimina il grafico {number}", + "customizer_add_chart": "+ Aggiungi grafico", + "customizer_available": "Variabili disponibili", + "customizer_drag": "Trascina {variable}", + "customizer_remove": "Rimuovi {variable}", + "customizer_drop_here": "Trascina qui le variabili", + "customizer_all_in_use": "Tutte le variabili sono in uso", + "variables_aria": "Selezione delle variabili", + "variables_close": "Chiudi la selezione delle variabili", + "variables_table_section": "Tabella oraria", + "variables_move_up": "Sposta {variable} in alto", + "variables_move_down": "Sposta {variable} in basso", + "variables_charts_hint_before": "Le variabili dei meteogrammi si impostano con il pulsante", + "variables_charts_hint_after": "sopra i grafici.", + "table_customize": "Personalizza le variabili", + "table_interval_aria": "Intervallo orario", + "table_now": "Ora", + "interval_toggle": "Alterna tra intervalli di 1 e 3 ore", + "charts_loading": "Caricamento dei grafici…", + "chart_download": "Scarica il meteogramma come immagine PNG", + "chart_credit_viz": "visualizzazione di", + "chart_toggle_series": "Mostra o nascondi {series}", + "legend_show": "Mostra legenda", + "meteograms_none_before": "Nessun meteogramma configurato,", + "meteograms_none_action": "aggiungi qualche variabile", + "meteograms_none_historical": "Nessun meteogramma configurato. Aggiungi variabili dalla pagina a 7 giorni.", + "search_searching": "Ricerca in corso…", + "search_favorites": "Preferiti", + "search_recent": "Recenti", + "search_hint": "Inizia a digitare o usa il GPS per rilevare la tua posizione", + "search_no_results": "Nessuna località trovata", + "search_remove_recent": "Rimuovi {location} dai luoghi recenti", + "search_remove_recent_short": "Rimuovi dai recenti", + "search_aria": "Cerca una località", + "search_gps": "Usa la posizione GPS", + "model_automatic_selection": "Selezione automatica", + "model_selector_aria": "Selezione di {label}", + "compare_models_heading": "Modelli", + "compare_variables_heading": "Variabili meteo orarie", + "compare_timeline_title": "Cronologia del confronto tra modelli", + "compare_model_label": "Modello", + "ensemble_trimmed": "L'ensemble di questo modello arriva solo a circa {days} giorni, quindi la dispersione è ridotta al periodo disponibile.", + "seasonal_explainer_before": "Una previsione stagionale mostra quanto un intero mese potrebbe", + "seasonal_explainer_strong": "discostarsi dalla sua normale climatica", + "seasonal_explainer_after": "- non il tempo di un singolo giorno. Guarda la tendenza mensile e l'accordo dell'ensemble, non gli sbalzi giornalieri.", + "seasonal_runs_to": "Queste prospettive arrivano fino al {date}.", + "seasonal_range_aria": "Periodo delle prospettive", + "seasonal_no_normal": "nessuna normale per questo mese", + "normals_loading": "caricamento della normale…", + "normals_loading_period": "caricamento della normale 1991–2020…", + "normal_band_legend": "banda normale = media 1991–2020", + "anomaly_vs_normal": "{value} rispetto alla normale", + "stat_avg_temperature": "Temperatura media", + "stat_mean_temperature": "Temperatura media", + "stat_total_precipitation": "Precipitazioni totali", + "stat_warmest_day": "Giorno più caldo", + "stat_coldest_day": "Giorno più freddo", + "historical_daily_heading": "Giornaliero", + "historical_daily_hint": "– seleziona un giorno per il dettaglio orario", + "historical_full_range": "– intervallo completo", + "historical_from": "Dal", + "historical_to": "Al", + "historical_quick_ranges": "Intervalli rapidi", + "historical_last_days": "{days} giorni", + "historical_month_last_year": "Questo mese, l'anno scorso", + "label_day": "giorno", + "label_night": "notte", + "daycards_past": "Ultimi 3 giorni", + "daycards_load_15": "Carica 15 giorni", + "error_technical_details": "Dettagli tecnici", + "err_network_title": "Servizio meteo non raggiungibile", + "err_network_hint": "Controlla la connessione a internet e riprova.", + "err_nodata_title": "Nessun dato per questa località con il modello selezionato", + "err_nodata_hint": "I modelli regionali coprono solo la propria area - \"Best match\" sceglie automaticamente un modello adatto.", + "err_rejected_title": "Il servizio meteo ha rifiutato la richiesta", + "err_rejected_hint": "Prova impostazioni diverse o torna al modello \"Best match\".", + "err_generic_title": "Caricamento dei dati meteo non riuscito", + "err_generic_hint": "Riprova tra un momento. Se continua, passa al modello \"Best match\".", + "maps_iframe_title": "Mappa interattiva di Open-Meteo", + "page_maps_title": "Mappa meteo", + "page_week_title": "Meteo", + "search_favorite_add": "Aggiungi ai preferiti", + "search_favorite_remove": "Rimuovi dai preferiti" } diff --git a/scripts/build-cities.mjs b/scripts/build-cities.mjs new file mode 100644 index 0000000..1eb79c8 --- /dev/null +++ b/scripts/build-cities.mjs @@ -0,0 +1,119 @@ +/** + * Regenerates static/data/cities/ - the city lists the "nearby cities" section + * on the week page picks from. + * + * Source: GeoNames (CC BY 4.0) via the cities500 mirror. + * + * The full list of towns down to 20k inhabitants is ~1 MB, far too much to ship + * to a phone for one section, so it is cut into 10x10 degree tiles. A tile + * holds every town within NEAR_RADIUS_KM of anywhere inside it, plus the larger + * cities within FAR_RADIUS_KM - so one small fetch answers both "what is around + * the corner" and "what is the nearest place you would recognise" (which is all + * a location in, say, the Australian outback can offer). Tiles overlap by + * design; duplicating a few rows is cheaper than a second round trip. + * + * The ids are GeoNames ids, which is what Open-Meteo's geocoding API returns, + * so a row can be turned into a location route without another lookup. + * + * Run with: node scripts/build-cities.mjs + */ +import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const SOURCE = 'https://raw.githubusercontent.com/lmfmaier/cities-json/master/cities500.json'; + +const MIN_POPULATION = 20_000; +/** every city of any size within this range of the tile */ +const NEAR_RADIUS_KM = 400; +/** only cities above FAR_MIN_POPULATION out to here */ +const FAR_RADIUS_KM = 1500; +const FAR_MIN_POPULATION = 300_000; +const TILE_DEGREES = 10; + +const outDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'static', 'data', 'cities'); + +// CITIES_SOURCE_FILE lets you build from an already-downloaded copy, which is +// also the only way to run this behind a proxy that node doesn't pick up. +const localSource = process.env.CITIES_SOURCE_FILE; +const source = localSource + ? JSON.parse(await readFile(localSource, 'utf8')) + : await fetch(SOURCE).then((res) => { + if (!res.ok) throw new Error(`source fetch failed: ${res.status}`); + return res.json(); + }); + +const cities = source + .map((c) => ({ + id: Number(c.id), + name: c.name, + country: c.country, + lat: Number(c.lat), + lon: Number(c.lon), + pop: Number(c.pop ?? 0) + })) + .filter((c) => Number.isFinite(c.lat) && Number.isFinite(c.lon) && c.pop >= MIN_POPULATION) + // biggest first, so a tile that gets truncated keeps the recognisable names + .sort((a, b) => b.pop - a.pop); + +const toRad = Math.PI / 180; + +/** Distance from a point to the nearest point of a lat/lon rectangle. */ +function distanceToTileKm(lat, lon, latMin, latMax, lonMin, lonMax) { + const clampedLat = Math.min(latMax, Math.max(latMin, lat)); + // longitude distance shrinks towards the poles, measured at the closest latitude + let dLon = 0; + if (lon < lonMin) dLon = lonMin - lon; + else if (lon > lonMax) dLon = lon - lonMax; + if (dLon > 180) dLon = 360 - dLon; + + const dLat = Math.abs(clampedLat - lat); + const lonKm = dLon * 111.32 * Math.cos(clampedLat * toRad); + return Math.hypot(dLat * 111.32, lonKm); +} + +const tiles = new Map(); +for (let latIndex = 0; latIndex < 180 / TILE_DEGREES; latIndex++) { + for (let lonIndex = 0; lonIndex < 360 / TILE_DEGREES; lonIndex++) { + const latMin = -90 + latIndex * TILE_DEGREES; + const lonMin = -180 + lonIndex * TILE_DEGREES; + const bounds = [latMin, latMin + TILE_DEGREES, lonMin, lonMin + TILE_DEGREES]; + + const rows = []; + for (const c of cities) { + const dist = distanceToTileKm(c.lat, c.lon, ...bounds); + const inRange = + dist <= NEAR_RADIUS_KM || (c.pop >= FAR_MIN_POPULATION && dist <= FAR_RADIUS_KM); + if (!inRange) continue; + // tuples, not objects: same data, roughly half the bytes + rows.push([ + c.id, + c.name, + c.country, + +c.lat.toFixed(3), + +c.lon.toFixed(3), + Math.round(c.pop / 1000) + ]); + } + if (rows.length > 0) tiles.set(`${latIndex}_${lonIndex}`, rows); + } +} + +await rm(outDir, { recursive: true, force: true }); +await mkdir(outDir, { recursive: true }); + +let bytes = 0; +for (const [key, rows] of tiles) { + const json = JSON.stringify(rows); + bytes += json.length; + await writeFile(join(outDir, `${key}.json`), json, 'utf8'); +} +// the index tells the client which tiles exist, so an empty ocean tile is a +// no-op instead of a 404 +await writeFile(join(outDir, 'index.json'), JSON.stringify([...tiles.keys()]), 'utf8'); + +const largest = Math.max(...[...tiles.values()].map((r) => r.length)); +console.log( + `wrote ${tiles.size} tiles (${(bytes / 1024 / 1024).toFixed(1)} MB total, ` + + `avg ${Math.round(bytes / tiles.size / 1024)} KB, largest ${largest} cities) to ${outDir}` +); diff --git a/src/lib/charts/CanvasChart.svelte b/src/lib/charts/CanvasChart.svelte index 3ec6a20..d0904e9 100644 --- a/src/lib/charts/CanvasChart.svelte +++ b/src/lib/charts/CanvasChart.svelte @@ -18,6 +18,8 @@ /> --> @@ -87,7 +89,7 @@ class="toolbar-btn" disabled={!hasCharts || downloading} onclick={handleDownload} - title="Download meteogram as PNG image" + title={m.chart_download()} > {#if downloading} toggleFavorite(location)} - aria-label={fav ? 'Remove from favorites' : 'Add to favorites'} - title={fav ? 'Remove from favorites' : 'Add to favorites'} + aria-label={fav ? m.search_favorite_remove() : m.search_favorite_add()} + title={fav ? m.search_favorite_remove() : m.search_favorite_add()} > removeRecent(location)} - aria-label="Remove {location.name} from recent locations" - title="Remove from recent" + aria-label={m.search_remove_recent({ location: location.name })} + title={m.search_remove_recent_short()} > @@ -231,7 +233,7 @@ class="h-9" autocomplete="off" spellcheck="false" - aria-label="Search Location" + aria-label={m.search_aria()} bind:value={searchQuery} bind:ref={searchInputEl} /> @@ -240,7 +242,7 @@ variant="outline" size="default" class="h-9 px-2.5" - title="Use GPS Location" + title={m.search_gps()} onclick={() => (searchQuery = 'GPS')} > @@ -266,7 +268,7 @@
- Searching... + {m.search_searching()}
{:then results} @@ -276,7 +278,7 @@
- Favorites + {m.search_favorites()}
{#each $storedFavoriteLocations as loc (locationKey(loc))} @@ -290,7 +292,7 @@ ? 'mt-3' : ''}" > - Recent + {m.search_recent()}
{#each recentToShow as loc (locationKey(loc))} @@ -316,7 +318,7 @@ /> - Start typing to search or use GPS to detect your position + {m.search_hint()}
{/if} @@ -336,7 +338,7 @@ {:else} - No locations found + {m.search_no_results()} {/if} {:catch error} diff --git a/src/lib/paywall/PaywallGate.svelte b/src/lib/paywall/PaywallGate.svelte index 08f661e..0be755c 100644 --- a/src/lib/paywall/PaywallGate.svelte +++ b/src/lib/paywall/PaywallGate.svelte @@ -1,6 +1,8 @@ - Drizz.li Supporter + {m.supporter_dialog_title()} - Paste the access key from your supporter email to unlock the extras. + {m.supporter_dialog_desc()} @@ -87,7 +89,7 @@
-

Supporter extras are active

+

{m.supporter_extras_active()}

{#if expiresLabel}

{expiresLabel}

{/if}
@@ -95,7 +97,7 @@
- + {localError}

{:else if $supporterState.status === 'error'}

- Couldn't reach the server. Check your connection and try again. + {m.supporter_server_unreachable()}

{/if} @@ -126,18 +128,18 @@ class="cursor-pointer text-center text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline" onclick={removeKey} > - Remove key from this device + {m.supporter_remove_key()} {:else}

- No key yet? + {m.supporter_no_key_yet()} - Support the project from {price} + {m.supporter_support_from({ price })}

{/if} diff --git a/src/lib/paywall/config.ts b/src/lib/paywall/config.ts index 338fe8d..77b14b7 100644 --- a/src/lib/paywall/config.ts +++ b/src/lib/paywall/config.ts @@ -1,3 +1,5 @@ +import * as m from '$lib/paraglide/messages'; + /** * Paywall configuration. * @@ -63,9 +65,10 @@ export function getSupporterPrice(): string { return currency === 'CHF' ? `${symbol} 3 / month` : `${symbol}3 / month`; } -/** Short, human list of what supporting unlocks (shown on the locked panel). */ -export const SUPPORTER_PERKS = [ - 'Historical weather & climate-normal comparisons', - 'Seasonal outlook: months ahead vs the climate normal', - 'New supporter extras as they land' +/** Short, human list of what supporting unlocks (shown on the locked panel). + * A function, not a constant: the copy has to follow the active locale. */ +export const SUPPORTER_PERKS = (): string[] => [ + m.supporter_perk_historical(), + m.supporter_perk_seasonal(), + m.supporter_perk_future() ]; diff --git a/src/lib/paywall/supporter.ts b/src/lib/paywall/supporter.ts index 710ddfa..416d9ba 100644 --- a/src/lib/paywall/supporter.ts +++ b/src/lib/paywall/supporter.ts @@ -14,6 +14,8 @@ import { derived, get, writable } from 'svelte/store'; import { persisted } from 'svelte-persisted-store'; +import * as m from '$lib/paraglide/messages'; + import { PAYWALL_API_BASE } from './config'; /** The supporter's access key, e.g. "DRZ-7Q2K-9F4M-XW3P". Empty when signed out. */ @@ -99,7 +101,7 @@ export async function verifyKey(key: string): Promise { const trimmed = key.trim(); if (!trimmed) { supporterState.set({ status: 'invalid' }); - return { valid: false, error: 'Enter your access key.' }; + return { valid: false, error: m.supporter_enter_key() }; } supporterState.set({ status: 'checking' }); diff --git a/src/lib/services/nearby-cities.ts b/src/lib/services/nearby-cities.ts new file mode 100644 index 0000000..23483f2 --- /dev/null +++ b/src/lib/services/nearby-cities.ts @@ -0,0 +1,143 @@ +/** + * Picks a handful of cities around a location, for cross-referencing a forecast + * against places you already have a feel for. + * + * Open-Meteo's geocoding API can only search by name - there is no radius or + * reverse lookup - so the candidates come from a static GeoNames extract, cut + * into 10x10 degree tiles (see scripts/build-cities.mjs). Looking somewhere up + * costs one small tile fetch, which the browser then caches. + */ +import { base } from '$app/paths'; + +/** [geonames id, name, country code, latitude, longitude, population/1000] */ +type CityRow = [number, string, string, number, number, number]; + +export interface NearbyCity { + id: number; + name: string; + countryCode: string; + latitude: number; + longitude: number; + population: number; + distanceKm: number; +} + +const EARTH_RADIUS_KM = 6371; +const TILE_DEGREES = 10; + +export function distanceKm(lat1: number, lon1: number, lat2: number, lon2: number): number { + const toRad = Math.PI / 180; + const dLat = (lat2 - lat1) * toRad; + const dLon = (lon2 - lon1) * toRad; + const a = + Math.sin(dLat / 2) ** 2 + + Math.cos(lat1 * toRad) * Math.cos(lat2 * toRad) * Math.sin(dLon / 2) ** 2; + return 2 * EARTH_RADIUS_KM * Math.asin(Math.min(1, Math.sqrt(a))); +} + +function tileKey(latitude: number, longitude: number): string { + const lat = Math.min(89.999, Math.max(-90, latitude)); + const lon = ((((longitude + 180) % 360) + 360) % 360) - 180; + return `${Math.floor((lat + 90) / TILE_DEGREES)}_${Math.floor((lon + 180) / TILE_DEGREES)}`; +} + +const tileCache = new Map>(); +let indexPromise: Promise> | null = null; + +async function fetchJson(path: string): Promise { + const res = await fetch(`${base}/data/cities/${path}`); + if (!res.ok) throw new Error(`${path}: ${res.status}`); + return res.json() as Promise; +} + +/** Tiles exist only where there are cities, so the index turns an empty ocean + * tile into a no-op instead of a 404. */ +function loadIndex(): Promise> { + indexPromise ??= fetchJson('index.json') + .then((keys) => new Set(keys)) + .catch((err) => { + indexPromise = null; + throw err; + }); + return indexPromise; +} + +async function loadTile(key: string): Promise { + if (!(await loadIndex()).has(key)) return []; + + let tile = tileCache.get(key); + if (!tile) { + tile = fetchJson(`${key}.json`).catch((err) => { + // a failed fetch must not poison the cache: the next visit retries + tileCache.delete(key); + throw err; + }); + tileCache.set(key, tile); + } + return tile; +} + +/** + * Widening rings. Within 400 km the tile holds towns down to 20k; past that + * only cities above 300k, which is exactly the bias a wide search wants - if + * nothing is nearby, the answer should be a place people have heard of. + */ +const SEARCH_RADII_KM = [200, 400, 1500]; + +/** + * Returns up to `count` cities around the given point, ordered by distance. + * + * Candidates are ranked by population damped by distance, so a town up the + * valley can outrank a metropolis three hours away, and picks have to keep + * their distance from each other - otherwise a place like New York fills the + * whole list with its own boroughs. + */ +export async function findNearbyCities( + latitude: number, + longitude: number, + count = 10 +): Promise { + const rows = await loadTile(tileKey(latitude, longitude)); + if (rows.length === 0) return []; + + let best: NearbyCity[] = []; + + for (const radius of SEARCH_RADII_KM) { + const halfWeightKm = radius / 4; + // far-apart picks in a wide search, tight ones when everything is close + const minSeparationKm = Math.max(15, radius / 20); + + const scored = rows + .map(([id, name, countryCode, lat, lon, popK]) => { + const dist = distanceKm(latitude, longitude, lat, lon); + return { + city: { + id, + name, + countryCode, + latitude: lat, + longitude: lon, + population: popK * 1000, + distanceKm: dist + }, + score: popK / (1 + (dist / halfWeightKm) ** 2) + }; + }) + .filter(({ city }) => city.distanceKm >= minSeparationKm && city.distanceKm <= radius) + .sort((a, b) => b.score - a.score); + + const picked: NearbyCity[] = []; + for (const { city } of scored) { + if (picked.length === count) break; + const tooClose = picked.some( + (p) => distanceKm(p.latitude, p.longitude, city.latitude, city.longitude) < minSeparationKm + ); + if (!tooClose) picked.push(city); + } + + if (picked.length >= count) return picked.sort((a, b) => a.distanceKm - b.distanceKm); + if (picked.length > best.length) best = picked; + } + + return best.sort((a, b) => a.distanceKm - b.distanceKm); +} diff --git a/src/lib/services/weather.ts b/src/lib/services/weather.ts index 4299466..e2ade83 100644 --- a/src/lib/services/weather.ts +++ b/src/lib/services/weather.ts @@ -14,6 +14,7 @@ import { Unit } from '@openmeteo/sdk/unit'; import { fetchWeatherApi } from 'openmeteo'; import { type DaylightBand, buildDaylightBands } from '$lib/charts/bands'; +import * as m from '$lib/paraglide/messages'; import type { VariableWithValues } from '@openmeteo/sdk/variable-with-values'; import type { VariablesWithTime } from '@openmeteo/sdk/variables-with-time'; @@ -313,8 +314,8 @@ export function humanizeWeatherError(err: unknown): FriendlyWeatherError { msg.includes('network request failed') ) { return { - title: "Couldn't reach the weather service", - hint: 'Check your internet connection and try again.', + title: m.err_network_title(), + hint: m.err_network_hint(), detail: raw }; } @@ -325,21 +326,21 @@ export function humanizeWeatherError(err: unknown): FriendlyWeatherError { msg.includes('coordinates') ) { return { - title: 'No data for this location with the selected model', - hint: 'Regional weather models only cover their own area — "Best match" picks a suitable model automatically.', + title: m.err_nodata_title(), + hint: m.err_nodata_hint(), detail: raw }; } if (msg.includes('invalid') || msg.includes('cannot be') || msg.includes('bad request')) { return { - title: 'The weather service rejected the request', - hint: 'Try different settings, or switch the model back to "Best match".', + title: m.err_rejected_title(), + hint: m.err_rejected_hint(), detail: raw }; } return { - title: 'Loading the weather data failed', - hint: 'Try again in a moment. If it keeps happening, switch the model to "Best match".', + title: m.err_generic_title(), + hint: m.err_generic_hint(), detail: raw }; } @@ -936,6 +937,8 @@ export interface HistoricalForecastParams extends WeatherLocation, WeatherUnitPa end_date: string; /** Hourly API variables to request; defaults to the core week set. */ hourlyVariables?: string[]; + /** Reanalysis to read from; omitted lets the API pick. */ + model?: string; } export interface HistoricalForecastResult { @@ -995,7 +998,8 @@ export async function fetchHistoricalWeather( temperature_unit: params.temperature_unit ?? 'celsius', wind_speed_unit: params.wind_speed_unit ?? 'kmh', precipitation_unit: params.precipitation_unit ?? 'mm', - timezone: params.timezone + timezone: params.timezone, + models: params.model && params.model !== 'best_match' ? params.model : undefined }; const cleanParams: Record = {}; @@ -1201,6 +1205,8 @@ export interface SeasonalForecastParams extends WeatherLocation, WeatherUnitPara dailyVariables?: string[]; /** Lead time in days; the API allows at most 216. */ forecast_days?: number; + /** Seasonal model; omitted lets the API pick. */ + model?: string; } export interface SeasonalForecastResult { @@ -1273,7 +1279,8 @@ export async function fetchSeasonalForecast( temperature_unit: params.temperature_unit ?? 'celsius', wind_speed_unit: params.wind_speed_unit ?? 'kmh', precipitation_unit: params.precipitation_unit ?? 'mm', - timezone: params.timezone + timezone: params.timezone, + models: params.model && params.model !== 'best_match' ? params.model : undefined }; const cleanParams: Record = {}; @@ -1372,3 +1379,71 @@ export async function fetchSeasonalForecast( timezone }; } + +// ─── Nearby cities snapshot ───────────────────────────────────────────────────── + +export interface NearbyDaily { + /** local calendar date ("yyyy-MM-dd") -> that day's summary for this city */ + byDate: Record; +} + +export interface NearbySnapshotParams extends WeatherUnitParams { + points: { latitude: number; longitude: number }[]; + past_days?: number; + forecast_days?: number; +} + +/** + * Fetches a daily summary for several locations in one request - the forecast + * API takes comma-separated coordinates and answers with one response per + * point, in order. + * + * Deliberately runs on best_match: the nearby list can reach well past the + * domain of whatever regional model the page is showing, and a row of dashes + * is worse than a row from a model that covers everywhere. + */ +export async function fetchNearbyDaily( + params: NearbySnapshotParams +): Promise<(NearbyDaily | null)[]> { + if (params.points.length === 0) return []; + + const apiParams: Record = { + latitude: params.points.map((p) => p.latitude).join(','), + longitude: params.points.map((p) => p.longitude).join(','), + daily: 'weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum', + temperature_unit: params.temperature_unit ?? 'celsius', + wind_speed_unit: params.wind_speed_unit ?? 'kmh', + precipitation_unit: params.precipitation_unit ?? 'mm', + past_days: String(params.past_days ?? 3), + forecast_days: String(params.forecast_days ?? 16), + timezone: 'auto' + }; + + const responses = await fetchWeatherApi(FORECAST_URL, apiParams); + + return params.points.map((_, i) => { + const response = responses[i]; + const dailyBlock = response?.daily(); + if (!dailyBlock) return null; + + const utcOffsetSeconds = response.utcOffsetSeconds(); + const codes = getValues(dailyBlock.variables(0)!); + const max = getValues(dailyBlock.variables(1)!); + const min = getValues(dailyBlock.variables(2)!); + const precip = getValues(dailyBlock.variables(3)!); + + // Same convention as the seasonal fetch: shift by the response's own + // offset, then read the calendar date off the ISO string. + const byDate: NearbyDaily['byDate'] = {}; + getTimestamps(dailyBlock).forEach((t, d) => { + const key = new Date(t + utcOffsetSeconds * 1000).toISOString().slice(0, 10); + byDate[key] = { + weatherCode: codes[d], + max: max[d], + min: min[d], + precipitation: precip[d] + }; + }); + return { byDate }; + }); +} diff --git a/src/lib/stores/settings.ts b/src/lib/stores/settings.ts index baed673..f83816d 100644 --- a/src/lib/stores/settings.ts +++ b/src/lib/stores/settings.ts @@ -150,6 +150,10 @@ export const storedChartRange = persisted('chart_range_v1', 'aut /** Selected ensemble model for the 14-day spread forecast. */ export const storedEnsembleModel = persisted('ensemble_model', 'ncep_gefs_seamless'); +/** Reanalysis used on the historical page, and the seasonal model. */ +export const storedArchiveModel = persisted('archive_model', 'best_match'); +export const storedSeasonalModel = persisted('seasonal_model', 'best_match'); + /** Measurement units, shared across every forecast page and persisted. */ export interface UnitPrefs { temperature_unit: 'celsius' | 'fahrenheit'; diff --git a/src/lib/utils/location.ts b/src/lib/utils/location.ts index ebd250a..824cf4f 100644 --- a/src/lib/utils/location.ts +++ b/src/lib/utils/location.ts @@ -13,7 +13,14 @@ export const geoLocationNameToRoute = (name: string) => { // selections navigate here directly, no geocoding id involved const COORD_ROUTE = /^(-?\d+(?:\.\d+)?)N(-?\d+(?:\.\d+)?)E$/i; -export function buildLocationRoute(location: GeoLocation): string { +/** Only the fields the route is built from, so callers holding a partial record + * (the nearby-cities list, for one) don't have to fake a whole GeoLocation. */ +export type RoutableLocation = Pick< + GeoLocation, + 'id' | 'name' | 'latitude' | 'longitude' | 'feature_code' | 'population' +>; + +export function buildLocationRoute(location: RoutableLocation): string { // coordinate-only locations (GPS) have no real geocoding id if (location.feature_code === 'COORD' || !location.id) { return `${location.latitude.toFixed(4)}N${location.longitude.toFixed(4)}E`; diff --git a/src/routes/legal/imprint/+page.svelte b/src/routes/legal/imprint/+page.svelte index e586a94..2d66e78 100644 --- a/src/routes/legal/imprint/+page.svelte +++ b/src/routes/legal/imprint/+page.svelte @@ -1,33 +1,11 @@ - - -

Service provider

-
- Vincent van der Wal
- Tschalungasse 2
- CH-6442 Gersau
- Switzerland -
-

- Email: drizzli@vincentvanderwal.nl -

-

Drizz.li is operated as a personal, independent project.

- -

Disclaimer

-

- Weather data shown on this site is provided for general information only. Forecasts are - inherently uncertain; do not rely on them where life, health or property is at stake - consult - official warnings from your national weather service instead. -

-

- Weather and geodata are retrieved from - Open-Meteo, which is not - affiliated with this site. External links are provided in good faith; their content is the - responsibility of the respective operators. -

-
+ diff --git a/src/routes/legal/imprint/content/de.svelte b/src/routes/legal/imprint/content/de.svelte new file mode 100644 index 0000000..fede000 --- /dev/null +++ b/src/routes/legal/imprint/content/de.svelte @@ -0,0 +1,32 @@ + + + +

Diensteanbieter

+
+ Vincent van der Wal
+ Tschalungasse 2
+ CH-6442 Gersau
+ Switzerland +
+

+ E-Mail: drizzli@vincentvanderwal.nl +

+

Drizz.li wird als persönliches, unabhängiges Projekt betrieben.

+ +

Haftungsausschluss

+

+ Die hier gezeigten Wetterdaten dienen ausschliesslich der allgemeinen Information. Vorhersagen + sind naturgemäss unsicher; verlassen Sie sich nicht auf sie, wenn Leben, Gesundheit oder + Sachwerte auf dem Spiel stehen - massgeblich sind die amtlichen Warnungen Ihres nationalen + Wetterdienstes. +

+

+ Wetter- und Geodaten stammen von + Open-Meteo, das nicht mit + dieser Seite verbunden ist. Externe Links werden nach bestem Wissen gesetzt; für deren Inhalte + sind die jeweiligen Betreiber verantwortlich. +

+

Massgeblich ist die englische Fassung dieses Impressums.

+
diff --git a/src/routes/legal/imprint/content/en.svelte b/src/routes/legal/imprint/content/en.svelte new file mode 100644 index 0000000..745980f --- /dev/null +++ b/src/routes/legal/imprint/content/en.svelte @@ -0,0 +1,30 @@ + + + +

Service provider

+
+ Vincent van der Wal
+ Tschalungasse 2
+ CH-6442 Gersau
+ Switzerland +
+

+ Email: drizzli@vincentvanderwal.nl +

+

Drizz.li is operated as a personal, independent project.

+ +

Disclaimer

+

+ Weather data shown on this site is provided for general information only. Forecasts are + inherently uncertain; do not rely on them where life, health or property is at stake - consult + official warnings from your national weather service instead. +

+

+ Weather and geodata are retrieved from + Open-Meteo, which is not + affiliated with this site. External links are provided in good faith; their content is the + responsibility of the respective operators. +

+
diff --git a/src/routes/legal/imprint/content/es.svelte b/src/routes/legal/imprint/content/es.svelte new file mode 100644 index 0000000..edab751 --- /dev/null +++ b/src/routes/legal/imprint/content/es.svelte @@ -0,0 +1,33 @@ + + + +

Prestador del servicio

+
+ Vincent van der Wal
+ Tschalungasse 2
+ CH-6442 Gersau
+ Switzerland +
+

+ Correo electrónico: drizzli@vincentvanderwal.nl +

+

Drizz.li se gestiona como un proyecto personal e independiente.

+ +

Descargo de responsabilidad

+

+ Los datos meteorológicos de este sitio son solo informativos. Los pronósticos son + intrínsecamente inciertos; no confíes en ellos cuando estén en juego la vida, la salud o los + bienes: consulta los avisos oficiales de tu servicio meteorológico nacional. +

+

+ Los datos meteorológicos y geográficos proceden de + Open-Meteo, que no está + afiliado a este sitio. Los enlaces externos se ofrecen de buena fe; su contenido es + responsabilidad de sus respectivos operadores. +

+

+ En caso de discrepancia prevalece la versión inglesa de este aviso legal. +

+
diff --git a/src/routes/legal/imprint/content/fr.svelte b/src/routes/legal/imprint/content/fr.svelte new file mode 100644 index 0000000..073a553 --- /dev/null +++ b/src/routes/legal/imprint/content/fr.svelte @@ -0,0 +1,33 @@ + + + +

Éditeur du service

+
+ Vincent van der Wal
+ Tschalungasse 2
+ CH-6442 Gersau
+ Switzerland +
+

+ E-mail: drizzli@vincentvanderwal.nl +

+

Drizz.li est exploité comme un projet personnel et indépendant.

+ +

Avertissement

+

+ Les données météo présentées ici sont fournies à titre d'information générale. Les prévisions + sont par nature incertaines ; ne vous y fiez pas lorsque des vies, la santé ou des biens sont en + jeu - consultez les alertes officielles de votre service météorologique national. +

+

+ Les données météo et géographiques proviennent d' + Open-Meteo, qui n'est pas + affilié à ce site. Les liens externes sont fournis de bonne foi ; leur contenu relève de la + responsabilité de leurs exploitants. +

+

+ En cas de divergence, la version anglaise de ces mentions fait foi. +

+
diff --git a/src/routes/legal/imprint/content/it.svelte b/src/routes/legal/imprint/content/it.svelte new file mode 100644 index 0000000..f41d89d --- /dev/null +++ b/src/routes/legal/imprint/content/it.svelte @@ -0,0 +1,33 @@ + + + +

Fornitore del servizio

+
+ Vincent van der Wal
+ Tschalungasse 2
+ CH-6442 Gersau
+ Switzerland +
+

+ E-mail: drizzli@vincentvanderwal.nl +

+

Drizz.li è gestito come progetto personale e indipendente.

+ +

Avvertenza

+

+ I dati meteo mostrati su questo sito hanno finalità puramente informative. Le previsioni sono + per natura incerte: non farvi affidamento quando sono in gioco la vita, la salute o i beni - + consulta gli avvisi ufficiali del tuo servizio meteorologico nazionale. +

+

+ I dati meteo e geografici provengono da + Open-Meteo, che non è + affiliato a questo sito. I link esterni sono forniti in buona fede; i contenuti sono + responsabilità dei rispettivi gestori. +

+

+ In caso di discrepanza prevale la versione inglese di queste note legali. +

+
diff --git a/src/routes/legal/privacy/+page.svelte b/src/routes/legal/privacy/+page.svelte index 9abe0f3..2d66e78 100644 --- a/src/routes/legal/privacy/+page.svelte +++ b/src/routes/legal/privacy/+page.svelte @@ -1,147 +1,11 @@ - - -

- Drizz.li is built to need as little of your data as possible: there are no user accounts, no - cookies, no advertising and no analytics or tracking scripts. This page explains what little - processing does happen - when you browse the site, and when you support the project with a - contribution. -

- -

1. Controller

-
- Vincent van der Wal
- Tschalungasse 2, CH-6442 Gersau, Switzerland
- Email: - drizzli@vincentvanderwal.nl -
-

- This policy covers the websites drizz.li (the weather app) and support.drizz.li (the supporter - signup page). -

- -

2. Browsing the site

-

Weather data requests

-

- Drizz.li is a static site: when you open a forecast, your browser fetches the weather data - directly from the open-data APIs of - Open-Meteo - (api.open-meteo.com, geocoding-api.open-meteo.com, archive-api.open-meteo.com, ensemble-api.open-meteo.com, - and map tiles from maps.open-meteo.com / map-tiles.open-meteo.com / map-assets.open-meteo.com). Like - any web request, this transmits your IP address and the requested location or search term to Open-Meteo. - We do not receive or store any of this. See - Open-Meteo's terms and privacy information. Legal basis: our legitimate interest in delivering the content you request (Art. 6(1)(f) - GDPR). -

-

Hosting

-

- The static site is served by [hosting provider, location]. The hosting infrastructure may keep - short-lived technical server logs (IP address, requested URL, timestamp) for security and - operations. Legal basis: legitimate interest in a secure, reliable service (Art. 6(1)(f) GDPR). -

-

Settings on your device (local storage)

-

- Your preferences - theme, measurement units, last searched location, and (for supporters) your - access key and its last verification result - are stored only in your browser's local storage. - They never leave your device except as described below for key verification, and you can clear - them at any time via your browser. -

- -

3. Supporting the project (contributions)

-

- Supporter contributions unlock the supporter extras, so legally they are a paid agreement, not a - pure gift - and this is what we process to handle them: -

-

Signup form

-
    -
  • - Email address (required) - to send you the transfer details and, after your contribution - arrives, your access key. -
  • -
  • Name (optional) - to help match your bank transfer.
  • -
  • - Currency and amount - shown based on your browser's locale/timezone, detected on - your device (no geolocation request is made). -
  • -
  • - IP address and technical anti-bot signals - kept with the signup request to prevent - abuse and spam of the form. -
  • -
  • - Payment reference (e.g. DRZ-XXXXXX) - generated per request to match your transfer - to your signup. -
  • -
-

- The transfer details (bank account and reference) are sent to you by email rather than shown on - the page. Legal bases: performance of the agreement (Art. 6(1)(b) GDPR) and, for the anti-abuse - measures, legitimate interest (Art. 6(1)(f) GDPR). -

-

Bank transfer

-

- Contributions are paid by ordinary bank transfer. Your bank and ours process the transfer data - under their own responsibility; on our bank statement we see the usual transfer details (your - name, account number, amount, reference). We use them only to match your contribution and are - required to retain accounting records under statutory bookkeeping and tax law (Art. 6(1)(c) - GDPR). -

-

Access key and verification

-

- After your transfer is matched (manually, usually within 24 hours), you receive an access key by - email. When you paste it into Drizz.li, your browser sends the key to our verification endpoint - (support.drizz.li) to check whether it is active; the response contains only the validity, tier - and expiry. The subscriber list (key, email, expiry) is stored on our server for as long as your - access is active. -

-

Email

-

- Transactional emails (transfer details, access key, renewals) are sent through our email - provider, Strato (Strato AG, Germany), acting as a processor. We send no newsletters or - marketing email. -

- -

4. Retention

-
    -
  • Signup requests that are never paid are deleted after 6 months at the latest.
  • -
  • - Supporter records (key, email, expiry) are kept while your access is active and deleted within - 12 months after it expires, unless you ask us to delete them sooner. -
  • -
  • - Accounting records (bank statements showing your transfer) are kept for the statutory - retention period (up to 10 years, depending on jurisdiction). -
  • -
- -

5. Recipients

-

- We do not sell or share personal data with third parties for their own purposes. Recipients are - limited to: Open-Meteo (weather requests made directly by your browser), our hosting provider, - our email provider (Strato), and the banks involved in your transfer. -

- -

6. Your rights

-

- Under the GDPR you have the right to access, rectification, erasure, restriction of processing, - data portability, and to object to processing based on legitimate interest. Where processing is - based on consent, you may withdraw it at any time. To exercise any of these rights, email - drizzli@vincentvanderwal.nl. You also have the - right to lodge a complaint with a supervisory authority, in particular in the EU member state of - your residence, or with [competent supervisory authority of the controller]. -

-

- For visitors from Switzerland: the corresponding rights under the Swiss Federal Act on Data - Protection (FADP) apply equivalently. -

- -

7. Changes

-

- We may update this policy when the service changes; the date above reflects the latest revision. -

-
+ diff --git a/src/routes/legal/privacy/content/de.svelte b/src/routes/legal/privacy/content/de.svelte new file mode 100644 index 0000000..8c12146 --- /dev/null +++ b/src/routes/legal/privacy/content/de.svelte @@ -0,0 +1,150 @@ + + + +

+ Drizz.li ist so gebaut, dass möglichst wenige Ihrer Daten nötig sind: keine Benutzerkonten, + keine Cookies, keine Werbung, keine Analyse- oder Tracking-Skripte. Diese Seite erklärt, welche + wenige Verarbeitung dennoch stattfindet - beim Besuch der Seite und wenn Sie das Projekt mit + einem Beitrag unterstützen. +

+ +

1. Verantwortlicher

+
+ Vincent van der Wal
+ Tschalungasse 2, CH-6442 Gersau, Switzerland
+ E-Mail: + drizzli@vincentvanderwal.nl +
+

+ Diese Erklärung gilt für die Websites drizz.li (die Wetter-App) und support.drizz.li (die + Unterstützer-Anmeldung). +

+ +

2. Besuch der Website

+

Abruf der Wetterdaten

+

+ Drizz.li ist eine statische Seite: Wenn Sie eine Vorhersage öffnen, ruft Ihr Browser die + Wetterdaten direkt bei den Open-Data-Schnittstellen von + Open-Meteo. Wie bei jeder + Webanfrage werden dabei Ihre IP-Adresse und der gewünschte Ort bzw. Suchbegriff an Open-Meteo + übertragen. Wir erhalten und speichern davon nichts. Siehe + Nutzungs- und Datenschutzhinweise von Open-Meteo. Rechtsgrundlage: unser berechtigtes Interesse an der Auslieferung der angeforderten Inhalte + (Art. 6 Abs. 1 lit. f DSGVO). +

+

Hosting

+

+ Die statische Seite wird von [Hosting-Anbieter, Standort] ausgeliefert. Die + Hosting-Infrastruktur kann kurzlebige technische Server-Logs (IP-Adresse, angefragte URL, + Zeitstempel) zu Sicherheits- und Betriebszwecken speichern. Rechtsgrundlage: berechtigtes + Interesse an einem sicheren, zuverlässigen Dienst (Art. 6 Abs. 1 lit. f DSGVO). +

+

Einstellungen auf Ihrem Gerät (Local Storage)

+

+ Ihre Einstellungen - Design, Masseinheiten, zuletzt gesuchter Ort und (für Unterstützer) Ihr + Zugangsschlüssel samt letztem Prüfergebnis - werden ausschliesslich im Local Storage Ihres + Browsers gespeichert. Sie verlassen Ihr Gerät nicht, ausser wie unten für die Schlüsselprüfung + beschrieben, und Sie können sie jederzeit über Ihren Browser löschen. +

+ +

3. Das Projekt unterstützen (Beiträge)

+

+ Unterstützerbeiträge schalten die Extras frei, rechtlich sind sie daher eine entgeltliche + Vereinbarung und keine reine Schenkung - das verarbeiten wir zur Abwicklung: +

+

Anmeldeformular

+
    +
  • + E-Mail-Adresse (erforderlich) - um Ihnen die Überweisungsdaten und nach Eingang + Ihres Beitrags den Zugangsschlüssel zu senden. +
  • +
  • Name (optional) - zur leichteren Zuordnung Ihrer Überweisung.
  • +
  • + Währung und Betrag - anhand der Spracheinstellung/Zeitzone Ihres Browsers auf Ihrem + Gerät ermittelt (keine Standortabfrage). +
  • +
  • + IP-Adresse und technische Anti-Bot-Signale - zusammen mit der Anfrage gespeichert, + um Missbrauch und Spam zu verhindern. +
  • +
  • + Zahlungsreferenz (z. B. DRZ-XXXXXX) - je Anfrage erzeugt, um Ihre Überweisung Ihrer + Anmeldung zuzuordnen. +
  • +
+

+ Die Überweisungsdaten (Bankverbindung und Referenz) senden wir Ihnen per E-Mail statt sie auf + der Seite anzuzeigen. Rechtsgrundlagen: Erfüllung der Vereinbarung (Art. 6 Abs. 1 lit. b DSGVO) + und für die Missbrauchsabwehr berechtigtes Interesse (Art. 6 Abs. 1 lit. f DSGVO). +

+

Banküberweisung

+

+ Beiträge werden per gewöhnlicher Banküberweisung gezahlt. Ihre und unsere Bank verarbeiten die + Überweisungsdaten in eigener Verantwortung; auf unserem Kontoauszug sehen wir die üblichen + Angaben (Name, Kontonummer, Betrag, Referenz). Wir nutzen sie nur zur Zuordnung Ihres Beitrags + und sind nach handels- und steuerrechtlichen Vorschriften zur Aufbewahrung verpflichtet (Art. 6 + Abs. 1 lit. c DSGVO). +

+

Zugangsschlüssel und Prüfung

+

+ Nach Zuordnung Ihrer Überweisung (manuell, meist innerhalb von 24 Stunden) erhalten Sie den + Zugangsschlüssel per E-Mail. Wenn Sie ihn in Drizz.li einfügen, sendet Ihr Browser den Schlüssel + an unseren Prüfendpunkt (support.drizz.li), um seine Gültigkeit zu prüfen; die Antwort enthält + nur Gültigkeit, Stufe und Ablauf. Die Abonnentenliste (Schlüssel, E-Mail, Ablauf) liegt auf + unserem Server, solange Ihr Zugang aktiv ist. +

+

E-Mail

+

+ Transaktions-E-Mails (Überweisungsdaten, Zugangsschlüssel, Verlängerungen) versenden wir über + unseren E-Mail-Anbieter Strato (Strato AG, Deutschland) als Auftragsverarbeiter. Newsletter oder + Werbe-E-Mails versenden wir nicht. +

+ +

4. Speicherdauer

+
    +
  • Nicht bezahlte Anmeldungen werden spätestens nach 6 Monaten gelöscht.
  • +
  • + Unterstützerdaten (Schlüssel, E-Mail, Ablauf) werden für die Dauer Ihres Zugangs aufbewahrt + und innerhalb von 12 Monaten nach dessen Ablauf gelöscht, sofern Sie keine frühere Löschung + wünschen. +
  • +
  • + Buchhaltungsunterlagen (Kontoauszüge mit Ihrer Überweisung) werden für die gesetzliche + Aufbewahrungsfrist gespeichert (je nach Rechtsordnung bis zu 10 Jahre). +
  • +
+ +

5. Empfänger

+

+ Wir verkaufen personenbezogene Daten nicht und geben sie nicht für eigene Zwecke Dritter weiter. + Empfänger sind ausschliesslich: Open-Meteo (Wetterabrufe direkt durch Ihren Browser), unser + Hosting-Anbieter, unser E-Mail-Anbieter (Strato) und die an der Überweisung beteiligten Banken. +

+ +

6. Ihre Rechte

+

+ Nach der DSGVO haben Sie das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der + Verarbeitung, Datenübertragbarkeit sowie das Recht, einer auf berechtigtem Interesse beruhenden + Verarbeitung zu widersprechen. Beruht eine Verarbeitung auf Einwilligung, können Sie diese + jederzeit widerrufen. Zur Ausübung schreiben Sie an + drizzli@vincentvanderwal.nl. Ausserdem haben + Sie das Recht, sich bei einer Aufsichtsbehörde zu beschweren, insbesondere im EU-Mitgliedstaat + Ihres Wohnsitzes, oder bei [zuständige Aufsichtsbehörde des Verantwortlichen]. +

+

+ Für Besucherinnen und Besucher aus der Schweiz gelten die entsprechenden Rechte nach dem + Bundesgesetz über den Datenschutz (DSG) sinngemäss. +

+ +

7. Änderungen

+

+ Wir aktualisieren diese Erklärung, wenn sich der Dienst ändert; das Datum oben nennt die letzte + Fassung. +

+

+ Massgeblich ist die englische Fassung dieser Datenschutzerklärung. +

+
diff --git a/src/routes/legal/privacy/content/en.svelte b/src/routes/legal/privacy/content/en.svelte new file mode 100644 index 0000000..c2b3828 --- /dev/null +++ b/src/routes/legal/privacy/content/en.svelte @@ -0,0 +1,145 @@ + + + +

+ Drizz.li is built to need as little of your data as possible: there are no user accounts, no + cookies, no advertising and no analytics or tracking scripts. This page explains what little + processing does happen - when you browse the site, and when you support the project with a + contribution. +

+ +

1. Controller

+
+ Vincent van der Wal
+ Tschalungasse 2, CH-6442 Gersau, Switzerland
+ Email: + drizzli@vincentvanderwal.nl +
+

+ This policy covers the websites drizz.li (the weather app) and support.drizz.li (the supporter + signup page). +

+ +

2. Browsing the site

+

Weather data requests

+

+ Drizz.li is a static site: when you open a forecast, your browser fetches the weather data + directly from the open-data APIs of + Open-Meteo + (api.open-meteo.com, geocoding-api.open-meteo.com, archive-api.open-meteo.com, ensemble-api.open-meteo.com, + and map tiles from maps.open-meteo.com / map-tiles.open-meteo.com / map-assets.open-meteo.com). Like + any web request, this transmits your IP address and the requested location or search term to Open-Meteo. + We do not receive or store any of this. See + Open-Meteo's terms and privacy information. Legal basis: our legitimate interest in delivering the content you request (Art. 6(1)(f) + GDPR). +

+

Hosting

+

+ The static site is served by [hosting provider, location]. The hosting infrastructure may keep + short-lived technical server logs (IP address, requested URL, timestamp) for security and + operations. Legal basis: legitimate interest in a secure, reliable service (Art. 6(1)(f) GDPR). +

+

Settings on your device (local storage)

+

+ Your preferences - theme, measurement units, last searched location, and (for supporters) your + access key and its last verification result - are stored only in your browser's local storage. + They never leave your device except as described below for key verification, and you can clear + them at any time via your browser. +

+ +

3. Supporting the project (contributions)

+

+ Supporter contributions unlock the supporter extras, so legally they are a paid agreement, not a + pure gift - and this is what we process to handle them: +

+

Signup form

+
    +
  • + Email address (required) - to send you the transfer details and, after your contribution + arrives, your access key. +
  • +
  • Name (optional) - to help match your bank transfer.
  • +
  • + Currency and amount - shown based on your browser's locale/timezone, detected on + your device (no geolocation request is made). +
  • +
  • + IP address and technical anti-bot signals - kept with the signup request to prevent + abuse and spam of the form. +
  • +
  • + Payment reference (e.g. DRZ-XXXXXX) - generated per request to match your transfer + to your signup. +
  • +
+

+ The transfer details (bank account and reference) are sent to you by email rather than shown on + the page. Legal bases: performance of the agreement (Art. 6(1)(b) GDPR) and, for the anti-abuse + measures, legitimate interest (Art. 6(1)(f) GDPR). +

+

Bank transfer

+

+ Contributions are paid by ordinary bank transfer. Your bank and ours process the transfer data + under their own responsibility; on our bank statement we see the usual transfer details (your + name, account number, amount, reference). We use them only to match your contribution and are + required to retain accounting records under statutory bookkeeping and tax law (Art. 6(1)(c) + GDPR). +

+

Access key and verification

+

+ After your transfer is matched (manually, usually within 24 hours), you receive an access key by + email. When you paste it into Drizz.li, your browser sends the key to our verification endpoint + (support.drizz.li) to check whether it is active; the response contains only the validity, tier + and expiry. The subscriber list (key, email, expiry) is stored on our server for as long as your + access is active. +

+

Email

+

+ Transactional emails (transfer details, access key, renewals) are sent through our email + provider, Strato (Strato AG, Germany), acting as a processor. We send no newsletters or + marketing email. +

+ +

4. Retention

+
    +
  • Signup requests that are never paid are deleted after 6 months at the latest.
  • +
  • + Supporter records (key, email, expiry) are kept while your access is active and deleted within + 12 months after it expires, unless you ask us to delete them sooner. +
  • +
  • + Accounting records (bank statements showing your transfer) are kept for the statutory + retention period (up to 10 years, depending on jurisdiction). +
  • +
+ +

5. Recipients

+

+ We do not sell or share personal data with third parties for their own purposes. Recipients are + limited to: Open-Meteo (weather requests made directly by your browser), our hosting provider, + our email provider (Strato), and the banks involved in your transfer. +

+ +

6. Your rights

+

+ Under the GDPR you have the right to access, rectification, erasure, restriction of processing, + data portability, and to object to processing based on legitimate interest. Where processing is + based on consent, you may withdraw it at any time. To exercise any of these rights, email + drizzli@vincentvanderwal.nl. You also have the + right to lodge a complaint with a supervisory authority, in particular in the EU member state of + your residence, or with [competent supervisory authority of the controller]. +

+

+ For visitors from Switzerland: the corresponding rights under the Swiss Federal Act on Data + Protection (FADP) apply equivalently. +

+ +

7. Changes

+

+ We may update this policy when the service changes; the date above reflects the latest revision. +

+
diff --git a/src/routes/legal/privacy/content/es.svelte b/src/routes/legal/privacy/content/es.svelte new file mode 100644 index 0000000..fffbaf9 --- /dev/null +++ b/src/routes/legal/privacy/content/es.svelte @@ -0,0 +1,149 @@ + + + +

+ Drizz.li está hecho para necesitar los mínimos datos posibles: sin cuentas, sin cookies, sin + publicidad y sin scripts de analítica ni de seguimiento. Esta página explica el poco tratamiento + que sí ocurre: al navegar por el sitio y al apoyar el proyecto con una aportación. +

+ +

1. Responsable

+
+ Vincent van der Wal
+ Tschalungasse 2, CH-6442 Gersau, Switzerland
+ Correo electrónico: + drizzli@vincentvanderwal.nl +
+

+ Esta política cubre los sitios drizz.li (la app meteorológica) y support.drizz.li (la página de + registro de colaboradores). +

+ +

2. Navegación por el sitio

+

Peticiones de datos meteorológicos

+

+ Drizz.li es un sitio estático: cuando abres un pronóstico, tu navegador obtiene los datos + directamente de las API abiertas de + Open-Meteo. Como en + cualquier petición web, esto transmite tu dirección IP y la ubicación o término buscado a + Open-Meteo. Nosotros no recibimos ni almacenamos nada de eso. Consulta + las condiciones e información de privacidad de Open-Meteo. Base jurídica: nuestro interés legítimo en entregar el contenido que solicitas (art. 6.1.f + del RGPD). +

+

Alojamiento

+

+ El sitio estático lo sirve [proveedor de alojamiento, ubicación]. La infraestructura puede + conservar registros técnicos de corta duración (dirección IP, URL solicitada, marca de tiempo) + por seguridad y operación. Base jurídica: interés legítimo en un servicio seguro y fiable (art. + 6.1.f del RGPD). +

+

Ajustes en tu dispositivo (almacenamiento local)

+

+ Tus preferencias - tema, unidades, última ubicación buscada y (para colaboradores) tu clave de + acceso y su última verificación - se guardan únicamente en el almacenamiento local de tu + navegador. No salen de tu dispositivo salvo como se describe abajo para verificar la clave, y + puedes borrarlas cuando quieras. +

+ +

3. Apoyar el proyecto (aportaciones)

+

+ Las aportaciones desbloquean los extras, así que legalmente son un acuerdo remunerado y no una + donación pura; esto es lo que tratamos para gestionarlas: +

+

Formulario de registro

+
    +
  • + Dirección de correo (obligatoria) - para enviarte los datos de la transferencia + y, cuando llegue tu aportación, tu clave de acceso. +
  • +
  • Nombre (opcional) - para ayudar a casar tu transferencia.
  • +
  • + Moneda e importe - determinados por la configuración regional/zona horaria de tu + navegador, en tu dispositivo (no se solicita geolocalización). +
  • +
  • + Dirección IP y señales técnicas anti-bots - guardadas con la solicitud para evitar + abusos y spam. +
  • +
  • + Referencia de pago (p. ej. DRZ-XXXXXX) - generada por solicitud para casar tu transferencia + con tu registro. +
  • +
+

+ Los datos de la transferencia (cuenta y referencia) se te envían por correo en lugar de + mostrarse en la página. Bases jurídicas: ejecución del contrato (art. 6.1.b del RGPD) y, para + las medidas antiabuso, interés legítimo (art. 6.1.f del RGPD). +

+

Transferencia bancaria

+

+ Las aportaciones se pagan por transferencia bancaria ordinaria. Tu banco y el nuestro tratan los + datos bajo su propia responsabilidad; en nuestro extracto vemos los datos habituales (nombre, + número de cuenta, importe, referencia). Solo los usamos para casar tu aportación y debemos + conservar los registros contables por obligación legal (art. 6.1.c del RGPD). +

+

Clave de acceso y verificación

+

+ Una vez casada tu transferencia (manualmente, normalmente en 24 horas), recibes una clave de + acceso por correo. Al pegarla en Drizz.li, tu navegador envía la clave a nuestro punto de + verificación (support.drizz.li) para comprobar si está activa; la respuesta contiene solo + validez, nivel y caducidad. La lista de suscriptores (clave, correo, caducidad) se guarda en + nuestro servidor mientras tu acceso esté activo. +

+

Correo electrónico

+

+ Los correos transaccionales (datos de transferencia, clave de acceso, renovaciones) se envían a + través de nuestro proveedor Strato (Strato AG, Alemania), que actúa como encargado del + tratamiento. No enviamos boletines ni correo comercial. +

+ +

4. Conservación

+
    +
  • Las solicitudes de registro que nunca se pagan se eliminan como máximo a los 6 meses.
  • +
  • + Los registros de colaborador (clave, correo, caducidad) se conservan mientras tu acceso esté + activo y se eliminan en los 12 meses siguientes a su expiración, salvo que pidas borrarlos + antes. +
  • +
  • + Los registros contables (extractos que muestran tu transferencia) se conservan durante el + plazo legal (hasta 10 años según la jurisdicción). +
  • +
+ +

5. Destinatarios

+

+ No vendemos ni compartimos datos personales con terceros para sus propios fines. Los + destinatarios se limitan a: Open-Meteo (peticiones meteorológicas hechas directamente por tu + navegador), nuestro proveedor de alojamiento, nuestro proveedor de correo (Strato) y los bancos + implicados en tu transferencia. +

+ +

6. Tus derechos

+

+ Conforme al RGPD tienes derecho de acceso, rectificación, supresión, limitación del tratamiento, + portabilidad y oposición al tratamiento basado en el interés legítimo. Cuando el tratamiento se + base en el consentimiento, puedes retirarlo en cualquier momento. Para ejercer estos derechos, + escribe a + drizzli@vincentvanderwal.nl. También tienes + derecho a presentar una reclamación ante una autoridad de control, en particular en el Estado + miembro de tu residencia, o ante [autoridad de control competente]. +

+

+ Para visitantes desde Suiza, se aplican de forma equivalente los derechos correspondientes de la + Ley Federal de Protección de Datos (LPD). +

+ +

7. Cambios

+

+ Podemos actualizar esta política cuando cambie el servicio; la fecha de arriba refleja la última + revisión. +

+

+ En caso de discrepancia prevalece la versión inglesa de esta política. +

+
diff --git a/src/routes/legal/privacy/content/fr.svelte b/src/routes/legal/privacy/content/fr.svelte new file mode 100644 index 0000000..8098dab --- /dev/null +++ b/src/routes/legal/privacy/content/fr.svelte @@ -0,0 +1,151 @@ + + + +

+ Drizz.li est conçu pour avoir besoin du minimum de vos données : aucun compte, aucun cookie, + aucune publicité, aucun script d'analyse ou de suivi. Cette page explique le peu de traitement + qui a lieu - lorsque vous consultez le site et lorsque vous soutenez le projet par une + contribution. +

+ +

1. Responsable du traitement

+
+ Vincent van der Wal
+ Tschalungasse 2, CH-6442 Gersau, Switzerland
+ E-mail: + drizzli@vincentvanderwal.nl +
+

+ Cette politique couvre les sites drizz.li (l'application météo) et support.drizz.li (la page + d'inscription contributeur). +

+ +

2. Navigation sur le site

+

Requêtes de données météo

+

+ Drizz.li est un site statique : lorsque vous ouvrez une prévision, votre navigateur récupère les + données directement auprès des API ouvertes d' + Open-Meteo. Comme pour toute + requête web, cela transmet votre adresse IP et le lieu ou le terme recherché à Open-Meteo. Nous + n'en recevons ni n'en conservons rien. Voir + les conditions et informations de confidentialité d'Open-Meteo. Base légale : notre intérêt légitime à fournir le contenu demandé (art. 6, par. 1, point f) + du RGPD). +

+

Hébergement

+

+ Le site statique est servi par [hébergeur, localisation]. L'infrastructure d'hébergement peut + conserver de brefs journaux techniques (adresse IP, URL demandée, horodatage) à des fins de + sécurité et d'exploitation. Base légale : intérêt légitime à un service sûr et fiable (art. 6, + par. 1, point f) du RGPD). +

+

Réglages sur votre appareil (stockage local)

+

+ Vos préférences - thème, unités, dernier lieu recherché et (pour les contributeurs) votre clé + d'accès et son dernier résultat de vérification - sont stockées uniquement dans le stockage + local de votre navigateur. Elles ne quittent pas votre appareil, sauf comme décrit ci-dessous + pour la vérification de la clé, et vous pouvez les effacer à tout moment. +

+ +

3. Soutenir le projet (contributions)

+

+ Les contributions débloquent les bonus : juridiquement il s'agit donc d'un accord payant et non + d'un simple don - voici ce que nous traitons pour les gérer : +

+

Formulaire d'inscription

+
    +
  • + Adresse e-mail (obligatoire) - pour vous envoyer les coordonnées bancaires puis, + à réception de votre contribution, votre clé d'accès. +
  • +
  • Nom (facultatif) - pour faciliter le rapprochement de votre virement.
  • +
  • + Devise et montant - déterminés d'après la langue/le fuseau horaire de votre navigateur, + sur votre appareil (aucune demande de géolocalisation). +
  • +
  • + Adresse IP et signaux anti-robots techniques - conservés avec la demande pour prévenir + les abus et le spam. +
  • +
  • + Référence de paiement (p. ex. DRZ-XXXXXX) - générée par demande pour rapprocher + votre virement de votre inscription. +
  • +
+

+ Les coordonnées bancaires (compte et référence) vous sont envoyées par e-mail plutôt + qu'affichées sur la page. Bases légales : exécution du contrat (art. 6, par. 1, point b) du + RGPD) et, pour les mesures anti-abus, intérêt légitime (art. 6, par. 1, point f) du RGPD). +

+

Virement bancaire

+

+ Les contributions sont réglées par virement bancaire ordinaire. Votre banque et la nôtre + traitent les données du virement sous leur propre responsabilité ; sur notre relevé figurent les + informations habituelles (nom, numéro de compte, montant, référence). Nous ne les utilisons que + pour rapprocher votre contribution et devons conserver les pièces comptables au titre des + obligations légales (art. 6, par. 1, point c) du RGPD). +

+

Clé d'accès et vérification

+

+ Une fois votre virement rapproché (manuellement, en général sous 24 heures), vous recevez une + clé d'accès par e-mail. Lorsque vous la collez dans Drizz.li, votre navigateur envoie la clé à + notre point de vérification (support.drizz.li) pour savoir si elle est active ; la réponse ne + contient que la validité, le niveau et l'échéance. La liste des abonnés (clé, e-mail, échéance) + est conservée sur notre serveur tant que votre accès est actif. +

+

E-mail

+

+ Les e-mails transactionnels (coordonnées bancaires, clé d'accès, renouvellements) sont envoyés + via notre prestataire Strato (Strato AG, Allemagne), agissant comme sous-traitant. Nous + n'envoyons ni newsletter ni e-mail marketing. +

+ +

4. Conservation

+
    +
  • Les demandes d'inscription jamais payées sont supprimées au plus tard après 6 mois.
  • +
  • + Les données contributeur (clé, e-mail, échéance) sont conservées tant que votre accès est + actif et supprimées dans les 12 mois suivant son expiration, sauf demande de suppression + anticipée. +
  • +
  • + Les pièces comptables (relevés bancaires mentionnant votre virement) sont conservées pendant + la durée légale (jusqu'à 10 ans selon la juridiction). +
  • +
+ +

5. Destinataires

+

+ Nous ne vendons ni ne partageons de données personnelles avec des tiers pour leurs propres + finalités. Les destinataires se limitent à : Open-Meteo (requêtes météo effectuées directement + par votre navigateur), notre hébergeur, notre prestataire e-mail (Strato) et les banques + impliquées dans votre virement. +

+ +

6. Vos droits

+

+ En vertu du RGPD, vous disposez d'un droit d'accès, de rectification, d'effacement, de + limitation du traitement, de portabilité, et d'opposition au traitement fondé sur l'intérêt + légitime. Lorsque le traitement repose sur le consentement, vous pouvez le retirer à tout + moment. Pour exercer ces droits, écrivez à + drizzli@vincentvanderwal.nl. Vous avez + également le droit d'introduire une réclamation auprès d'une autorité de contrôle, notamment + dans l'État membre de votre résidence, ou auprès de [autorité de contrôle compétente]. +

+

+ Pour les visiteurs suisses, les droits correspondants de la loi fédérale sur la protection des + données (LPD) s'appliquent de manière équivalente. +

+ +

7. Modifications

+

+ Nous pouvons mettre à jour cette politique lorsque le service évolue ; la date ci-dessus indique + la dernière révision. +

+

+ En cas de divergence, la version anglaise de cette politique fait foi. +

+
diff --git a/src/routes/legal/privacy/content/it.svelte b/src/routes/legal/privacy/content/it.svelte new file mode 100644 index 0000000..3c8214f --- /dev/null +++ b/src/routes/legal/privacy/content/it.svelte @@ -0,0 +1,149 @@ + + + +

+ Drizz.li è costruito per aver bisogno del minimo dei tuoi dati: nessun account, nessun cookie, + nessuna pubblicità, nessuno script di analisi o tracciamento. Questa pagina spiega il poco + trattamento che avviene comunque: quando navighi sul sito e quando sostieni il progetto con un + contributo. +

+ +

1. Titolare del trattamento

+
+ Vincent van der Wal
+ Tschalungasse 2, CH-6442 Gersau, Switzerland
+ E-mail: + drizzli@vincentvanderwal.nl +
+

+ Questa informativa copre i siti drizz.li (l'app meteo) e support.drizz.li (la pagina di + iscrizione per i sostenitori). +

+ +

2. Navigazione sul sito

+

Richieste di dati meteo

+

+ Drizz.li è un sito statico: quando apri una previsione, il tuo browser scarica i dati + direttamente dalle API aperte di + Open-Meteo. Come per + qualsiasi richiesta web, questo trasmette il tuo indirizzo IP e la località o il termine cercato + a Open-Meteo. Noi non riceviamo né conserviamo nulla di tutto ciò. Vedi + le condizioni e le informazioni sulla privacy di Open-Meteo. Base giuridica: il nostro legittimo interesse a fornire il contenuto richiesto (art. 6, par. + 1, lett. f del GDPR). +

+

Hosting

+

+ Il sito statico è servito da [fornitore di hosting, sede]. L'infrastruttura può conservare brevi + log tecnici del server (indirizzo IP, URL richiesto, data e ora) per sicurezza e gestione. Base + giuridica: legittimo interesse a un servizio sicuro e affidabile (art. 6, par. 1, lett. f del + GDPR). +

+

Impostazioni sul tuo dispositivo (archiviazione locale)

+

+ Le tue preferenze - tema, unità di misura, ultima località cercata e (per i sostenitori) la + chiave di accesso e l'ultimo esito della verifica - sono salvate solo nell'archiviazione locale + del browser. Non lasciano il tuo dispositivo, salvo quanto descritto sotto per la verifica della + chiave, e puoi cancellarle quando vuoi. +

+ +

3. Sostenere il progetto (contributi)

+

+ I contributi sbloccano gli extra, quindi giuridicamente sono un accordo a pagamento e non una + pura donazione: ecco cosa trattiamo per gestirli: +

+

Modulo di iscrizione

+
    +
  • + Indirizzo e-mail (obbligatorio) - per inviarti i dati del bonifico e, all'arrivo + del contributo, la chiave di accesso. +
  • +
  • Nome (facoltativo) - per facilitare l'abbinamento del bonifico.
  • +
  • + Valuta e importo - determinati dalle impostazioni locali/fuso orario del browser, + sul tuo dispositivo (nessuna richiesta di geolocalizzazione). +
  • +
  • + Indirizzo IP e segnali tecnici anti-bot - conservati con la richiesta per prevenire + abusi e spam. +
  • +
  • + Riferimento di pagamento (es. DRZ-XXXXXX) - generato per ogni richiesta per abbinare + il bonifico all'iscrizione. +
  • +
+

+ I dati del bonifico (conto e riferimento) ti vengono inviati via e-mail anziché mostrati sulla + pagina. Basi giuridiche: esecuzione del contratto (art. 6, par. 1, lett. b del GDPR) e, per le + misure anti-abuso, legittimo interesse (art. 6, par. 1, lett. f del GDPR). +

+

Bonifico bancario

+

+ I contributi si pagano con un normale bonifico. La tua banca e la nostra trattano i dati del + bonifico sotto la propria responsabilità; sul nostro estratto conto vediamo i dati consueti + (nome, numero di conto, importo, riferimento). Li usiamo solo per abbinare il contributo e siamo + tenuti a conservare le scritture contabili per obbligo di legge (art. 6, par. 1, lett. c del + GDPR). +

+

Chiave di accesso e verifica

+

+ Dopo l'abbinamento del bonifico (manuale, di solito entro 24 ore) ricevi una chiave di accesso + via e-mail. Quando la incolli in Drizz.li, il browser invia la chiave al nostro endpoint di + verifica (support.drizz.li) per controllare se è attiva; la risposta contiene solo validità, + livello e scadenza. L'elenco dei sostenitori (chiave, e-mail, scadenza) resta sul nostro server + finché il tuo accesso è attivo. +

+

E-mail

+

+ Le e-mail transazionali (dati del bonifico, chiave di accesso, rinnovi) sono inviate tramite il + nostro fornitore Strato (Strato AG, Germania), che agisce come responsabile del trattamento. Non + inviamo newsletter né e-mail di marketing. +

+ +

4. Conservazione

+
    +
  • Le richieste di iscrizione mai pagate sono cancellate al più tardi dopo 6 mesi.
  • +
  • + I dati dei sostenitori (chiave, e-mail, scadenza) sono conservati finché l'accesso è attivo e + cancellati entro 12 mesi dalla scadenza, salvo richiesta di cancellazione anticipata. +
  • +
  • + Le scritture contabili (estratti conto con il tuo bonifico) sono conservate per il periodo di + legge (fino a 10 anni a seconda della giurisdizione). +
  • +
+ +

5. Destinatari

+

+ Non vendiamo né condividiamo dati personali con terzi per finalità proprie. I destinatari si + limitano a: Open-Meteo (richieste meteo fatte direttamente dal tuo browser), il nostro fornitore + di hosting, il nostro fornitore e-mail (Strato) e le banche coinvolte nel bonifico. +

+ +

6. I tuoi diritti

+

+ In base al GDPR hai diritto di accesso, rettifica, cancellazione, limitazione del trattamento, + portabilità dei dati e opposizione al trattamento fondato sul legittimo interesse. Se il + trattamento si basa sul consenso, puoi revocarlo in qualsiasi momento. Per esercitare questi + diritti scrivi a + drizzli@vincentvanderwal.nl. Hai inoltre il + diritto di proporre reclamo a un'autorità di controllo, in particolare nello Stato membro di + residenza, o a [autorità di controllo competente]. +

+

+ Per i visitatori dalla Svizzera valgono in modo equivalente i corrispondenti diritti previsti + dalla Legge federale sulla protezione dei dati (LPD). +

+ +

7. Modifiche

+

+ Possiamo aggiornare questa informativa quando il servizio cambia; la data in alto indica + l'ultima revisione. +

+

+ In caso di discrepanza prevale la versione inglese di questa informativa. +

+
diff --git a/src/routes/legal/terms/+page.svelte b/src/routes/legal/terms/+page.svelte index 7acde49..2d66e78 100644 --- a/src/routes/legal/terms/+page.svelte +++ b/src/routes/legal/terms/+page.svelte @@ -1,82 +1,11 @@ - - -

- Drizz.li is free to use. These terms cover the optional supporter contribution: a small payment - that helps keep the project running and, as a thank-you, unlocks the supporter extras. Although - we call it a contribution, features are unlocked in return. -

- -

1. What you get

-

- A supporter contribution unlocks the supporter extras for the paid period: currently historical - weather with climate-normal comparisons, plus new supporter features as they land. The free - parts of Drizz.li stay free for everyone. -

- -

2. How it works

-
    -
  • - You request the transfer details on the signup page; we email them to you together with a - personal payment reference. -
  • -
  • - You transfer the amount (from €3 / $3 / CHF 3 per month) by ordinary bank transfer, including - the reference. -
  • -
  • - Transfers are matched manually. Once your contribution arrives - usually within 24 hours, at - most a few days - you receive a personal access key by email. -
  • -
  • - You paste the key into Drizz.li once; it is stored on your device and verified automatically. -
  • -
- -

3. No auto-renewal

-

- Contributions are one-off and prepaid: nothing renews automatically and we never charge you. - When your period ends, the extras simply lock again; contributing again with the same email - extends your existing key. -

- -

4. Withdrawal and refunds

-

- If you are a consumer in the EU/EEA, you have a statutory 14-day right of withdrawal. Beyond - that, we keep it simple: if you are unhappy for any reason within 14 days of receiving your - access key, email - drizzli@vincentvanderwal.nl and we will refund your - contribution in full. -

- -

5. Fair use

-

- The access key is personal. Please don't publish or share it; keys that are clearly abused (e.g. - shared publicly) may be revoked. If your key is revoked without cause, you are entitled to a - pro-rata refund. -

- -

6. Service and availability

-

- Drizz.li is a personal open-source project, provided as-is. We work to keep it available and - accurate, but we cannot guarantee uninterrupted availability or the correctness of forecasts - - weather data is informational only (see the disclaimer in the imprint). If the supporter extras - become permanently unavailable during a period you paid for, we will refund the remaining period - on request. -

- -

7. Data

-

- How we handle your data (email, payment reference, bank transfer details) is described in the - privacy policy. -

- -

8. Governing law

-

- These terms are governed by the law of [Switzerland], without prejudice to mandatory consumer - protection provisions of your country of residence. -

-
+ diff --git a/src/routes/legal/terms/content/de.svelte b/src/routes/legal/terms/content/de.svelte new file mode 100644 index 0000000..d991bbf --- /dev/null +++ b/src/routes/legal/terms/content/de.svelte @@ -0,0 +1,87 @@ + + + +

+ Drizz.li ist kostenlos nutzbar. Diese Bedingungen betreffen den freiwilligen + Unterstützerbeitrag: eine kleine Zahlung, die das Projekt am Laufen hält und als Dankeschön die + Unterstützer-Extras freischaltet. Auch wenn wir von einem Beitrag sprechen, werden dafür + Funktionen freigeschaltet. +

+ +

1. Was Sie erhalten

+

+ Ein Unterstützerbeitrag schaltet die Extras für den bezahlten Zeitraum frei: derzeit + historisches Wetter mit Vergleich zu den Klimanormalen sowie neue Funktionen, sobald sie + erscheinen. Die kostenlosen Teile von Drizz.li bleiben für alle frei. +

+ +

2. Ablauf

+
    +
  • + Sie fordern die Überweisungsdaten auf der Anmeldeseite an; wir senden sie Ihnen zusammen mit + einer persönlichen Zahlungsreferenz per E-Mail. +
  • +
  • + Sie überweisen den Betrag (ab 3 € / 3 $ / 3 CHF pro Monat) per gewöhnlicher Banküberweisung + unter Angabe der Referenz. +
  • +
  • + Überweisungen werden manuell zugeordnet. Sobald Ihr Beitrag eingeht - meist innerhalb von 24 + Stunden, höchstens einige Tage - erhalten Sie per E-Mail einen persönlichen Zugangsschlüssel. +
  • +
  • + Sie fügen den Schlüssel einmal in Drizz.li ein; er wird auf Ihrem Gerät gespeichert und + automatisch überprüft. +
  • +
+ +

3. Keine automatische Verlängerung

+

+ Beiträge sind einmalig und im Voraus bezahlt: Nichts verlängert sich automatisch und wir + belasten Sie nie. Läuft Ihr Zeitraum ab, sperren sich die Extras einfach wieder; ein erneuter + Beitrag mit derselben E-Mail verlängert Ihren bestehenden Schlüssel. +

+ +

4. Widerruf und Erstattung

+

+ Als Verbraucherin oder Verbraucher in der EU/im EWR haben Sie ein gesetzliches 14-tägiges + Widerrufsrecht. Darüber hinaus halten wir es einfach: Sind Sie innerhalb von 14 Tagen nach + Erhalt Ihres Zugangsschlüssels aus irgendeinem Grund unzufrieden, schreiben Sie an + drizzli@vincentvanderwal.nl + und wir erstatten Ihren Beitrag vollständig. +

+ +

5. Faire Nutzung

+

+ Der Zugangsschlüssel ist persönlich. Bitte veröffentlichen oder teilen Sie ihn nicht; eindeutig + missbrauchte Schlüssel (z. B. öffentlich geteilt) können gesperrt werden. Wird Ihr Schlüssel + ohne Grund gesperrt, haben Sie Anspruch auf eine anteilige Erstattung. +

+ +

6. Dienst und Verfügbarkeit

+

+ Drizz.li ist ein persönliches Open-Source-Projekt und wird ohne Gewähr bereitgestellt. Wir + bemühen uns um Verfügbarkeit und Genauigkeit, können aber weder unterbrechungsfreien Betrieb + noch die Richtigkeit der Vorhersagen garantieren - Wetterdaten sind rein informativ (siehe + Haftungsausschluss im Impressum). Sollten die Unterstützer-Extras während eines bezahlten + Zeitraums dauerhaft ausfallen, erstatten wir auf Anfrage den Restzeitraum. +

+ +

7. Daten

+

+ Wie wir Ihre Daten (E-Mail, Zahlungsreferenz, Überweisungsdaten) verarbeiten, steht in der + Datenschutzerklärung. +

+ +

8. Anwendbares Recht

+

+ Für diese Bedingungen gilt das Recht der [Schweiz], unbeschadet zwingender + Verbraucherschutzvorschriften Ihres Wohnsitzlandes. +

+ +

Massgeblich ist die englische Fassung dieser Bedingungen.

+
diff --git a/src/routes/legal/terms/content/en.svelte b/src/routes/legal/terms/content/en.svelte new file mode 100644 index 0000000..1b3e869 --- /dev/null +++ b/src/routes/legal/terms/content/en.svelte @@ -0,0 +1,83 @@ + + + +

+ Drizz.li is free to use. These terms cover the optional supporter contribution: a small payment + that helps keep the project running and, as a thank-you, unlocks the supporter extras. Although + we call it a contribution, features are unlocked in return. +

+ +

1. What you get

+

+ A supporter contribution unlocks the supporter extras for the paid period: currently historical + weather with climate-normal comparisons, plus new supporter features as they land. The free + parts of Drizz.li stay free for everyone. +

+ +

2. How it works

+
    +
  • + You request the transfer details on the signup page; we email them to you together with a + personal payment reference. +
  • +
  • + You transfer the amount (from €3 / $3 / CHF 3 per month) by ordinary bank transfer, including + the reference. +
  • +
  • + Transfers are matched manually. Once your contribution arrives - usually within 24 hours, at + most a few days - you receive a personal access key by email. +
  • +
  • + You paste the key into Drizz.li once; it is stored on your device and verified automatically. +
  • +
+ +

3. No auto-renewal

+

+ Contributions are one-off and prepaid: nothing renews automatically and we never charge you. + When your period ends, the extras simply lock again; contributing again with the same email + extends your existing key. +

+ +

4. Withdrawal and refunds

+

+ If you are a consumer in the EU/EEA, you have a statutory 14-day right of withdrawal. Beyond + that, we keep it simple: if you are unhappy for any reason within 14 days of receiving your + access key, email + drizzli@vincentvanderwal.nl and we will refund your + contribution in full. +

+ +

5. Fair use

+

+ The access key is personal. Please don't publish or share it; keys that are clearly abused (e.g. + shared publicly) may be revoked. If your key is revoked without cause, you are entitled to a + pro-rata refund. +

+ +

6. Service and availability

+

+ Drizz.li is a personal open-source project, provided as-is. We work to keep it available and + accurate, but we cannot guarantee uninterrupted availability or the correctness of forecasts - + weather data is informational only (see the disclaimer in the imprint). If the supporter extras + become permanently unavailable during a period you paid for, we will refund the remaining period + on request. +

+ +

7. Data

+

+ How we handle your data (email, payment reference, bank transfer details) is described in the + privacy policy. +

+ +

8. Governing law

+

+ These terms are governed by the law of [Switzerland], without prejudice to mandatory consumer + protection provisions of your country of residence. +

+
diff --git a/src/routes/legal/terms/content/es.svelte b/src/routes/legal/terms/content/es.svelte new file mode 100644 index 0000000..d9e355d --- /dev/null +++ b/src/routes/legal/terms/content/es.svelte @@ -0,0 +1,91 @@ + + + +

+ Drizz.li es gratuito. Estas condiciones cubren la aportación voluntaria de colaborador: un + pequeño pago que ayuda a mantener el proyecto y que, como agradecimiento, desbloquea los extras. + Aunque lo llamamos aportación, a cambio se desbloquean funciones. +

+ +

1. Qué obtienes

+

+ Una aportación desbloquea los extras durante el periodo pagado: actualmente el clima histórico + con comparación frente a las normales climáticas, además de las nuevas funciones que vayan + llegando. Las partes gratuitas de Drizz.li siguen siendo gratuitas para todos. +

+ +

2. Cómo funciona

+
    +
  • + Solicitas los datos de la transferencia en la página de registro; te los enviamos por correo + junto con una referencia de pago personal. +
  • +
  • + Transfieres el importe (desde 3 € / 3 $ / 3 CHF al mes) mediante una transferencia bancaria + ordinaria, incluyendo la referencia. +
  • +
  • + Las transferencias se concilian manualmente. Cuando llega tu aportación - normalmente en 24 + horas, como mucho unos días - recibes por correo una clave de acceso personal. +
  • +
  • + Pegas la clave en Drizz.li una vez; se guarda en tu dispositivo y se verifica automáticamente. +
  • +
+ +

3. Sin renovación automática

+

+ Las aportaciones son únicas y prepagadas: nada se renueva solo y nunca te cobramos. Cuando + termina tu periodo, los extras simplemente se bloquean de nuevo; volver a aportar con el mismo + correo amplía tu clave existente. +

+ +

4. Desistimiento y reembolsos

+

+ Si eres consumidor en la UE/EEE, dispones de un derecho legal de desistimiento de 14 días. Más + allá de eso, lo mantenemos sencillo: si no estás satisfecho por cualquier motivo en los 14 días + siguientes a recibir tu clave, escribe a + drizzli@vincentvanderwal.nl + y te reembolsaremos la aportación íntegra. +

+ +

5. Uso razonable

+

+ La clave de acceso es personal. No la publiques ni la compartas; las claves claramente abusadas + (por ejemplo, compartidas públicamente) pueden revocarse. Si tu clave se revoca sin causa, + tienes derecho a un reembolso proporcional. +

+ +

6. Servicio y disponibilidad

+

+ Drizz.li es un proyecto personal de código abierto, ofrecido tal cual. Trabajamos para + mantenerlo disponible y preciso, pero no podemos garantizar disponibilidad ininterrumpida ni la + exactitud de los pronósticos: los datos meteorológicos son solo informativos (véase el descargo + del aviso legal). Si los extras dejan de estar disponibles de forma permanente durante un + periodo que has pagado, reembolsaremos el periodo restante a petición. +

+ +

7. Datos

+

+ Cómo tratamos tus datos (correo, referencia de pago, datos de la transferencia) se describe en + la + política de privacidad. +

+ +

8. Legislación aplicable

+

+ Estas condiciones se rigen por la legislación de [Suiza], sin perjuicio de las disposiciones + imperativas de protección al consumidor de tu país de residencia. +

+ +

+ En caso de discrepancia prevalece la versión inglesa de estas condiciones. +

+
diff --git a/src/routes/legal/terms/content/fr.svelte b/src/routes/legal/terms/content/fr.svelte new file mode 100644 index 0000000..2f1ec5f --- /dev/null +++ b/src/routes/legal/terms/content/fr.svelte @@ -0,0 +1,89 @@ + + + +

+ Drizz.li est gratuit. Ces conditions portent sur la contribution facultative : un petit paiement + qui aide à faire vivre le projet et qui, en remerciement, débloque les bonus. Même si nous + parlons de contribution, des fonctionnalités sont débloquées en retour. +

+ +

1. Ce que vous obtenez

+

+ Une contribution débloque les bonus pour la période payée : actuellement la météo historique + avec comparaison aux normales climatiques, ainsi que les nouvelles fonctions à venir. Les + parties gratuites de Drizz.li le restent pour tout le monde. +

+ +

2. Fonctionnement

+
    +
  • + Vous demandez les coordonnées bancaires sur la page d'inscription ; nous vous les envoyons par + e-mail avec une référence de paiement personnelle. +
  • +
  • + Vous virez le montant (à partir de 3 € / 3 $ / 3 CHF par mois) par virement bancaire + ordinaire, en indiquant la référence. +
  • +
  • + Les virements sont rapprochés manuellement. Dès réception de votre contribution - en général + sous 24 heures, au plus quelques jours - vous recevez une clé d'accès personnelle par e-mail. +
  • +
  • + Vous collez la clé dans Drizz.li une seule fois ; elle est stockée sur votre appareil et + vérifiée automatiquement. +
  • +
+ +

3. Pas de reconduction automatique

+

+ Les contributions sont ponctuelles et prépayées : rien ne se reconduit et nous ne vous prélevons + jamais. À la fin de votre période, les bonus se reverrouillent simplement ; contribuer à nouveau + avec la même adresse prolonge votre clé existante. +

+ +

4. Rétractation et remboursement

+

+ Si vous êtes consommateur dans l'UE/EEE, vous disposez d'un droit légal de rétractation de 14 + jours. Au-delà, nous faisons simple : si vous n'êtes pas satisfait, pour quelque raison que ce + soit, dans les 14 jours suivant la réception de votre clé, écrivez à + drizzli@vincentvanderwal.nl + et nous vous remboursons intégralement. +

+ +

5. Usage loyal

+

+ La clé d'accès est personnelle. Merci de ne pas la publier ni la partager ; les clés + manifestement détournées (par exemple partagées publiquement) peuvent être révoquées. Si votre + clé est révoquée sans motif, vous avez droit à un remboursement au prorata. +

+ +

6. Service et disponibilité

+

+ Drizz.li est un projet personnel open source, fourni en l'état. Nous nous efforçons de le + maintenir disponible et exact, mais nous ne pouvons garantir ni une disponibilité ininterrompue + ni l'exactitude des prévisions - les données météo sont purement informatives (voir + l'avertissement dans les mentions légales). Si les bonus devenaient définitivement indisponibles + pendant une période payée, nous rembourserions la période restante sur demande. +

+ +

7. Données

+

+ La façon dont nous traitons vos données (e-mail, référence de paiement, informations de + virement) est décrite dans la + politique de confidentialité. +

+ +

8. Droit applicable

+

+ Ces conditions sont régies par le droit de la [Suisse], sans préjudice des dispositions + impératives de protection des consommateurs de votre pays de résidence. +

+ +

+ En cas de divergence, la version anglaise de ces conditions fait foi. +

+
diff --git a/src/routes/legal/terms/content/it.svelte b/src/routes/legal/terms/content/it.svelte new file mode 100644 index 0000000..4b4111c --- /dev/null +++ b/src/routes/legal/terms/content/it.svelte @@ -0,0 +1,89 @@ + + + +

+ Drizz.li è gratuito. Queste condizioni riguardano il contributo facoltativo di sostegno: un + piccolo pagamento che aiuta a mantenere vivo il progetto e che, come ringraziamento, sblocca gli + extra. Anche se lo chiamiamo contributo, in cambio vengono sbloccate delle funzioni. +

+ +

1. Cosa ottieni

+

+ Un contributo sblocca gli extra per il periodo pagato: attualmente il meteo storico con + confronto rispetto alle normali climatiche, oltre alle nuove funzioni man mano che arrivano. Le + parti gratuite di Drizz.li restano gratuite per tutti. +

+ +

2. Come funziona

+
    +
  • + Richiedi i dati per il bonifico nella pagina di iscrizione; te li inviamo via e-mail insieme a + un riferimento di pagamento personale. +
  • +
  • + Effettui il bonifico (da 3 € / 3 $ / 3 CHF al mese) con un normale bonifico bancario, + indicando il riferimento. +
  • +
  • + I bonifici vengono abbinati manualmente. Appena il contributo arriva - di solito entro 24 ore, + al massimo pochi giorni - ricevi via e-mail una chiave di accesso personale. +
  • +
  • + Incolli la chiave in Drizz.li una volta sola; viene salvata sul tuo dispositivo e verificata + automaticamente. +
  • +
+ +

3. Nessun rinnovo automatico

+

+ I contributi sono una tantum e prepagati: nulla si rinnova da solo e non ti addebitiamo mai + nulla. Alla fine del periodo gli extra si bloccano semplicemente di nuovo; un nuovo contributo + con la stessa e-mail estende la chiave esistente. +

+ +

4. Recesso e rimborsi

+

+ Se sei un consumatore nell'UE/SEE, hai un diritto di recesso legale di 14 giorni. Oltre a + questo, la teniamo semplice: se per qualsiasi motivo non sei soddisfatto entro 14 giorni dalla + ricezione della chiave, scrivi a + drizzli@vincentvanderwal.nl + e ti rimborseremo l'intero contributo. +

+ +

5. Uso corretto

+

+ La chiave di accesso è personale. Non pubblicarla né condividerla; le chiavi chiaramente abusate + (ad esempio condivise pubblicamente) possono essere revocate. Se la tua chiave viene revocata + senza motivo, hai diritto a un rimborso proporzionale. +

+ +

6. Servizio e disponibilità

+

+ Drizz.li è un progetto personale open source, fornito così com'è. Lavoriamo per mantenerlo + disponibile e accurato, ma non possiamo garantire né la continuità del servizio né la + correttezza delle previsioni: i dati meteo sono solo informativi (vedi l'avvertenza nelle note + legali). Se gli extra dovessero diventare permanentemente non disponibili durante un periodo + pagato, rimborseremo su richiesta il periodo residuo. +

+ +

7. Dati

+

+ Il trattamento dei tuoi dati (e-mail, riferimento di pagamento, dati del bonifico) è descritto + nell' + informativa sulla privacy. +

+ +

8. Legge applicabile

+

+ Queste condizioni sono regolate dal diritto della [Svizzera], fatte salve le disposizioni + imperative di tutela del consumatore del tuo paese di residenza. +

+ +

+ In caso di discrepanza prevale la versione inglese di queste condizioni. +

+
diff --git a/src/routes/weather/14-day/[location]/+page.svelte b/src/routes/weather/14-day/[location]/+page.svelte index a409d86..3f08103 100644 --- a/src/routes/weather/14-day/[location]/+page.svelte +++ b/src/routes/weather/14-day/[location]/+page.svelte @@ -327,8 +327,7 @@ /> - This model's ensemble only reaches about {validDays} days ahead, the spread is - trimmed to its available range. + {m.ensemble_trimmed({ days: validDays })}
{/if} @@ -385,7 +384,9 @@ {#snippet controls()}
- +
{/snippet} diff --git a/src/routes/weather/compare/[location]/+page.svelte b/src/routes/weather/compare/[location]/+page.svelte index ab0834b..bf115ec 100644 --- a/src/routes/weather/compare/[location]/+page.svelte +++ b/src/routes/weather/compare/[location]/+page.svelte @@ -26,6 +26,7 @@ groupRange, isColumnUnit } from '$lib/charts'; + import * as m from '$lib/paraglide/messages'; import { type DaylightBand, type ModelCompareResult, @@ -315,10 +316,10 @@
{#if zoomActive} {/if}
{#each rangePresets as preset (preset.label)}
{/snippet} @@ -436,7 +439,9 @@
-

Models

+

{m.compare_models_heading()}

{#if params.models && params.models.length > 0}

- Hourly Weather Variables + {m.compare_variables_heading()}

{#if params.hourly && params.hourly.length > 0} diff --git a/src/routes/weather/compare/[location]/ModelPictogramTimeline.svelte b/src/routes/weather/compare/[location]/ModelPictogramTimeline.svelte index d97b67f..ef64421 100644 --- a/src/routes/weather/compare/[location]/ModelPictogramTimeline.svelte +++ b/src/routes/weather/compare/[location]/ModelPictogramTimeline.svelte @@ -1,6 +1,8 @@ + +{#snippet heroActions()} +
+ { + archiveModel = model; + storedArchiveModel.set(model); + }} + /> +
+{/snippet} + - Drizz.li | Historical weather + Drizz.li | {m.page_historical_subtitle()} - + + import * as m from '$lib/paraglide/messages'; + interface Props { start: string; end: string; @@ -34,10 +36,10 @@ } const presets = [ - { label: '7 days', apply: () => applyLastDays(7) }, - { label: '30 days', apply: () => applyLastDays(30) }, - { label: '90 days', apply: () => applyLastDays(90) }, - { label: 'Month, last year', apply: applyThisMonthLastYear } + { label: () => m.historical_last_days({ days: 7 }), apply: () => applyLastDays(7) }, + { label: () => m.historical_last_days({ days: 30 }), apply: () => applyLastDays(30) }, + { label: () => m.historical_last_days({ days: 90 }), apply: () => applyLastDays(90) }, + { label: () => m.historical_month_last_year(), apply: applyThisMonthLastYear } ]; // ─── Manual inputs ──────────────────────────────────────────────────────────── @@ -65,7 +67,9 @@ >
- +
- + - {#each presets as preset (preset.label)} + {#each presets as preset (preset.label())} {/each}
diff --git a/src/routes/weather/historical/[location]/HistoricalDaily.svelte b/src/routes/weather/historical/[location]/HistoricalDaily.svelte index 7360c2b..1d5f989 100644 --- a/src/routes/weather/historical/[location]/HistoricalDaily.svelte +++ b/src/routes/weather/historical/[location]/HistoricalDaily.svelte @@ -1,6 +1,7 @@ - Weather Map | Open-Meteo.com + {m.page_maps_title()} | Open-Meteo.com @@ -88,7 +90,7 @@