mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
fix(import): translate machine-readable import failure reasons
A user whose import died to a worker restart saw the literal string interrupted_by_restart in the import card — the reconcile writes it as a machine-readable code, and the card echoed it raw. Classify the two coded reasons (interrupted_by_restart, the insufficient_memory prefix from the memory preflight) onto translated, actionable copy across all six locales; the copy tells the user their data is safe and to upload the export again, which the re-enabled card already supports. Honest free-text reasons keep passing through verbatim. Refs #775.
This commit is contained in:
1 parent
50ff90a409
commit
d26bbca52a
8 files changed
+86
-2
No files matched your search
@@ -4984,6 +4984,8 @@
|
||||
"rateLimited": "Zu viele Uploads. Bitte kurz warten und erneut versuchen.",
|
||||
"tooLarge": "Diese Datei ist zu groß (Grenze 1,5 GB).",
|
||||
"failed": "Der Import ist fehlgeschlagen.",
|
||||
"failureInterrupted": "Der Import wurde unterbrochen, bevor er fertig war, meist durch einen Server-Neustart oder Speichermangel. Die Daten sind sicher; das Archiv einfach erneut hochladen.",
|
||||
"failureInsufficientMemory": "Das Speicherlimit des Servers ist für ein Archiv dieser Größe zu niedrig. Bitte die Betreiberin oder den Betreiber das Node.js-Heap-Limit erhöhen lassen (NODE_OPTIONS=--max-old-space-size) und das Archiv danach erneut hochladen.",
|
||||
"rowsImported": "Bisher {count} Einträge importiert",
|
||||
"doneSummary": "Fertig — {imported} von {read} Datensätzen importiert, {skipped} klinische Datensätze übersprungen.",
|
||||
"doneNothing": "Aus diesem Archiv wurde nichts importiert: 0 von {read} Datensätzen sind angekommen.",
|
||||
|
||||
@@ -4984,6 +4984,8 @@
|
||||
"rateLimited": "Too many uploads. Please wait a moment and try again.",
|
||||
"tooLarge": "That file is too large (limit 1.5 GB).",
|
||||
"failed": "The import failed.",
|
||||
"failureInterrupted": "The import was interrupted before it finished, usually by a server restart or an out-of-memory stop. Your data is safe; upload the export again to retry.",
|
||||
"failureInsufficientMemory": "The server's memory limit is too low for an archive this size. Ask whoever runs the server to raise the Node.js heap limit (NODE_OPTIONS=--max-old-space-size), then upload the export again.",
|
||||
"rowsImported": "{count} rows imported so far",
|
||||
"doneSummary": "Done — {imported} of {read} records imported, {skipped} clinical records skipped.",
|
||||
"doneNothing": "Nothing was imported from that archive: 0 of {read} records landed.",
|
||||
|
||||
@@ -4984,6 +4984,8 @@
|
||||
"rateLimited": "Demasiadas subidas. Espera un momento e inténtalo de nuevo.",
|
||||
"tooLarge": "El archivo es demasiado grande (límite 1,5 GB).",
|
||||
"failed": "La importación falló.",
|
||||
"failureInterrupted": "La importación se interrumpió antes de terminar, normalmente por un reinicio del servidor o por falta de memoria. Tus datos están a salvo; vuelve a subir el archivo para reintentarlo.",
|
||||
"failureInsufficientMemory": "El límite de memoria del servidor es demasiado bajo para un archivo de este tamaño. Pide a quien administra el servidor que aumente el límite de memoria de Node.js (NODE_OPTIONS=--max-old-space-size) y vuelve a subir el archivo.",
|
||||
"rowsImported": "{count} filas importadas hasta ahora",
|
||||
"doneSummary": "Listo: {imported} de {read} registros importados, {skipped} registros clínicos omitidos.",
|
||||
"doneNothing": "No se importó nada de ese archivo: llegaron 0 de {read} registros.",
|
||||
|
||||
@@ -4984,6 +4984,8 @@
|
||||
"rateLimited": "Trop d'envois. Patientez un instant et réessayez.",
|
||||
"tooLarge": "Ce fichier est trop volumineux (limite 1,5 Go).",
|
||||
"failed": "L'import a échoué.",
|
||||
"failureInterrupted": "L'import a été interrompu avant la fin, généralement par un redémarrage du serveur ou un manque de mémoire. Vos données sont en sécurité ; renvoyez l'archive pour réessayer.",
|
||||
"failureInsufficientMemory": "La limite de mémoire du serveur est trop basse pour une archive de cette taille. Demandez à la personne qui administre le serveur d'augmenter la limite de mémoire de Node.js (NODE_OPTIONS=--max-old-space-size), puis renvoyez l'archive.",
|
||||
"rowsImported": "{count} lignes importées jusqu'ici",
|
||||
"doneSummary": "Terminé — {imported} enregistrements sur {read} importés, {skipped} enregistrements cliniques ignorés.",
|
||||
"doneNothing": "Rien n'a été importé de cette archive : 0 enregistrement sur {read} est arrivé.",
|
||||
|
||||
@@ -4984,6 +4984,8 @@
|
||||
"rateLimited": "Troppi caricamenti. Attendi un momento e riprova.",
|
||||
"tooLarge": "Il file è troppo grande (limite 1,5 GB).",
|
||||
"failed": "L'importazione non è riuscita.",
|
||||
"failureInterrupted": "L'importazione è stata interrotta prima di finire, di solito per un riavvio del server o per memoria esaurita. I dati sono al sicuro; ricarica l'archivio per riprovare.",
|
||||
"failureInsufficientMemory": "Il limite di memoria del server è troppo basso per un archivio di queste dimensioni. Chiedi a chi gestisce il server di aumentare il limite di memoria di Node.js (NODE_OPTIONS=--max-old-space-size), poi ricarica l'archivio.",
|
||||
"rowsImported": "{count} righe importate finora",
|
||||
"doneSummary": "Fatto: {imported} di {read} record importati, {skipped} record clinici saltati.",
|
||||
"doneNothing": "Da questo archivio non è stato importato nulla: sono arrivati 0 record su {read}.",
|
||||
|
||||
@@ -4984,6 +4984,8 @@
|
||||
"rateLimited": "Zbyt wiele przesłań. Poczekaj chwilę i spróbuj ponownie.",
|
||||
"tooLarge": "Ten plik jest za duży (limit 1,5 GB).",
|
||||
"failed": "Import nie powiódł się.",
|
||||
"failureInterrupted": "Import został przerwany przed zakończeniem, zwykle przez restart serwera lub brak pamięci. Dane są bezpieczne; prześlij archiwum ponownie, aby spróbować jeszcze raz.",
|
||||
"failureInsufficientMemory": "Limit pamięci serwera jest zbyt niski dla archiwum tej wielkości. Poproś osobę administrującą serwerem o zwiększenie limitu pamięci Node.js (NODE_OPTIONS=--max-old-space-size), a następnie prześlij archiwum ponownie.",
|
||||
"rowsImported": "Zaimportowano dotąd {count} wierszy",
|
||||
"doneSummary": "Gotowe — zaimportowano {imported} z {read} rekordów, pominięto {skipped} rekordów klinicznych.",
|
||||
"doneNothing": "Z tego archiwum nic nie zaimportowano: dotarło 0 z {read} rekordów.",
|
||||
|
||||
@@ -27,7 +27,10 @@ vi.mock("next/navigation", () => ({
|
||||
}));
|
||||
|
||||
import type { Locale } from "@/lib/i18n/config";
|
||||
import { AppleHealthEstimateWarning } from "../import-panel/apple-health-import-card";
|
||||
import {
|
||||
AppleHealthEstimateWarning,
|
||||
appleHealthFailureKind,
|
||||
} from "../import-panel/apple-health-import-card";
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import {
|
||||
ImportPanel,
|
||||
@@ -116,6 +119,40 @@ describe("<AppleHealthEstimateWarning>", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// (issue #775) — the worker writes two machine-readable failure
|
||||
// reasons (the reconcile's `interrupted_by_restart` code and the memory
|
||||
// preflight's `insufficient_memory` prefix). The card must translate
|
||||
// them instead of echoing a raw code at the user; honest free-text
|
||||
// reasons keep passing through verbatim.
|
||||
describe("appleHealthFailureKind", () => {
|
||||
it("maps the reconcile code onto translated copy", () => {
|
||||
expect(appleHealthFailureKind("interrupted_by_restart")).toBe(
|
||||
"interrupted",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps the memory-preflight refusal onto translated copy", () => {
|
||||
expect(
|
||||
appleHealthFailureKind(
|
||||
"insufficient_memory: export.xml declares 8192 MiB uncompressed; " +
|
||||
"parsing it needs roughly 320 MiB of heap but the Node.js heap " +
|
||||
"limit is 256 MiB.",
|
||||
),
|
||||
).toBe("insufficientMemory");
|
||||
});
|
||||
|
||||
it("passes honest free-text reasons through verbatim", () => {
|
||||
expect(
|
||||
appleHealthFailureKind("Import staging file is no longer available"),
|
||||
).toBe("raw");
|
||||
});
|
||||
|
||||
it("returns null when the job has no failure reason", () => {
|
||||
expect(appleHealthFailureKind(null)).toBeNull();
|
||||
expect(appleHealthFailureKind("")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("EXAMPLE_IMPORT payload", () => {
|
||||
it("round-trips through JSON and stays parseable", () => {
|
||||
const serialised = JSON.stringify(EXAMPLE_IMPORT, null, 2);
|
||||
|
||||
@@ -42,6 +42,24 @@ interface JobStatus {
|
||||
failureReason: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify a worker `failureReason` for display. The worker writes two
|
||||
* machine-readable reasons — the reconcile's bare `interrupted_by_restart`
|
||||
* code and the memory preflight's `insufficient_memory`-prefixed string
|
||||
* (issue #775) — which must surface as translated, actionable copy
|
||||
* instead of a raw code. Anything else is honest English free text from
|
||||
* the parse and passes through verbatim. Exported for the SSR contract
|
||||
* suite.
|
||||
*/
|
||||
export function appleHealthFailureKind(
|
||||
reason: string | null,
|
||||
): "interrupted" | "insufficientMemory" | "raw" | null {
|
||||
if (reason === null || reason.length === 0) return null;
|
||||
if (reason === "interrupted_by_restart") return "interrupted";
|
||||
if (reason.startsWith("insufficient_memory")) return "insufficientMemory";
|
||||
return "raw";
|
||||
}
|
||||
|
||||
export function AppleHealthEstimateWarning({ days }: { days: number }) {
|
||||
const { t } = useTranslations();
|
||||
return (
|
||||
@@ -160,6 +178,23 @@ export function AppleHealthImportCard() {
|
||||
const isFailed = status?.status === "failed";
|
||||
const busy = uploading || isRunning;
|
||||
const percent = status?.progress?.percent ?? null;
|
||||
const failureText = (() => {
|
||||
const reason = status?.failureReason ?? null;
|
||||
switch (appleHealthFailureKind(reason)) {
|
||||
case "interrupted":
|
||||
return t(
|
||||
"settings.sections.export.import.appleHealth.failureInterrupted",
|
||||
);
|
||||
case "insufficientMemory":
|
||||
return t(
|
||||
"settings.sections.export.import.appleHealth.failureInsufficientMemory",
|
||||
);
|
||||
case "raw":
|
||||
return reason;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const ecg = status?.result?.ecg;
|
||||
const ecgWritten = (ecg?.imported ?? 0) + (ecg?.updated ?? 0);
|
||||
|
||||
@@ -302,7 +337,7 @@ export function AppleHealthImportCard() {
|
||||
/>
|
||||
<span>
|
||||
{uploadError ??
|
||||
status?.failureReason ??
|
||||
failureText ??
|
||||
t("settings.sections.export.import.appleHealth.failed")}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
Reference in new issue
Block a user