fix(i18n): translate backend errors at UI boundaries

This commit is contained in:
Rahul authored and GitHub committed 2026-07-29 02:40:45 +08:00
1 parent 4422ac8e01
commit 2f44d36018
82 files changed
+1862 -335

No files matched your search

@@ -0,0 +1,55 @@
import { shallowRef } from "vue";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { TreeNode } from "@/types/database";
const toastMock = vi.hoisted(() => vi.fn());
const apiMock = vi.hoisted(() => ({
buildTableSelectSql: vi.fn(async () => 'SELECT * FROM "main"."users" LIMIT 10000'),
executeQuery: vi.fn(),
exportQueryResultJson: vi.fn(),
}));
vi.mock("@/lib/backend/api", () => apiMock);
vi.mock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: toastMock }) }));
vi.mock("@/composables/useExportTracker", () => ({ useExportTracker: () => ({ addTask: vi.fn() }) }));
vi.mock("vue-i18n", () => ({
useI18n: () => ({
t: (key: string, params?: Record<string, unknown>) => {
if (key === "editor.duckdbDraining") return "上一个 DuckDB 查询仍在停止中,请稍后重试。";
if (key === "grid.exportFailed") return `导出失败:${params?.message}`;
return key;
},
}),
}));
import { useSidebarTreeExportRuntime } from "@/composables/useSidebarTreeExportRuntime";
describe("useSidebarTreeExportRuntime", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("translates direct executeQuery errors for sidebar JSON export", async () => {
apiMock.executeQuery.mockRejectedValueOnce(new Error("The previous DuckDB query is still stopping. Please try again shortly."));
const activeNode = shallowRef({ id: "table-1", type: "table", label: "users", connectionId: "conn-1", database: "db", schema: "main", children: [] } as TreeNode);
const connectionStore = {
ensureConnected: vi.fn(),
getConfig: vi.fn(() => ({ db_type: "duckdb" })),
connectionIdentifierQuote: vi.fn(() => '"'),
treeNodes: [],
selectedTreeNodeIds: [],
};
const runtime = useSidebarTreeExportRuntime({
activeNode,
connectionStore: connectionStore as never,
settingsStore: {} as never,
acceptedSelectionIds: () => null,
});
await runtime.exportData("json");
expect(apiMock.executeQuery).toHaveBeenCalledOnce();
expect(toastMock).toHaveBeenCalledWith("导出失败:上一个 DuckDB 查询仍在停止中,请稍后重试。", 5000);
});
});
@@ -13,6 +13,7 @@ import { assessProductionSql, productionContextForDatabase } from "@/lib/databas
import type { ColumnInfo, DatabaseType } from "@/types/database";
import { DBX_NEO4J_ELEMENT_ID_COLUMN, usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import i18n from "@/i18n";
interface RowItem {
id: number;
@@ -1273,7 +1274,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
if (!confirmed) return;
}
if (customHandler && snapshot.newRows.length > 0 && customHandler.supportsInsert !== true && customHandler.canInsert !== true) {
saveError.value = "当前保存目标不支持新增行。";
saveError.value = i18n.global.t("grid.insertRowsNotSupported");
return;
}
saveError.value = "";
@@ -23,6 +23,7 @@ import type { QueryResultExportRequest } from "@/lib/backend/api";
import { usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
import { buildXlsxSqlWorksheet } from "@/lib/export/xlsxSqlSheet";
import { formatTemporalRowsForExport } from "@/lib/dataGrid/columnFormatter";
import { translateBackendError } from "@/i18n/backend-errors";
/**
* Format metadata for backend table exports. Each entry maps a format key
@@ -571,7 +572,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
errorMessage: e?.message || String(e),
};
}
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -593,7 +594,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await api.exportQueryResultCsv(outputPath, result.columns, result.rows);
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -617,7 +618,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await api.exportQueryResultJson(outputPath, result.columns, result.rows);
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -639,7 +640,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await api.exportQueryResultJson(outputPath, result.columns, result.rows);
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -663,7 +664,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await api.exportQueryResultMarkdown(outputPath, result.columns, result.rows);
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -685,7 +686,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await api.exportQueryResultMarkdown(outputPath, result.columns, result.rows);
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -700,7 +701,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await saveTextFile(content, exportFileName(tableMeta.value?.tableName || "export", "txt", { preferFallback: true }), "Text", "txt");
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -713,7 +714,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await saveTextFile(content, exportFileName("export-page", "txt", { page: true }), "Text", "txt");
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -784,7 +785,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
errorMessage: e?.message || String(e),
};
}
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -814,7 +815,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await writeXlsxResult(outputPath, result, includeSqlSheet);
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -857,7 +858,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await api.exportQueryResultsXlsx(outputPath, sqlWorksheet ? [...worksheets, sqlWorksheet] : worksheets);
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -1113,7 +1114,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await saveTextFile(content, exportFileName(tableMeta.value?.tableName || "export", "sql", { preferFallback: true }), "SQL", "sql");
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -1134,7 +1135,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await saveTextFile(content, exportFileName("export-page", "sql", { page: true }), "SQL", "sql");
toast(t("grid.exported"));
} catch (e: any) {
toast(t("grid.exportFailed", { message: e?.message || String(e) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, e) }), 5000);
}
});
}
@@ -150,7 +150,7 @@ export function useSidebarConnectionMutationRuntime(options: SidebarConnectionMu
try {
await revealPathInFileManager(path);
} catch (error: any) {
toast(typeof error === "string" ? error : error?.message || String(error), 5000);
toast(translateBackendError(t, typeof error === "string" ? error : error?.message || String(error)), 5000);
}
}
@@ -11,6 +11,7 @@ import { copyToClipboard } from "@/lib/common/clipboard";
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { joinExportedDdls } from "@/lib/export/ddlExport";
import { formatSqlInsert } from "@/lib/export/exportFormats";
import { translateBackendError } from "@/i18n/backend-errors";
import { sidebarStructureExportTargets } from "@/lib/sidebar/sidebarExportRuntime";
import { fetchTableDataForExport } from "@/lib/table/tableDataExport";
import { isLoadingStructurePreview, showStructureDocCopyDialog, showStructurePreviewDialog, structureDocCopyText, structureDocCopyTitle, structurePreviewDefaultFileName, structurePreviewError, structurePreviewSql, structurePreviewTitle } from "@/components/sidebar/sidebarTreeDialogState";
@@ -246,7 +247,7 @@ export function useSidebarTreeExportRuntime(options: SidebarTreeExportRuntimeOpt
await saveFileContent(content, `${node.label}.sql`, "SQL", "sql");
toast(t("grid.exported"));
} catch (error: any) {
toast(t("grid.exportFailed", { message: error?.message || String(error) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, error) }), 5000);
}
}
@@ -297,14 +298,14 @@ export function useSidebarTreeExportRuntime(options: SidebarTreeExportRuntimeOpt
currentTask.status = progress.status;
currentTask.errorMessage = progress.errorMessage || null;
if (progress.status === "Done") toast(t("grid.exported"));
else if (progress.status === "Error") toast(t("grid.exportFailed", { message: progress.errorMessage || "" }), 5000);
else if (progress.status === "Error") toast(t("grid.exportFailed", { message: translateBackendError(t, progress.errorMessage || "") }), 5000);
});
} catch (error: any) {
if (task) {
task.status = "Error";
task.errorMessage = error?.message || String(error);
}
toast(t("grid.exportFailed", { message: error?.message || String(error) }), 5000);
toast(t("grid.exportFailed", { message: translateBackendError(t, error) }), 5000);
}
}