mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +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",
|
"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."
|
"description": "Ein unerwarteter Fehler ist aufgetreten. Du kannst es erneut versuchen, die technischen Details kopieren oder einen Bug-Report öffnen."
|
||||||
},
|
},
|
||||||
"errors": {
|
"notFound": {
|
||||||
"notFound": {
|
"title": "Diese Seite existiert nicht.",
|
||||||
"title": "Seite nicht gefunden",
|
"backToDashboard": "Zurück zum Dashboard"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
@@ -353,7 +350,6 @@
|
|||||||
"notesPlaceholder": "z.B. nach dem Essen",
|
"notesPlaceholder": "z.B. nach dem Essen",
|
||||||
"formReset": "Formular zurücksetzen",
|
"formReset": "Formular zurücksetzen",
|
||||||
"saveError": "Fehler beim Speichern",
|
"saveError": "Fehler beim Speichern",
|
||||||
"loadError": "Fehler beim Laden",
|
|
||||||
"duplicateTimestamp": "Zum gewählten Zeitpunkt existiert bereits ein Messwert dieses Typs.",
|
"duplicateTimestamp": "Zum gewählten Zeitpunkt existiert bereits ein Messwert dieses Typs.",
|
||||||
"deleteError": "Fehler beim Löschen",
|
"deleteError": "Fehler beim Löschen",
|
||||||
"invalidValue": "Bitte einen gültigen Wert eingeben.",
|
"invalidValue": "Bitte einen gültigen Wert eingeben.",
|
||||||
@@ -860,10 +856,6 @@
|
|||||||
"legendMissed": "Verpasst",
|
"legendMissed": "Verpasst",
|
||||||
"emptyStateTitle": "Erfasse mehr Messungen, um Trends zu sehen",
|
"emptyStateTitle": "Erfasse mehr Messungen, um Trends zu sehen",
|
||||||
"emptyStateDescription": "Erfasse mindestens 3 Einträge, um die Trendlinie freizuschalten.",
|
"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",
|
"personalBaseline": "Dein Mittel",
|
||||||
"deltaVsBaseline": "{delta} vs. dein Mittel",
|
"deltaVsBaseline": "{delta} vs. dein Mittel",
|
||||||
"deltaUnchanged": "entspricht deinem Mittel",
|
"deltaUnchanged": "entspricht deinem Mittel",
|
||||||
@@ -1145,16 +1137,13 @@
|
|||||||
"feedbackThanks": "Danke für das Signal.",
|
"feedbackThanks": "Danke für das Signal.",
|
||||||
"dailyLimitTitle": "Heute-Limit erreicht",
|
"dailyLimitTitle": "Heute-Limit erreicht",
|
||||||
"dailyLimitBody": "Heute-Limit erreicht; Reset um 00:00 UTC.",
|
"dailyLimitBody": "Heute-Limit erreicht; Reset um 00:00 UTC.",
|
||||||
"providerRateLimitTitle": "Anbieter gedrosselt",
|
"providerRateLimitTitle": "Provider rate-limited",
|
||||||
"providerRateLimitBody": "Anbieter ist temporär überlastet; wieder verfügbar in ~5 min."
|
"providerRateLimitBody": "Provider ist temporär überlastet; Reset in ~5 min."
|
||||||
},
|
},
|
||||||
"relativeJustNow": "gerade eben",
|
"relativeJustNow": "gerade eben",
|
||||||
"relativeMinutesAgoOne": "vor {count} Minute",
|
"relativeMinutesAgo": "vor {count} Minuten",
|
||||||
"relativeMinutesAgoOther": "vor {count} Minuten",
|
"relativeHoursAgo": "vor {count} Stunden",
|
||||||
"relativeHoursAgoOne": "vor {count} Stunde",
|
"relativeDaysAgo": "vor {count} Tagen",
|
||||||
"relativeHoursAgoOther": "vor {count} Stunden",
|
|
||||||
"relativeDaysAgoOne": "vor {count} Tag",
|
|
||||||
"relativeDaysAgoOther": "vor {count} Tagen",
|
|
||||||
"navOverview": "Übersicht",
|
"navOverview": "Übersicht",
|
||||||
"navBloodPressure": "Blutdruck",
|
"navBloodPressure": "Blutdruck",
|
||||||
"navWeight": "Gewicht",
|
"navWeight": "Gewicht",
|
||||||
@@ -1631,7 +1620,7 @@
|
|||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"title": "Auswertungen",
|
"title": "Auswertungen",
|
||||||
"description": "Anbieter, Modell, API-Schlüssel."
|
"description": "Provider, Modell, API-Key."
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"title": "API & Tokens",
|
"title": "API & Tokens",
|
||||||
@@ -1769,9 +1758,9 @@
|
|||||||
"codexDisconnected": "ChatGPT-Verbindung getrennt.",
|
"codexDisconnected": "ChatGPT-Verbindung getrennt.",
|
||||||
"codexConnectionFailed": "ChatGPT-Verbindung fehlgeschlagen. Bitte versuche es erneut.",
|
"codexConnectionFailed": "ChatGPT-Verbindung fehlgeschlagen. Bitte versuche es erneut.",
|
||||||
"rawData": "Rohdaten mitsenden",
|
"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.",
|
"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 Anbieter kann dafür aber weniger fein auf Muster und kurzfristige Schwankungen eingehen.",
|
"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 Anbieter erhält zusätzlich anonymisierte Messpunkte der letzten 30 Tage für eine präzisere Auswertung.",
|
"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",
|
"regenerateInsights": "Reports neu generieren",
|
||||||
"regenerateSuccess": "Reports wurden neu generiert",
|
"regenerateSuccess": "Reports wurden neu generiert",
|
||||||
"regenerateRateLimit": "Bitte warte — du hast das stündliche Limit für Analysen erreicht.",
|
"regenerateRateLimit": "Bitte warte — du hast das stündliche Limit für Analysen erreicht.",
|
||||||
@@ -1904,17 +1893,12 @@
|
|||||||
"integrationPill": {
|
"integrationPill": {
|
||||||
"connected": "Verbunden",
|
"connected": "Verbunden",
|
||||||
"errorReconnect": "Fehler — neu verbinden",
|
"errorReconnect": "Fehler — neu verbinden",
|
||||||
"warningServerError": "Verbunden, aber Serverfehler",
|
|
||||||
"parkedReconnect": "Pausiert — manuell wieder verbinden",
|
|
||||||
"notConnected": "Nicht verbunden",
|
"notConnected": "Nicht verbunden",
|
||||||
"justNow": "gerade eben",
|
"justNow": "gerade eben",
|
||||||
"minutesAgo": "vor {count} min",
|
"minutesAgo": "vor {count} min",
|
||||||
"hoursAgo": "vor {count} Std.",
|
"hoursAgo": "vor {count} Std.",
|
||||||
"daysAgo": "vor {count} d",
|
"daysAgo": "vor {count} d",
|
||||||
"ariaLabel": "Integrationsstatus",
|
"ariaLabel": "Integrationsstatus"
|
||||||
"resumeCta": "Wieder verbinden",
|
|
||||||
"resumeSuccess": "Integration wieder aktiv",
|
|
||||||
"resumeError": "Wieder verbinden fehlgeschlagen"
|
|
||||||
},
|
},
|
||||||
"apiTokens": "API-Tokens",
|
"apiTokens": "API-Tokens",
|
||||||
"apiTokensDescription": "Erstelle API-Tokens für die externe Medikamenten-Einnahme (z.B. Shortcuts, Automationen).",
|
"apiTokensDescription": "Erstelle API-Tokens für die externe Medikamenten-Einnahme (z.B. Shortcuts, Automationen).",
|
||||||
|
|||||||
+7
-23
@@ -152,12 +152,9 @@
|
|||||||
"title": "Something went wrong",
|
"title": "Something went wrong",
|
||||||
"description": "An unexpected error occurred. You can retry, copy the technical details, or open a bug report."
|
"description": "An unexpected error occurred. You can retry, copy the technical details, or open a bug report."
|
||||||
},
|
},
|
||||||
"errors": {
|
"notFound": {
|
||||||
"notFound": {
|
"title": "This page doesn't exist.",
|
||||||
"title": "Page not found",
|
"backToDashboard": "Back to dashboard"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
@@ -353,7 +350,6 @@
|
|||||||
"notesPlaceholder": "e.g. after a meal",
|
"notesPlaceholder": "e.g. after a meal",
|
||||||
"formReset": "Reset form",
|
"formReset": "Reset form",
|
||||||
"saveError": "Error saving",
|
"saveError": "Error saving",
|
||||||
"loadError": "Could not load measurements",
|
|
||||||
"duplicateTimestamp": "A measurement of this type already exists at the selected timestamp.",
|
"duplicateTimestamp": "A measurement of this type already exists at the selected timestamp.",
|
||||||
"deleteError": "Error deleting",
|
"deleteError": "Error deleting",
|
||||||
"invalidValue": "Please enter a valid value.",
|
"invalidValue": "Please enter a valid value.",
|
||||||
@@ -860,10 +856,6 @@
|
|||||||
"legendMissed": "Missed",
|
"legendMissed": "Missed",
|
||||||
"emptyStateTitle": "Add more measurements to see trends",
|
"emptyStateTitle": "Add more measurements to see trends",
|
||||||
"emptyStateDescription": "Log at least 3 entries to unlock the trend line.",
|
"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",
|
"personalBaseline": "Your normal",
|
||||||
"deltaVsBaseline": "{delta} vs. your normal",
|
"deltaVsBaseline": "{delta} vs. your normal",
|
||||||
"deltaUnchanged": "matches your normal",
|
"deltaUnchanged": "matches your normal",
|
||||||
@@ -1149,12 +1141,9 @@
|
|||||||
"providerRateLimitBody": "Provider temporarily rate-limited; retry in ~5 min."
|
"providerRateLimitBody": "Provider temporarily rate-limited; retry in ~5 min."
|
||||||
},
|
},
|
||||||
"relativeJustNow": "just now",
|
"relativeJustNow": "just now",
|
||||||
"relativeMinutesAgoOne": "{count} minute ago",
|
"relativeMinutesAgo": "{count} minutes ago",
|
||||||
"relativeMinutesAgoOther": "{count} minutes ago",
|
"relativeHoursAgo": "{count} hours ago",
|
||||||
"relativeHoursAgoOne": "{count} hour ago",
|
"relativeDaysAgo": "{count} days ago",
|
||||||
"relativeHoursAgoOther": "{count} hours ago",
|
|
||||||
"relativeDaysAgoOne": "{count} day ago",
|
|
||||||
"relativeDaysAgoOther": "{count} days ago",
|
|
||||||
"navOverview": "Overview",
|
"navOverview": "Overview",
|
||||||
"navBloodPressure": "Blood Pressure",
|
"navBloodPressure": "Blood Pressure",
|
||||||
"navWeight": "Weight",
|
"navWeight": "Weight",
|
||||||
@@ -1904,17 +1893,12 @@
|
|||||||
"integrationPill": {
|
"integrationPill": {
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
"errorReconnect": "Error — reconnect",
|
"errorReconnect": "Error — reconnect",
|
||||||
"warningServerError": "Connected, server error",
|
|
||||||
"parkedReconnect": "Paused — reconnect manually",
|
|
||||||
"notConnected": "Not connected",
|
"notConnected": "Not connected",
|
||||||
"justNow": "just now",
|
"justNow": "just now",
|
||||||
"minutesAgo": "{count} min ago",
|
"minutesAgo": "{count} min ago",
|
||||||
"hoursAgo": "{count} h ago",
|
"hoursAgo": "{count} h ago",
|
||||||
"daysAgo": "{count} d ago",
|
"daysAgo": "{count} d ago",
|
||||||
"ariaLabel": "Integration status",
|
"ariaLabel": "Integration status"
|
||||||
"resumeCta": "Reconnect",
|
|
||||||
"resumeSuccess": "Integration resumed",
|
|
||||||
"resumeError": "Reconnect failed"
|
|
||||||
},
|
},
|
||||||
"apiTokens": "API Tokens",
|
"apiTokens": "API Tokens",
|
||||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||||
|
|||||||
+10
-26
@@ -152,12 +152,9 @@
|
|||||||
"title": "Algo ha salido mal",
|
"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."
|
"description": "Se ha producido un error inesperado. Puedes reintentar, copiar los detalles técnicos o abrir un reporte de bug."
|
||||||
},
|
},
|
||||||
"errors": {
|
"notFound": {
|
||||||
"notFound": {
|
"title": "Esta página no existe.",
|
||||||
"title": "Página no encontrada",
|
"backToDashboard": "Volver al panel"
|
||||||
"body": "La página que buscas no existe o se ha movido. Vuelve al panel para continuar donde lo dejaste.",
|
|
||||||
"backToDashboard": "Volver al panel"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"dashboard": "Panel",
|
"dashboard": "Panel",
|
||||||
@@ -353,7 +350,6 @@
|
|||||||
"notesPlaceholder": "p. ej. después de comer",
|
"notesPlaceholder": "p. ej. después de comer",
|
||||||
"formReset": "Restablecer formulario",
|
"formReset": "Restablecer formulario",
|
||||||
"saveError": "Error al guardar",
|
"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.",
|
"duplicateTimestamp": "Ya existe una medición de este tipo en la marca de tiempo seleccionada.",
|
||||||
"deleteError": "Error al eliminar",
|
"deleteError": "Error al eliminar",
|
||||||
"invalidValue": "Introduce un valor válido.",
|
"invalidValue": "Introduce un valor válido.",
|
||||||
@@ -860,10 +856,6 @@
|
|||||||
"legendMissed": "Omitida",
|
"legendMissed": "Omitida",
|
||||||
"emptyStateTitle": "Registra más mediciones para ver tendencias",
|
"emptyStateTitle": "Registra más mediciones para ver tendencias",
|
||||||
"emptyStateDescription": "Registra al menos 3 entradas para desbloquear la línea de tendencia.",
|
"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",
|
"personalBaseline": "Tu media",
|
||||||
"deltaVsBaseline": "{delta} vs. tu media",
|
"deltaVsBaseline": "{delta} vs. tu media",
|
||||||
"deltaUnchanged": "coincide con 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."
|
"settingsContextHint": "Bloques opcionales en los que el coach puede apoyarse. Si desactivas alguno, se omitirá en cada respuesta."
|
||||||
},
|
},
|
||||||
"relativeJustNow": "ahora mismo",
|
"relativeJustNow": "ahora mismo",
|
||||||
"relativeMinutesAgoOne": "hace {count} minuto",
|
"relativeMinutesAgo": "hace {count} minutos",
|
||||||
"relativeMinutesAgoOther": "hace {count} minutos",
|
"relativeHoursAgo": "hace {count} horas",
|
||||||
"relativeHoursAgoOne": "hace {count} hora",
|
"relativeDaysAgo": "hace {count} días",
|
||||||
"relativeHoursAgoOther": "hace {count} horas",
|
|
||||||
"relativeDaysAgoOne": "hace {count} día",
|
|
||||||
"relativeDaysAgoOther": "hace {count} días",
|
|
||||||
"navOverview": "Resumen",
|
"navOverview": "Resumen",
|
||||||
"navBloodPressure": "Presión arterial",
|
"navBloodPressure": "Presión arterial",
|
||||||
"navWeight": "Peso",
|
"navWeight": "Peso",
|
||||||
@@ -1902,19 +1891,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"integrationPill": {
|
"integrationPill": {
|
||||||
"connected": "Conectado",
|
"connected": "Connected",
|
||||||
"errorReconnect": "Error — reconectar",
|
"errorReconnect": "Error — reconnect",
|
||||||
"warningServerError": "Conectado, error del servidor",
|
"notConnected": "Not connected",
|
||||||
"parkedReconnect": "Pausado — reconectar manualmente",
|
|
||||||
"notConnected": "No conectado",
|
|
||||||
"justNow": "just now",
|
"justNow": "just now",
|
||||||
"minutesAgo": "{count} min ago",
|
"minutesAgo": "{count} min ago",
|
||||||
"hoursAgo": "{count} h ago",
|
"hoursAgo": "{count} h ago",
|
||||||
"daysAgo": "{count} d ago",
|
"daysAgo": "{count} d ago",
|
||||||
"ariaLabel": "Integration status",
|
"ariaLabel": "Integration status"
|
||||||
"resumeCta": "Reconectar",
|
|
||||||
"resumeSuccess": "Integración reanudada",
|
|
||||||
"resumeError": "Reconexión fallida"
|
|
||||||
},
|
},
|
||||||
"apiTokens": "API Tokens",
|
"apiTokens": "API Tokens",
|
||||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||||
|
|||||||
+10
-26
@@ -152,12 +152,9 @@
|
|||||||
"title": "Une erreur est survenue",
|
"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."
|
"description": "Une erreur inattendue est survenue. Tu peux réessayer, copier les détails techniques ou ouvrir un rapport de bug."
|
||||||
},
|
},
|
||||||
"errors": {
|
"notFound": {
|
||||||
"notFound": {
|
"title": "Cette page n'existe pas.",
|
||||||
"title": "Page introuvable",
|
"backToDashboard": "Retour au tableau de bord"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"dashboard": "Tableau de bord",
|
"dashboard": "Tableau de bord",
|
||||||
@@ -353,7 +350,6 @@
|
|||||||
"notesPlaceholder": "p. ex. après le repas",
|
"notesPlaceholder": "p. ex. après le repas",
|
||||||
"formReset": "Réinitialiser le formulaire",
|
"formReset": "Réinitialiser le formulaire",
|
||||||
"saveError": "Erreur lors de l’enregistrement",
|
"saveError": "Erreur lors de l’enregistrement",
|
||||||
"loadError": "Impossible de charger les mesures",
|
|
||||||
"duplicateTimestamp": "Une mesure de ce type existe déjà à cet horodatage.",
|
"duplicateTimestamp": "Une mesure de ce type existe déjà à cet horodatage.",
|
||||||
"deleteError": "Erreur lors de la suppression",
|
"deleteError": "Erreur lors de la suppression",
|
||||||
"invalidValue": "Saisis une valeur valide.",
|
"invalidValue": "Saisis une valeur valide.",
|
||||||
@@ -860,10 +856,6 @@
|
|||||||
"legendMissed": "Manquée",
|
"legendMissed": "Manquée",
|
||||||
"emptyStateTitle": "Saisis plus de mesures pour voir les tendances",
|
"emptyStateTitle": "Saisis plus de mesures pour voir les tendances",
|
||||||
"emptyStateDescription": "Saisis au moins 3 entrées pour débloquer la courbe de tendance.",
|
"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",
|
"personalBaseline": "Ta moyenne",
|
||||||
"deltaVsBaseline": "{delta} vs. ta moyenne",
|
"deltaVsBaseline": "{delta} vs. ta moyenne",
|
||||||
"deltaUnchanged": "correspond à 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."
|
"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",
|
"relativeJustNow": "à l’instant",
|
||||||
"relativeMinutesAgoOne": "il y a {count} minute",
|
"relativeMinutesAgo": "il y a {count} minutes",
|
||||||
"relativeMinutesAgoOther": "il y a {count} minutes",
|
"relativeHoursAgo": "il y a {count} heures",
|
||||||
"relativeHoursAgoOne": "il y a {count} heure",
|
"relativeDaysAgo": "il y a {count} jours",
|
||||||
"relativeHoursAgoOther": "il y a {count} heures",
|
|
||||||
"relativeDaysAgoOne": "il y a {count} jour",
|
|
||||||
"relativeDaysAgoOther": "il y a {count} jours",
|
|
||||||
"navOverview": "Vue d'ensemble",
|
"navOverview": "Vue d'ensemble",
|
||||||
"navBloodPressure": "Tension artérielle",
|
"navBloodPressure": "Tension artérielle",
|
||||||
"navWeight": "Poids",
|
"navWeight": "Poids",
|
||||||
@@ -1902,19 +1891,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"integrationPill": {
|
"integrationPill": {
|
||||||
"connected": "Connecté",
|
"connected": "Connected",
|
||||||
"errorReconnect": "Erreur — reconnecter",
|
"errorReconnect": "Error — reconnect",
|
||||||
"warningServerError": "Connecté, erreur serveur",
|
"notConnected": "Not connected",
|
||||||
"parkedReconnect": "En pause — reconnecter manuellement",
|
|
||||||
"notConnected": "Non connecté",
|
|
||||||
"justNow": "just now",
|
"justNow": "just now",
|
||||||
"minutesAgo": "{count} min ago",
|
"minutesAgo": "{count} min ago",
|
||||||
"hoursAgo": "{count} h ago",
|
"hoursAgo": "{count} h ago",
|
||||||
"daysAgo": "{count} d ago",
|
"daysAgo": "{count} d ago",
|
||||||
"ariaLabel": "Integration status",
|
"ariaLabel": "Integration status"
|
||||||
"resumeCta": "Reconnecter",
|
|
||||||
"resumeSuccess": "Intégration reprise",
|
|
||||||
"resumeError": "Échec de la reconnexion"
|
|
||||||
},
|
},
|
||||||
"apiTokens": "API Tokens",
|
"apiTokens": "API Tokens",
|
||||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||||
|
|||||||
+10
-26
@@ -152,12 +152,9 @@
|
|||||||
"title": "Qualcosa è andato storto",
|
"title": "Qualcosa è andato storto",
|
||||||
"description": "Si è verificato un errore inatteso. Puoi riprovare, copiare i dettagli tecnici o aprire una segnalazione di bug."
|
"description": "Si è verificato un errore inatteso. Puoi riprovare, copiare i dettagli tecnici o aprire una segnalazione di bug."
|
||||||
},
|
},
|
||||||
"errors": {
|
"notFound": {
|
||||||
"notFound": {
|
"title": "Questa pagina non esiste.",
|
||||||
"title": "Pagina non trovata",
|
"backToDashboard": "Torna alla dashboard"
|
||||||
"body": "La pagina che cercavi non esiste o è stata spostata. Torna alla dashboard per riprendere da dove eri rimasto.",
|
|
||||||
"backToDashboard": "Torna alla dashboard"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
@@ -353,7 +350,6 @@
|
|||||||
"notesPlaceholder": "es. dopo il pasto",
|
"notesPlaceholder": "es. dopo il pasto",
|
||||||
"formReset": "Reimposta modulo",
|
"formReset": "Reimposta modulo",
|
||||||
"saveError": "Errore durante il salvataggio",
|
"saveError": "Errore durante il salvataggio",
|
||||||
"loadError": "Impossibile caricare le misurazioni",
|
|
||||||
"duplicateTimestamp": "Esiste già una misurazione di questo tipo a questa marca temporale.",
|
"duplicateTimestamp": "Esiste già una misurazione di questo tipo a questa marca temporale.",
|
||||||
"deleteError": "Errore durante l’eliminazione",
|
"deleteError": "Errore durante l’eliminazione",
|
||||||
"invalidValue": "Inserisci un valore valido.",
|
"invalidValue": "Inserisci un valore valido.",
|
||||||
@@ -860,10 +856,6 @@
|
|||||||
"legendMissed": "Mancata",
|
"legendMissed": "Mancata",
|
||||||
"emptyStateTitle": "Registra altre misurazioni per vedere i trend",
|
"emptyStateTitle": "Registra altre misurazioni per vedere i trend",
|
||||||
"emptyStateDescription": "Registra almeno 3 voci per sbloccare la linea di tendenza.",
|
"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",
|
"personalBaseline": "La tua media",
|
||||||
"deltaVsBaseline": "{delta} vs. la tua media",
|
"deltaVsBaseline": "{delta} vs. la tua media",
|
||||||
"deltaUnchanged": "corrisponde alla 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."
|
"settingsContextHint": "Blocchi opzionali su cui il coach può basarsi. Disattivandone uno, viene omesso da ogni risposta."
|
||||||
},
|
},
|
||||||
"relativeJustNow": "proprio ora",
|
"relativeJustNow": "proprio ora",
|
||||||
"relativeMinutesAgoOne": "{count} minuto fa",
|
"relativeMinutesAgo": "{count} minuti fa",
|
||||||
"relativeMinutesAgoOther": "{count} minuti fa",
|
"relativeHoursAgo": "{count} ore fa",
|
||||||
"relativeHoursAgoOne": "{count} ora fa",
|
"relativeDaysAgo": "{count} giorni fa",
|
||||||
"relativeHoursAgoOther": "{count} ore fa",
|
|
||||||
"relativeDaysAgoOne": "{count} giorno fa",
|
|
||||||
"relativeDaysAgoOther": "{count} giorni fa",
|
|
||||||
"navOverview": "Panoramica",
|
"navOverview": "Panoramica",
|
||||||
"navBloodPressure": "Pressione arteriosa",
|
"navBloodPressure": "Pressione arteriosa",
|
||||||
"navWeight": "Peso",
|
"navWeight": "Peso",
|
||||||
@@ -1902,19 +1891,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"integrationPill": {
|
"integrationPill": {
|
||||||
"connected": "Connesso",
|
"connected": "Connected",
|
||||||
"errorReconnect": "Errore — riconnetti",
|
"errorReconnect": "Error — reconnect",
|
||||||
"warningServerError": "Connesso, errore del server",
|
"notConnected": "Not connected",
|
||||||
"parkedReconnect": "In pausa — riconnettere manualmente",
|
|
||||||
"notConnected": "Non connesso",
|
|
||||||
"justNow": "just now",
|
"justNow": "just now",
|
||||||
"minutesAgo": "{count} min ago",
|
"minutesAgo": "{count} min ago",
|
||||||
"hoursAgo": "{count} h ago",
|
"hoursAgo": "{count} h ago",
|
||||||
"daysAgo": "{count} d ago",
|
"daysAgo": "{count} d ago",
|
||||||
"ariaLabel": "Integration status",
|
"ariaLabel": "Integration status"
|
||||||
"resumeCta": "Riconnetti",
|
|
||||||
"resumeSuccess": "Integrazione ripresa",
|
|
||||||
"resumeError": "Riconnessione fallita"
|
|
||||||
},
|
},
|
||||||
"apiTokens": "API Tokens",
|
"apiTokens": "API Tokens",
|
||||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
||||||
|
|||||||
+10
-26
@@ -152,12 +152,9 @@
|
|||||||
"title": "Coś poszło nie tak",
|
"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."
|
"description": "Wystąpił nieoczekiwany błąd. Możesz spróbować ponownie, skopiować szczegóły techniczne lub otworzyć zgłoszenie błędu."
|
||||||
},
|
},
|
||||||
"errors": {
|
"notFound": {
|
||||||
"notFound": {
|
"title": "Ta strona nie istnieje.",
|
||||||
"title": "Nie znaleziono strony",
|
"backToDashboard": "Powrót do panelu"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"dashboard": "Panel",
|
"dashboard": "Panel",
|
||||||
@@ -353,7 +350,6 @@
|
|||||||
"notesPlaceholder": "np. po jedzeniu",
|
"notesPlaceholder": "np. po jedzeniu",
|
||||||
"formReset": "Resetuj formularz",
|
"formReset": "Resetuj formularz",
|
||||||
"saveError": "Błąd zapisu",
|
"saveError": "Błąd zapisu",
|
||||||
"loadError": "Nie udało się wczytać pomiarów",
|
|
||||||
"duplicateTimestamp": "Pomiar tego typu już istnieje dla wybranej daty.",
|
"duplicateTimestamp": "Pomiar tego typu już istnieje dla wybranej daty.",
|
||||||
"deleteError": "Błąd usuwania",
|
"deleteError": "Błąd usuwania",
|
||||||
"invalidValue": "Wprowadź poprawną wartość.",
|
"invalidValue": "Wprowadź poprawną wartość.",
|
||||||
@@ -860,10 +856,6 @@
|
|||||||
"legendMissed": "Pominięta",
|
"legendMissed": "Pominięta",
|
||||||
"emptyStateTitle": "Zarejestruj więcej pomiarów, aby zobaczyć trendy",
|
"emptyStateTitle": "Zarejestruj więcej pomiarów, aby zobaczyć trendy",
|
||||||
"emptyStateDescription": "Zarejestruj co najmniej 3 wpisy, aby odblokować linię trendu.",
|
"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",
|
"personalBaseline": "Twoja średnia",
|
||||||
"deltaVsBaseline": "{delta} vs. twoja średnia",
|
"deltaVsBaseline": "{delta} vs. twoja średnia",
|
||||||
"deltaUnchanged": "odpowiada twojej średniej",
|
"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."
|
"settingsContextHint": "Opcjonalne bloki, na których coach może się opierać. Wyłączenie pominie blok w każdej odpowiedzi."
|
||||||
},
|
},
|
||||||
"relativeJustNow": "przed chwilą",
|
"relativeJustNow": "przed chwilą",
|
||||||
"relativeMinutesAgoOne": "{count} minutę temu",
|
"relativeMinutesAgo": "{count} minut temu",
|
||||||
"relativeMinutesAgoOther": "{count} minut temu",
|
"relativeHoursAgo": "{count} godzin temu",
|
||||||
"relativeHoursAgoOne": "{count} godzinę temu",
|
"relativeDaysAgo": "{count} dni temu",
|
||||||
"relativeHoursAgoOther": "{count} godzin temu",
|
|
||||||
"relativeDaysAgoOne": "{count} dzień temu",
|
|
||||||
"relativeDaysAgoOther": "{count} dni temu",
|
|
||||||
"navOverview": "Przegląd",
|
"navOverview": "Przegląd",
|
||||||
"navBloodPressure": "Ciśnienie krwi",
|
"navBloodPressure": "Ciśnienie krwi",
|
||||||
"navWeight": "Waga",
|
"navWeight": "Waga",
|
||||||
@@ -1902,19 +1891,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"integrationPill": {
|
"integrationPill": {
|
||||||
"connected": "Połączono",
|
"connected": "Connected",
|
||||||
"errorReconnect": "Błąd — połącz ponownie",
|
"errorReconnect": "Error — reconnect",
|
||||||
"warningServerError": "Połączono, błąd serwera",
|
"notConnected": "Not connected",
|
||||||
"parkedReconnect": "Wstrzymane — połącz ręcznie",
|
|
||||||
"notConnected": "Nie połączono",
|
|
||||||
"justNow": "just now",
|
"justNow": "just now",
|
||||||
"minutesAgo": "{count} min ago",
|
"minutesAgo": "{count} min ago",
|
||||||
"hoursAgo": "{count} h ago",
|
"hoursAgo": "{count} h ago",
|
||||||
"daysAgo": "{count} d ago",
|
"daysAgo": "{count} d ago",
|
||||||
"ariaLabel": "Integration status",
|
"ariaLabel": "Integration status"
|
||||||
"resumeCta": "Połącz ponownie",
|
|
||||||
"resumeSuccess": "Integracja wznowiona",
|
|
||||||
"resumeError": "Ponowne połączenie nieudane"
|
|
||||||
},
|
},
|
||||||
"apiTokens": "API Tokens",
|
"apiTokens": "API Tokens",
|
||||||
"apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).",
|
"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,
|
* 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,
|
* 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
|
* in English, and self-contained.
|
||||||
* so a German visitor never lands on an all-English last-resort screen, and
|
|
||||||
* an English visitor can still read it.
|
|
||||||
*/
|
*/
|
||||||
export default function GlobalError({
|
export default function GlobalError({
|
||||||
error,
|
error,
|
||||||
@@ -30,7 +28,7 @@ export default function GlobalError({
|
|||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
} catch {
|
} 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 }}>
|
<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 }}>
|
<h1 style={{ fontSize: 20, marginBottom: 12 }}>
|
||||||
Etwas ist schiefgegangen · Something went wrong
|
Etwas ist schiefgegangen / Something went wrong
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ color: "#d0d0d0", marginBottom: 16, fontSize: 14 }}>
|
<p style={{ color: "#d0d0d0", marginBottom: 16, fontSize: 14 }}>
|
||||||
Ein kritischer Fehler ist aufgetreten. Du kannst es erneut versuchen
|
Ein kritischer Fehler ist aufgetreten. / A critical error
|
||||||
oder die Details kopieren.
|
occurred.
|
||||||
<br />A critical error occurred. You can retry or copy the details
|
|
||||||
for support.
|
|
||||||
</p>
|
</p>
|
||||||
<pre
|
<pre
|
||||||
style={{
|
style={{
|
||||||
@@ -76,7 +81,7 @@ export default function GlobalError({
|
|||||||
border: "1px solid #44475a",
|
border: "1px solid #44475a",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{error.message || "Unbekannter Fehler · Unknown error"}
|
{error.message || "Unknown error"}
|
||||||
</pre>
|
</pre>
|
||||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||||
<button
|
<button
|
||||||
@@ -96,7 +101,7 @@ export default function GlobalError({
|
|||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Erneut versuchen · Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
@@ -111,7 +116,7 @@ export default function GlobalError({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Details kopieren · Copy details
|
Copy details
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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
|
* application error and doesn't need the bug-report path. Just a
|
||||||
* branded splash with one link home.
|
* 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
|
* Mobile-first: `min-h-dvh` follows the dynamic viewport so the
|
||||||
* panel anchors centred under iOS Safari's animated URL bar;
|
* panel anchors centred under iOS Safari's animated URL bar;
|
||||||
* `safe-area-inset-top` keeps the headline clear of the notch or
|
* `safe-area-inset-top` keeps the headline clear of the notch or
|
||||||
* Dynamic Island.
|
* 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() {
|
export default async function NotFound() {
|
||||||
const locale = await resolveServerLocale();
|
const locale = await resolveServerLocale();
|
||||||
@@ -39,17 +44,14 @@ export default async function NotFound() {
|
|||||||
404
|
404
|
||||||
</p>
|
</p>
|
||||||
<h1 className="text-2xl font-bold tracking-tight md:text-3xl">
|
<h1 className="text-2xl font-bold tracking-tight md:text-3xl">
|
||||||
{t("errors.notFound.title")}
|
{t("notFound.title")}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
||||||
{t("errors.notFound.body")}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
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"
|
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>
|
</Link>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user