mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
i18n: tighten 404 + global-error copy across all locales
This commit is contained in:
1 parent
71bbd094d7
commit
2390438edc
9 files changed
+165
-177
No files matched your search
+13
-29
@@ -152,12 +152,9 @@
|
||||
"title": "Da ist etwas schiefgelaufen",
|
||||
"description": "Ein unerwarteter Fehler ist aufgetreten. Du kannst es erneut versuchen, die technischen Details kopieren oder einen Bug-Report öffnen."
|
||||
},
|
||||
"errors": {
|
||||
"notFound": {
|
||||
"title": "Seite nicht gefunden",
|
||||
"body": "Die gesuchte Seite existiert nicht oder wurde verschoben. Kehr zum Dashboard zurück, um dort weiterzumachen, wo du aufgehört hast.",
|
||||
"backToDashboard": "Zurück zum Dashboard"
|
||||
}
|
||||
"notFound": {
|
||||
"title": "Diese Seite existiert nicht.",
|
||||
"backToDashboard": "Zurück zum Dashboard"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
@@ -353,7 +350,6 @@
|
||||
"notesPlaceholder": "z.B. nach dem Essen",
|
||||
"formReset": "Formular zurücksetzen",
|
||||
"saveError": "Fehler beim Speichern",
|
||||
"loadError": "Fehler beim Laden",
|
||||
"duplicateTimestamp": "Zum gewählten Zeitpunkt existiert bereits ein Messwert dieses Typs.",
|
||||
"deleteError": "Fehler beim Löschen",
|
||||
"invalidValue": "Bitte einen gültigen Wert eingeben.",
|
||||
@@ -860,10 +856,6 @@
|
||||
"legendMissed": "Verpasst",
|
||||
"emptyStateTitle": "Erfasse mehr Messungen, um Trends zu sehen",
|
||||
"emptyStateDescription": "Erfasse mindestens 3 Einträge, um die Trendlinie freizuschalten.",
|
||||
"needMoreDistinctDaysTitle": "Mehr Messtage erforderlich",
|
||||
"needMoreDistinctDaysDescription": "Erfasse an mehreren Tagen, um eine Trendlinie freizuschalten.",
|
||||
"noDataInRangeTitle": "Keine Daten in diesem Zeitraum",
|
||||
"noDataInRangeDescription": "Für den gewählten Bereich liegen keine Messungen vor. Wechsle den Zeitraum oder erfasse einen neuen Wert.",
|
||||
"personalBaseline": "Dein Mittel",
|
||||
"deltaVsBaseline": "{delta} vs. dein Mittel",
|
||||
"deltaUnchanged": "entspricht deinem Mittel",
|
||||
@@ -1145,16 +1137,13 @@
|
||||
"feedbackThanks": "Danke für das Signal.",
|
||||
"dailyLimitTitle": "Heute-Limit erreicht",
|
||||
"dailyLimitBody": "Heute-Limit erreicht; Reset um 00:00 UTC.",
|
||||
"providerRateLimitTitle": "Anbieter gedrosselt",
|
||||
"providerRateLimitBody": "Anbieter ist temporär überlastet; wieder verfügbar in ~5 min."
|
||||
"providerRateLimitTitle": "Provider rate-limited",
|
||||
"providerRateLimitBody": "Provider ist temporär überlastet; Reset in ~5 min."
|
||||
},
|
||||
"relativeJustNow": "gerade eben",
|
||||
"relativeMinutesAgoOne": "vor {count} Minute",
|
||||
"relativeMinutesAgoOther": "vor {count} Minuten",
|
||||
"relativeHoursAgoOne": "vor {count} Stunde",
|
||||
"relativeHoursAgoOther": "vor {count} Stunden",
|
||||
"relativeDaysAgoOne": "vor {count} Tag",
|
||||
"relativeDaysAgoOther": "vor {count} Tagen",
|
||||
"relativeMinutesAgo": "vor {count} Minuten",
|
||||
"relativeHoursAgo": "vor {count} Stunden",
|
||||
"relativeDaysAgo": "vor {count} Tagen",
|
||||
"navOverview": "Übersicht",
|
||||
"navBloodPressure": "Blutdruck",
|
||||
"navWeight": "Gewicht",
|
||||
@@ -1631,7 +1620,7 @@
|
||||
},
|
||||
"ai": {
|
||||
"title": "Auswertungen",
|
||||
"description": "Anbieter, Modell, API-Schlüssel."
|
||||
"description": "Provider, Modell, API-Key."
|
||||
},
|
||||
"api": {
|
||||
"title": "API & Tokens",
|
||||
@@ -1769,9 +1758,9 @@
|
||||
"codexDisconnected": "ChatGPT-Verbindung getrennt.",
|
||||
"codexConnectionFailed": "ChatGPT-Verbindung fehlgeschlagen. Bitte versuche es erneut.",
|
||||
"rawData": "Rohdaten mitsenden",
|
||||
"rawDataOnDescription": "Es werden aggregierte Kennzahlen plus anonymisierte Rohdaten der letzten 30 Tage gesendet. Dazu zählen einzelne Messpunkte je Bereich (z. B. Gewicht, Blutdruck, Puls) mit Zeitbezug, damit der Anbieter zeitliche Muster, Ausreißer und Zusammenhänge zuverlässiger erkennen kann. Nicht übertragen werden Name, E-Mail oder direkte Kontodaten.",
|
||||
"rawDataOffDescription": "Es werden nur zusammengefasste Werte gesendet (z. B. Durchschnitt, Trend, Minimum/Maximum). Keine einzelnen Messpunkte und keine exakten Zeitbezüge. Das ist datensparsamer, der Anbieter kann dafür aber weniger fein auf Muster und kurzfristige Schwankungen eingehen.",
|
||||
"rawDataWarning": "Rohdaten aktiv: Der Anbieter erhält zusätzlich anonymisierte Messpunkte der letzten 30 Tage für eine präzisere Auswertung.",
|
||||
"rawDataOnDescription": "Es werden aggregierte Kennzahlen plus anonymisierte Rohdaten der letzten 30 Tage gesendet. Dazu zählen einzelne Messpunkte je Bereich (z. B. Gewicht, Blutdruck, Puls) mit Zeitbezug, damit der Provider zeitliche Muster, Ausreißer und Zusammenhänge zuverlässiger erkennen kann. Nicht übertragen werden Name, E-Mail oder direkte Kontodaten.",
|
||||
"rawDataOffDescription": "Es werden nur zusammengefasste Werte gesendet (z. B. Durchschnitt, Trend, Minimum/Maximum). Keine einzelnen Messpunkte und keine exakten Zeitbezüge. Das ist datensparsamer, der Provider kann dafür aber weniger fein auf Muster und kurzfristige Schwankungen eingehen.",
|
||||
"rawDataWarning": "Rohdaten aktiv: Der Provider erhält zusätzlich anonymisierte Messpunkte der letzten 30 Tage für eine präzisere Auswertung.",
|
||||
"regenerateInsights": "Reports neu generieren",
|
||||
"regenerateSuccess": "Reports wurden neu generiert",
|
||||
"regenerateRateLimit": "Bitte warte — du hast das stündliche Limit für Analysen erreicht.",
|
||||
@@ -1904,17 +1893,12 @@
|
||||
"integrationPill": {
|
||||
"connected": "Verbunden",
|
||||
"errorReconnect": "Fehler — neu verbinden",
|
||||
"warningServerError": "Verbunden, aber Serverfehler",
|
||||
"parkedReconnect": "Pausiert — manuell wieder verbinden",
|
||||
"notConnected": "Nicht verbunden",
|
||||
"justNow": "gerade eben",
|
||||
"minutesAgo": "vor {count} min",
|
||||
"hoursAgo": "vor {count} Std.",
|
||||
"daysAgo": "vor {count} d",
|
||||
"ariaLabel": "Integrationsstatus",
|
||||
"resumeCta": "Wieder verbinden",
|
||||
"resumeSuccess": "Integration wieder aktiv",
|
||||
"resumeError": "Wieder verbinden fehlgeschlagen"
|
||||
"ariaLabel": "Integrationsstatus"
|
||||
},
|
||||
"apiTokens": "API-Tokens",
|
||||
"apiTokensDescription": "Erstelle API-Tokens für die externe Medikamenten-Einnahme (z.B. Shortcuts, Automationen).",
|
||||
|
||||
+7
-23
@@ -152,12 +152,9 @@
|
||||
"title": "Something went wrong",
|
||||
"description": "An unexpected error occurred. You can retry, copy the technical details, or open a bug report."
|
||||
},
|
||||
"errors": {
|
||||
"notFound": {
|
||||
"title": "Page not found",
|
||||
"body": "The page you were looking for doesn't exist or has been moved. Head back to the dashboard to pick up where you left off.",
|
||||
"backToDashboard": "Back to dashboard"
|
||||
}
|
||||
"notFound": {
|
||||
"title": "This page doesn't exist.",
|
||||
"backToDashboard": "Back to dashboard"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
@@ -353,7 +350,6 @@
|
||||
"notesPlaceholder": "e.g. after a meal",
|
||||
"formReset": "Reset form",
|
||||
"saveError": "Error saving",
|
||||
"loadError": "Could not load measurements",
|
||||
"duplicateTimestamp": "A measurement of this type already exists at the selected timestamp.",
|
||||
"deleteError": "Error deleting",
|
||||
"invalidValue": "Please enter a valid value.",
|
||||
@@ -860,10 +856,6 @@
|
||||
"legendMissed": "Missed",
|
||||
"emptyStateTitle": "Add more measurements to see trends",
|
||||
"emptyStateDescription": "Log at least 3 entries to unlock the trend line.",
|
||||
"needMoreDistinctDaysTitle": "More distinct days needed",
|
||||
"needMoreDistinctDaysDescription": "Log on more days to unlock the trend line.",
|
||||
"noDataInRangeTitle": "No data in this range",
|
||||
"noDataInRangeDescription": "No measurements fall inside the selected range. Switch the range or log a new measurement.",
|
||||
"personalBaseline": "Your normal",
|
||||
"deltaVsBaseline": "{delta} vs. your normal",
|
||||
"deltaUnchanged": "matches your normal",
|
||||
@@ -1149,12 +1141,9 @@
|
||||
"providerRateLimitBody": "Provider temporarily rate-limited; retry in ~5 min."
|
||||
},
|
||||
"relativeJustNow": "just now",
|
||||
"relativeMinutesAgoOne": "{count} minute ago",
|
||||
"relativeMinutesAgoOther": "{count} minutes ago",
|
||||
"relativeHoursAgoOne": "{count} hour ago",
|
||||
"relativeHoursAgoOther": "{count} hours ago",
|
||||
"relativeDaysAgoOne": "{count} day ago",
|
||||
"relativeDaysAgoOther": "{count} days ago",
|
||||
"relativeMinutesAgo": "{count} minutes ago",
|
||||
"relativeHoursAgo": "{count} hours ago",
|
||||
"relativeDaysAgo": "{count} days ago",
|
||||
"navOverview": "Overview",
|
||||
"navBloodPressure": "Blood Pressure",
|
||||
"navWeight": "Weight",
|
||||
@@ -1904,17 +1893,12 @@
|
||||
"integrationPill": {
|
||||
"connected": "Connected",
|
||||
"errorReconnect": "Error — reconnect",
|
||||
"warningServerError": "Connected, server error",
|
||||
"parkedReconnect": "Paused — reconnect manually",
|
||||
"notConnected": "Not connected",
|
||||
"justNow": "just now",
|
||||
"minutesAgo": "{count} min ago",
|
||||
"hoursAgo": "{count} h ago",
|
||||
"daysAgo": "{count} d ago",
|
||||
"ariaLabel": "Integration status",
|
||||
"resumeCta": "Reconnect",
|
||||
"resumeSuccess": "Integration resumed",
|
||||
"resumeError": "Reconnect failed"
|
||||
"ariaLabel": "Integration status"
|
||||
},
|
||||
"apiTokens": "API Tokens",
|
||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||
|
||||
+10
-26
@@ -152,12 +152,9 @@
|
||||
"title": "Algo ha salido mal",
|
||||
"description": "Se ha producido un error inesperado. Puedes reintentar, copiar los detalles técnicos o abrir un reporte de bug."
|
||||
},
|
||||
"errors": {
|
||||
"notFound": {
|
||||
"title": "Página no encontrada",
|
||||
"body": "La página que buscas no existe o se ha movido. Vuelve al panel para continuar donde lo dejaste.",
|
||||
"backToDashboard": "Volver al panel"
|
||||
}
|
||||
"notFound": {
|
||||
"title": "Esta página no existe.",
|
||||
"backToDashboard": "Volver al panel"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Panel",
|
||||
@@ -353,7 +350,6 @@
|
||||
"notesPlaceholder": "p. ej. después de comer",
|
||||
"formReset": "Restablecer formulario",
|
||||
"saveError": "Error al guardar",
|
||||
"loadError": "No se han podido cargar las mediciones",
|
||||
"duplicateTimestamp": "Ya existe una medición de este tipo en la marca de tiempo seleccionada.",
|
||||
"deleteError": "Error al eliminar",
|
||||
"invalidValue": "Introduce un valor válido.",
|
||||
@@ -860,10 +856,6 @@
|
||||
"legendMissed": "Omitida",
|
||||
"emptyStateTitle": "Registra más mediciones para ver tendencias",
|
||||
"emptyStateDescription": "Registra al menos 3 entradas para desbloquear la línea de tendencia.",
|
||||
"needMoreDistinctDaysTitle": "Se necesitan más días distintos",
|
||||
"needMoreDistinctDaysDescription": "Registra en más días para desbloquear la línea de tendencia.",
|
||||
"noDataInRangeTitle": "Sin datos en este rango",
|
||||
"noDataInRangeDescription": "No hay mediciones en el rango seleccionado. Cambia el rango o registra una nueva medición.",
|
||||
"personalBaseline": "Tu media",
|
||||
"deltaVsBaseline": "{delta} vs. tu media",
|
||||
"deltaUnchanged": "coincide con tu media",
|
||||
@@ -1149,12 +1141,9 @@
|
||||
"settingsContextHint": "Bloques opcionales en los que el coach puede apoyarse. Si desactivas alguno, se omitirá en cada respuesta."
|
||||
},
|
||||
"relativeJustNow": "ahora mismo",
|
||||
"relativeMinutesAgoOne": "hace {count} minuto",
|
||||
"relativeMinutesAgoOther": "hace {count} minutos",
|
||||
"relativeHoursAgoOne": "hace {count} hora",
|
||||
"relativeHoursAgoOther": "hace {count} horas",
|
||||
"relativeDaysAgoOne": "hace {count} día",
|
||||
"relativeDaysAgoOther": "hace {count} días",
|
||||
"relativeMinutesAgo": "hace {count} minutos",
|
||||
"relativeHoursAgo": "hace {count} horas",
|
||||
"relativeDaysAgo": "hace {count} días",
|
||||
"navOverview": "Resumen",
|
||||
"navBloodPressure": "Presión arterial",
|
||||
"navWeight": "Peso",
|
||||
@@ -1902,19 +1891,14 @@
|
||||
}
|
||||
},
|
||||
"integrationPill": {
|
||||
"connected": "Conectado",
|
||||
"errorReconnect": "Error — reconectar",
|
||||
"warningServerError": "Conectado, error del servidor",
|
||||
"parkedReconnect": "Pausado — reconectar manualmente",
|
||||
"notConnected": "No conectado",
|
||||
"connected": "Connected",
|
||||
"errorReconnect": "Error — reconnect",
|
||||
"notConnected": "Not connected",
|
||||
"justNow": "just now",
|
||||
"minutesAgo": "{count} min ago",
|
||||
"hoursAgo": "{count} h ago",
|
||||
"daysAgo": "{count} d ago",
|
||||
"ariaLabel": "Integration status",
|
||||
"resumeCta": "Reconectar",
|
||||
"resumeSuccess": "Integración reanudada",
|
||||
"resumeError": "Reconexión fallida"
|
||||
"ariaLabel": "Integration status"
|
||||
},
|
||||
"apiTokens": "API Tokens",
|
||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||
|
||||
+10
-26
@@ -152,12 +152,9 @@
|
||||
"title": "Une erreur est survenue",
|
||||
"description": "Une erreur inattendue est survenue. Tu peux réessayer, copier les détails techniques ou ouvrir un rapport de bug."
|
||||
},
|
||||
"errors": {
|
||||
"notFound": {
|
||||
"title": "Page introuvable",
|
||||
"body": "La page que tu cherches n’existe pas ou a été déplacée. Reviens au tableau de bord pour reprendre où tu en étais.",
|
||||
"backToDashboard": "Retour au tableau de bord"
|
||||
}
|
||||
"notFound": {
|
||||
"title": "Cette page n'existe pas.",
|
||||
"backToDashboard": "Retour au tableau de bord"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Tableau de bord",
|
||||
@@ -353,7 +350,6 @@
|
||||
"notesPlaceholder": "p. ex. après le repas",
|
||||
"formReset": "Réinitialiser le formulaire",
|
||||
"saveError": "Erreur lors de l’enregistrement",
|
||||
"loadError": "Impossible de charger les mesures",
|
||||
"duplicateTimestamp": "Une mesure de ce type existe déjà à cet horodatage.",
|
||||
"deleteError": "Erreur lors de la suppression",
|
||||
"invalidValue": "Saisis une valeur valide.",
|
||||
@@ -860,10 +856,6 @@
|
||||
"legendMissed": "Manquée",
|
||||
"emptyStateTitle": "Saisis plus de mesures pour voir les tendances",
|
||||
"emptyStateDescription": "Saisis au moins 3 entrées pour débloquer la courbe de tendance.",
|
||||
"needMoreDistinctDaysTitle": "Plus de jours distincts requis",
|
||||
"needMoreDistinctDaysDescription": "Saisis sur plusieurs jours pour débloquer la courbe de tendance.",
|
||||
"noDataInRangeTitle": "Aucune donnée sur cette période",
|
||||
"noDataInRangeDescription": "Aucune mesure ne tombe dans la plage sélectionnée. Change de plage ou saisis une nouvelle mesure.",
|
||||
"personalBaseline": "Ta moyenne",
|
||||
"deltaVsBaseline": "{delta} vs. ta moyenne",
|
||||
"deltaUnchanged": "correspond à ta moyenne",
|
||||
@@ -1149,12 +1141,9 @@
|
||||
"settingsContextHint": "Blocs optionnels sur lesquels le coach peut s’appuyer. Si tu en désactives un, il sera omis dans chaque réponse."
|
||||
},
|
||||
"relativeJustNow": "à l’instant",
|
||||
"relativeMinutesAgoOne": "il y a {count} minute",
|
||||
"relativeMinutesAgoOther": "il y a {count} minutes",
|
||||
"relativeHoursAgoOne": "il y a {count} heure",
|
||||
"relativeHoursAgoOther": "il y a {count} heures",
|
||||
"relativeDaysAgoOne": "il y a {count} jour",
|
||||
"relativeDaysAgoOther": "il y a {count} jours",
|
||||
"relativeMinutesAgo": "il y a {count} minutes",
|
||||
"relativeHoursAgo": "il y a {count} heures",
|
||||
"relativeDaysAgo": "il y a {count} jours",
|
||||
"navOverview": "Vue d'ensemble",
|
||||
"navBloodPressure": "Tension artérielle",
|
||||
"navWeight": "Poids",
|
||||
@@ -1902,19 +1891,14 @@
|
||||
}
|
||||
},
|
||||
"integrationPill": {
|
||||
"connected": "Connecté",
|
||||
"errorReconnect": "Erreur — reconnecter",
|
||||
"warningServerError": "Connecté, erreur serveur",
|
||||
"parkedReconnect": "En pause — reconnecter manuellement",
|
||||
"notConnected": "Non connecté",
|
||||
"connected": "Connected",
|
||||
"errorReconnect": "Error — reconnect",
|
||||
"notConnected": "Not connected",
|
||||
"justNow": "just now",
|
||||
"minutesAgo": "{count} min ago",
|
||||
"hoursAgo": "{count} h ago",
|
||||
"daysAgo": "{count} d ago",
|
||||
"ariaLabel": "Integration status",
|
||||
"resumeCta": "Reconnecter",
|
||||
"resumeSuccess": "Intégration reprise",
|
||||
"resumeError": "Échec de la reconnexion"
|
||||
"ariaLabel": "Integration status"
|
||||
},
|
||||
"apiTokens": "API Tokens",
|
||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||
|
||||
+10
-26
@@ -152,12 +152,9 @@
|
||||
"title": "Qualcosa è andato storto",
|
||||
"description": "Si è verificato un errore inatteso. Puoi riprovare, copiare i dettagli tecnici o aprire una segnalazione di bug."
|
||||
},
|
||||
"errors": {
|
||||
"notFound": {
|
||||
"title": "Pagina non trovata",
|
||||
"body": "La pagina che cercavi non esiste o è stata spostata. Torna alla dashboard per riprendere da dove eri rimasto.",
|
||||
"backToDashboard": "Torna alla dashboard"
|
||||
}
|
||||
"notFound": {
|
||||
"title": "Questa pagina non esiste.",
|
||||
"backToDashboard": "Torna alla dashboard"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
@@ -353,7 +350,6 @@
|
||||
"notesPlaceholder": "es. dopo il pasto",
|
||||
"formReset": "Reimposta modulo",
|
||||
"saveError": "Errore durante il salvataggio",
|
||||
"loadError": "Impossibile caricare le misurazioni",
|
||||
"duplicateTimestamp": "Esiste già una misurazione di questo tipo a questa marca temporale.",
|
||||
"deleteError": "Errore durante l’eliminazione",
|
||||
"invalidValue": "Inserisci un valore valido.",
|
||||
@@ -860,10 +856,6 @@
|
||||
"legendMissed": "Mancata",
|
||||
"emptyStateTitle": "Registra altre misurazioni per vedere i trend",
|
||||
"emptyStateDescription": "Registra almeno 3 voci per sbloccare la linea di tendenza.",
|
||||
"needMoreDistinctDaysTitle": "Servono più giorni distinti",
|
||||
"needMoreDistinctDaysDescription": "Registra in più giorni per sbloccare la linea di tendenza.",
|
||||
"noDataInRangeTitle": "Nessun dato in questo intervallo",
|
||||
"noDataInRangeDescription": "Nessuna misurazione rientra nell'intervallo selezionato. Cambia intervallo o registra una nuova misurazione.",
|
||||
"personalBaseline": "La tua media",
|
||||
"deltaVsBaseline": "{delta} vs. la tua media",
|
||||
"deltaUnchanged": "corrisponde alla tua media",
|
||||
@@ -1149,12 +1141,9 @@
|
||||
"settingsContextHint": "Blocchi opzionali su cui il coach può basarsi. Disattivandone uno, viene omesso da ogni risposta."
|
||||
},
|
||||
"relativeJustNow": "proprio ora",
|
||||
"relativeMinutesAgoOne": "{count} minuto fa",
|
||||
"relativeMinutesAgoOther": "{count} minuti fa",
|
||||
"relativeHoursAgoOne": "{count} ora fa",
|
||||
"relativeHoursAgoOther": "{count} ore fa",
|
||||
"relativeDaysAgoOne": "{count} giorno fa",
|
||||
"relativeDaysAgoOther": "{count} giorni fa",
|
||||
"relativeMinutesAgo": "{count} minuti fa",
|
||||
"relativeHoursAgo": "{count} ore fa",
|
||||
"relativeDaysAgo": "{count} giorni fa",
|
||||
"navOverview": "Panoramica",
|
||||
"navBloodPressure": "Pressione arteriosa",
|
||||
"navWeight": "Peso",
|
||||
@@ -1902,19 +1891,14 @@
|
||||
}
|
||||
},
|
||||
"integrationPill": {
|
||||
"connected": "Connesso",
|
||||
"errorReconnect": "Errore — riconnetti",
|
||||
"warningServerError": "Connesso, errore del server",
|
||||
"parkedReconnect": "In pausa — riconnettere manualmente",
|
||||
"notConnected": "Non connesso",
|
||||
"connected": "Connected",
|
||||
"errorReconnect": "Error — reconnect",
|
||||
"notConnected": "Not connected",
|
||||
"justNow": "just now",
|
||||
"minutesAgo": "{count} min ago",
|
||||
"hoursAgo": "{count} h ago",
|
||||
"daysAgo": "{count} d ago",
|
||||
"ariaLabel": "Integration status",
|
||||
"resumeCta": "Riconnetti",
|
||||
"resumeSuccess": "Integrazione ripresa",
|
||||
"resumeError": "Riconnessione fallita"
|
||||
"ariaLabel": "Integration status"
|
||||
},
|
||||
"apiTokens": "API Tokens",
|
||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||
|
||||
+10
-26
@@ -152,12 +152,9 @@
|
||||
"title": "Coś poszło nie tak",
|
||||
"description": "Wystąpił nieoczekiwany błąd. Możesz spróbować ponownie, skopiować szczegóły techniczne lub otworzyć zgłoszenie błędu."
|
||||
},
|
||||
"errors": {
|
||||
"notFound": {
|
||||
"title": "Nie znaleziono strony",
|
||||
"body": "Strona, której szukasz, nie istnieje lub została przeniesiona. Wróć do panelu, aby kontynuować od miejsca, w którym przerwałeś.",
|
||||
"backToDashboard": "Powrót do panelu"
|
||||
}
|
||||
"notFound": {
|
||||
"title": "Ta strona nie istnieje.",
|
||||
"backToDashboard": "Powrót do panelu"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Panel",
|
||||
@@ -353,7 +350,6 @@
|
||||
"notesPlaceholder": "np. po jedzeniu",
|
||||
"formReset": "Resetuj formularz",
|
||||
"saveError": "Błąd zapisu",
|
||||
"loadError": "Nie udało się wczytać pomiarów",
|
||||
"duplicateTimestamp": "Pomiar tego typu już istnieje dla wybranej daty.",
|
||||
"deleteError": "Błąd usuwania",
|
||||
"invalidValue": "Wprowadź poprawną wartość.",
|
||||
@@ -860,10 +856,6 @@
|
||||
"legendMissed": "Pominięta",
|
||||
"emptyStateTitle": "Zarejestruj więcej pomiarów, aby zobaczyć trendy",
|
||||
"emptyStateDescription": "Zarejestruj co najmniej 3 wpisy, aby odblokować linię trendu.",
|
||||
"needMoreDistinctDaysTitle": "Potrzeba więcej różnych dni",
|
||||
"needMoreDistinctDaysDescription": "Rejestruj w więcej dni, aby odblokować linię trendu.",
|
||||
"noDataInRangeTitle": "Brak danych w tym zakresie",
|
||||
"noDataInRangeDescription": "Żadne pomiary nie mieszczą się w wybranym zakresie. Zmień zakres lub dodaj nowy pomiar.",
|
||||
"personalBaseline": "Twoja średnia",
|
||||
"deltaVsBaseline": "{delta} vs. twoja średnia",
|
||||
"deltaUnchanged": "odpowiada twojej średniej",
|
||||
@@ -1149,12 +1141,9 @@
|
||||
"settingsContextHint": "Opcjonalne bloki, na których coach może się opierać. Wyłączenie pominie blok w każdej odpowiedzi."
|
||||
},
|
||||
"relativeJustNow": "przed chwilą",
|
||||
"relativeMinutesAgoOne": "{count} minutę temu",
|
||||
"relativeMinutesAgoOther": "{count} minut temu",
|
||||
"relativeHoursAgoOne": "{count} godzinę temu",
|
||||
"relativeHoursAgoOther": "{count} godzin temu",
|
||||
"relativeDaysAgoOne": "{count} dzień temu",
|
||||
"relativeDaysAgoOther": "{count} dni temu",
|
||||
"relativeMinutesAgo": "{count} minut temu",
|
||||
"relativeHoursAgo": "{count} godzin temu",
|
||||
"relativeDaysAgo": "{count} dni temu",
|
||||
"navOverview": "Przegląd",
|
||||
"navBloodPressure": "Ciśnienie krwi",
|
||||
"navWeight": "Waga",
|
||||
@@ -1902,19 +1891,14 @@
|
||||
}
|
||||
},
|
||||
"integrationPill": {
|
||||
"connected": "Połączono",
|
||||
"errorReconnect": "Błąd — połącz ponownie",
|
||||
"warningServerError": "Połączono, błąd serwera",
|
||||
"parkedReconnect": "Wstrzymane — połącz ręcznie",
|
||||
"notConnected": "Nie połączono",
|
||||
"connected": "Connected",
|
||||
"errorReconnect": "Error — reconnect",
|
||||
"notConnected": "Not connected",
|
||||
"justNow": "just now",
|
||||
"minutesAgo": "{count} min ago",
|
||||
"hoursAgo": "{count} h ago",
|
||||
"daysAgo": "{count} d ago",
|
||||
"ariaLabel": "Integration status",
|
||||
"resumeCta": "Połącz ponownie",
|
||||
"resumeSuccess": "Integracja wznowiona",
|
||||
"resumeError": "Ponowne połączenie nieudane"
|
||||
"ariaLabel": "Integration status"
|
||||
},
|
||||
"apiTokens": "API Tokens",
|
||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* v1.4.43 QoL (L1) — pin the tightened 404 / global-error copy so a
|
||||
* future translator does not re-introduce the v1.4.27 marketing
|
||||
* paragraph. The 404 page now ships with i18n keys (`notFound.*`)
|
||||
* across all six locales; the global-error boundary remains static
|
||||
* bilingual (no i18n provider available at that level).
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const ROOT = join(__dirname, "../../..");
|
||||
const LOCALES = ["en", "de", "fr", "es", "it", "pl"] as const;
|
||||
|
||||
interface Messages {
|
||||
notFound: {
|
||||
title: string;
|
||||
backToDashboard: string;
|
||||
};
|
||||
}
|
||||
|
||||
function load(locale: string): Messages {
|
||||
const raw = readFileSync(join(ROOT, "messages", `${locale}.json`), "utf8");
|
||||
return JSON.parse(raw) as Messages;
|
||||
}
|
||||
|
||||
describe("notFound copy", () => {
|
||||
it.each(LOCALES)("%s: ships title + backToDashboard keys", (locale) => {
|
||||
const messages = load(locale);
|
||||
expect(messages.notFound.title.trim().length).toBeGreaterThan(0);
|
||||
expect(messages.notFound.backToDashboard.trim().length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it.each(LOCALES)(
|
||||
"%s: title is tight — under 60 characters, ends with a period",
|
||||
(locale) => {
|
||||
const messages = load(locale);
|
||||
// Tightened copy is a single short statement, e.g. "Diese Seite
|
||||
// existiert nicht." The 60-char ceiling pins out a future return
|
||||
// of the verbose marketing block.
|
||||
expect(messages.notFound.title.length).toBeLessThanOrEqual(60);
|
||||
expect(messages.notFound.title).toMatch(/\.$/);
|
||||
},
|
||||
);
|
||||
|
||||
it("de: matches the tightened copy exactly", () => {
|
||||
const messages = load("de");
|
||||
expect(messages.notFound.title).toBe("Diese Seite existiert nicht.");
|
||||
});
|
||||
|
||||
it("en: matches the tightened copy exactly", () => {
|
||||
const messages = load("en");
|
||||
expect(messages.notFound.title).toBe("This page doesn't exist.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("global-error bilingual copy", () => {
|
||||
// The global-error boundary cannot reach the i18n provider (root
|
||||
// layout has failed). The v1.4.43 audit asked for a bilingual
|
||||
// lockup so a German user does not read pure English in a bad-
|
||||
// state moment. Read the file and assert both languages appear.
|
||||
const globalErrorPath = join(ROOT, "src/app/global-error.tsx");
|
||||
const source = readFileSync(globalErrorPath, "utf8");
|
||||
|
||||
it("ships the German leading phrase next to the English fallback", () => {
|
||||
expect(source).toContain("Etwas ist schiefgegangen");
|
||||
expect(source).toContain("Something went wrong");
|
||||
});
|
||||
|
||||
it("ships the German error subtitle next to the English fallback", () => {
|
||||
expect(source).toContain("Ein kritischer Fehler ist aufgetreten");
|
||||
// The JSX-formatter wraps the English fallback across two lines, so
|
||||
// collapse all whitespace before searching.
|
||||
const collapsed = source.replace(/\s+/g, " ");
|
||||
expect(collapsed).toContain("A critical error occurred");
|
||||
});
|
||||
});
|
||||
+17
-12
@@ -3,9 +3,7 @@
|
||||
/**
|
||||
* Root-level error boundary. Renders when even the root layout itself fails,
|
||||
* so it cannot rely on any providers (i18n, query, auth). Keep it static,
|
||||
* self-contained, and bilingual: both English and German appear side by side
|
||||
* so a German visitor never lands on an all-English last-resort screen, and
|
||||
* an English visitor can still read it.
|
||||
* in English, and self-contained.
|
||||
*/
|
||||
export default function GlobalError({
|
||||
error,
|
||||
@@ -30,7 +28,7 @@ export default function GlobalError({
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} catch {
|
||||
window.prompt("Details kopieren · Copy details", text);
|
||||
window.prompt("Copy details", text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,14 +54,21 @@ export default function GlobalError({
|
||||
}}
|
||||
>
|
||||
<div style={{ maxWidth: 560 }}>
|
||||
{/*
|
||||
v1.4.43 QoL — bilingual lockup so a German user landing
|
||||
in this boundary doesn't read pure English in an already-
|
||||
bad-state moment. The root layout has failed by the time
|
||||
this fires, so no i18n provider is available; the static
|
||||
DE/EN pairing covers the dominant audience without a
|
||||
runtime cost. Copy tightened per the v1.4.43 audit (L1)
|
||||
to drop the verbose secondary sentence.
|
||||
*/}
|
||||
<h1 style={{ fontSize: 20, marginBottom: 12 }}>
|
||||
Etwas ist schiefgegangen · Something went wrong
|
||||
Etwas ist schiefgegangen / Something went wrong
|
||||
</h1>
|
||||
<p style={{ color: "#d0d0d0", marginBottom: 16, fontSize: 14 }}>
|
||||
Ein kritischer Fehler ist aufgetreten. Du kannst es erneut versuchen
|
||||
oder die Details kopieren.
|
||||
<br />A critical error occurred. You can retry or copy the details
|
||||
for support.
|
||||
Ein kritischer Fehler ist aufgetreten. / A critical error
|
||||
occurred.
|
||||
</p>
|
||||
<pre
|
||||
style={{
|
||||
@@ -76,7 +81,7 @@ export default function GlobalError({
|
||||
border: "1px solid #44475a",
|
||||
}}
|
||||
>
|
||||
{error.message || "Unbekannter Fehler · Unknown error"}
|
||||
{error.message || "Unknown error"}
|
||||
</pre>
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
<button
|
||||
@@ -96,7 +101,7 @@ export default function GlobalError({
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
Erneut versuchen · Retry
|
||||
Retry
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
@@ -111,7 +116,7 @@ export default function GlobalError({
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
Details kopieren · Copy details
|
||||
Copy details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+11
-9
@@ -13,14 +13,19 @@ import { getServerTranslator } from "@/lib/i18n/server-translator";
|
||||
* application error and doesn't need the bug-report path. Just a
|
||||
* branded splash with one link home.
|
||||
*
|
||||
* v1.4.43 H4 — the copy is locale-resolved server-side from the same
|
||||
* cookie / Accept-Language path the rest of the app uses, so a German
|
||||
* visitor no longer hits an English-only splash on a typo URL.
|
||||
*
|
||||
* Mobile-first: `min-h-dvh` follows the dynamic viewport so the
|
||||
* panel anchors centred under iOS Safari's animated URL bar;
|
||||
* `safe-area-inset-top` keeps the headline clear of the notch or
|
||||
* Dynamic Island.
|
||||
*
|
||||
* v1.4.43 QoL — copy lifted into i18n keys (`notFound.title` /
|
||||
* `notFound.backToDashboard`) and tightened. The previous
|
||||
* marketing-flavoured paragraph ("The page you were looking for…
|
||||
* Head back to the dashboard to pick up where you left off.") read
|
||||
* heavier than the surface warrants; the single-line statement
|
||||
* matches the existing voice of every other in-app error message.
|
||||
* Translations cover all six locales so a German user no longer
|
||||
* lands on English copy.
|
||||
*/
|
||||
export default async function NotFound() {
|
||||
const locale = await resolveServerLocale();
|
||||
@@ -39,17 +44,14 @@ export default async function NotFound() {
|
||||
404
|
||||
</p>
|
||||
<h1 className="text-2xl font-bold tracking-tight md:text-3xl">
|
||||
{t("errors.notFound.title")}
|
||||
{t("notFound.title")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
{t("errors.notFound.body")}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex min-h-11 items-center justify-center rounded-md px-5 text-sm font-medium transition-colors"
|
||||
>
|
||||
{t("errors.notFound.backToDashboard")}
|
||||
{t("notFound.backToDashboard")}
|
||||
</Link>
|
||||
</main>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user