diff --git a/.changeset/young-dancers-join.md b/.changeset/young-dancers-join.md new file mode 100644 index 00000000000..d6aab3351e6 --- /dev/null +++ b/.changeset/young-dancers-join.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Fix settings import when global settings are omitted diff --git a/src/core/config/__tests__/importExport.test.ts b/src/core/config/__tests__/importExport.test.ts index eef83959cc6..0d32e87deac 100644 --- a/src/core/config/__tests__/importExport.test.ts +++ b/src/core/config/__tests__/importExport.test.ts @@ -11,7 +11,6 @@ import { ProviderSettingsManager } from "../ProviderSettingsManager" import { ContextProxy } from "../ContextProxy" import { CustomModesManager } from "../CustomModesManager" -// Mock VSCode modules jest.mock("vscode", () => ({ window: { showOpenDialog: jest.fn(), @@ -22,14 +21,12 @@ jest.mock("vscode", () => ({ }, })) -// Mock fs/promises jest.mock("fs/promises", () => ({ readFile: jest.fn(), mkdir: jest.fn(), writeFile: jest.fn(), })) -// Mock os module jest.mock("os", () => ({ homedir: jest.fn(() => "/mock/home"), })) @@ -41,17 +38,14 @@ describe("importExport", () => { let mockCustomModesManager: jest.Mocked beforeEach(() => { - // Reset all mocks jest.clearAllMocks() - // Setup providerSettingsManager mock mockProviderSettingsManager = { export: jest.fn(), import: jest.fn(), listConfig: jest.fn(), } as unknown as jest.Mocked - // Setup contextProxy mock with properly typed export method mockContextProxy = { setValues: jest.fn(), setValue: jest.fn(), @@ -59,10 +53,7 @@ describe("importExport", () => { setProviderSettings: jest.fn(), } as unknown as jest.Mocked - // Setup customModesManager mock - mockCustomModesManager = { - updateCustomMode: jest.fn(), - } as unknown as jest.Mocked + mockCustomModesManager = { updateCustomMode: jest.fn() } as unknown as jest.Mocked const map = new Map() @@ -76,7 +67,6 @@ describe("importExport", () => { describe("importSettings", () => { it("should return success: false when user cancels file selection", async () => { - // Mock user canceling file selection ;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue(undefined) const result = await importSettings({ @@ -86,63 +76,43 @@ describe("importExport", () => { }) expect(result).toEqual({ success: false }) + expect(vscode.window.showOpenDialog).toHaveBeenCalledWith({ filters: { JSON: ["json"] }, canSelectMany: false, }) + expect(fs.readFile).not.toHaveBeenCalled() expect(mockProviderSettingsManager.import).not.toHaveBeenCalled() expect(mockContextProxy.setValues).not.toHaveBeenCalled() }) it("should import settings successfully from a valid file", async () => { - // Mock successful file selection ;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }]) - // Valid settings content const mockFileContent = JSON.stringify({ providerProfiles: { currentApiConfigName: "test", - apiConfigs: { - test: { - apiProvider: "openai" as ProviderName, - apiKey: "test-key", - id: "test-id", - }, - }, - }, - globalSettings: { - mode: "code", - autoApprovalEnabled: true, + apiConfigs: { test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" } }, }, + globalSettings: { mode: "code", autoApprovalEnabled: true }, }) - // Mock reading file ;(fs.readFile as jest.Mock).mockResolvedValue(mockFileContent) - // Mock export returning previous provider profiles const previousProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { - default: { - apiProvider: "anthropic" as ProviderName, - id: "default-id", - }, - }, + apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, } mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) - // Mock listConfig mockProviderSettingsManager.listConfig.mockResolvedValue([ { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, ]) - // Mock contextProxy.export - mockContextProxy.export.mockResolvedValue({ - mode: "code", - }) + mockContextProxy.export.mockResolvedValue({ mode: "code" }) const result = await importSettings({ providerSettingsManager: mockProviderSettingsManager, @@ -153,22 +123,16 @@ describe("importExport", () => { expect(result.success).toBe(true) expect(fs.readFile).toHaveBeenCalledWith("/mock/path/settings.json", "utf-8") expect(mockProviderSettingsManager.export).toHaveBeenCalled() + expect(mockProviderSettingsManager.import).toHaveBeenCalledWith({ ...previousProviderProfiles, currentApiConfigName: "test", - apiConfigs: { - test: { - apiProvider: "openai" as ProviderName, - apiKey: "test-key", - id: "test-id", - }, - }, - }) - expect(mockContextProxy.setValues).toHaveBeenCalledWith({ - mode: "code", - autoApprovalEnabled: true, + apiConfigs: { test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" } }, }) + + expect(mockContextProxy.setValues).toHaveBeenCalledWith({ mode: "code", autoApprovalEnabled: true }) expect(mockContextProxy.setValue).toHaveBeenCalledWith("currentApiConfigName", "test") + expect(mockContextProxy.setValue).toHaveBeenCalledWith("listApiConfigMeta", [ { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, @@ -176,16 +140,14 @@ describe("importExport", () => { }) it("should return success: false when file content is invalid", async () => { - // Mock successful file selection ;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }]) - // Invalid content (missing required fields) + // Invalid content (missing required fields). const mockInvalidContent = JSON.stringify({ providerProfiles: { apiConfigs: {} }, globalSettings: {}, }) - // Mock reading file ;(fs.readFile as jest.Mock).mockResolvedValue(mockInvalidContent) const result = await importSettings({ @@ -194,20 +156,67 @@ describe("importExport", () => { customModesManager: mockCustomModesManager, }) - expect(result).toEqual({ success: false }) + expect(result).toEqual({ success: false, error: "[providerProfiles.currentApiConfigName]: Required" }) expect(fs.readFile).toHaveBeenCalledWith("/mock/path/settings.json", "utf-8") expect(mockProviderSettingsManager.import).not.toHaveBeenCalled() expect(mockContextProxy.setValues).not.toHaveBeenCalled() }) - it("should return success: false when file content is not valid JSON", async () => { - // Mock successful file selection + it("should import settings successfully when globalSettings key is missing", async () => { ;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }]) - // Invalid JSON - const mockInvalidJson = "{ this is not valid JSON }" + const mockFileContent = JSON.stringify({ + providerProfiles: { + currentApiConfigName: "test", + apiConfigs: { test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" } }, + }, + }) + + ;(fs.readFile as jest.Mock).mockResolvedValue(mockFileContent) + + const previousProviderProfiles = { + currentApiConfigName: "default", + apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } }, + } + + mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles) - // Mock reading file + mockProviderSettingsManager.listConfig.mockResolvedValue([ + { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, + { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + ]) + + mockContextProxy.export.mockResolvedValue({ mode: "code" }) + + const result = await importSettings({ + providerSettingsManager: mockProviderSettingsManager, + contextProxy: mockContextProxy, + customModesManager: mockCustomModesManager, + }) + + expect(result.success).toBe(true) + expect(fs.readFile).toHaveBeenCalledWith("/mock/path/settings.json", "utf-8") + expect(mockProviderSettingsManager.export).toHaveBeenCalled() + expect(mockProviderSettingsManager.import).toHaveBeenCalledWith({ + ...previousProviderProfiles, + currentApiConfigName: "test", + apiConfigs: { + test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" }, + }, + }) + + // Should call setValues with an empty object since globalSettings is missing. + expect(mockContextProxy.setValues).toHaveBeenCalledWith({}) + expect(mockContextProxy.setValue).toHaveBeenCalledWith("currentApiConfigName", "test") + expect(mockContextProxy.setValue).toHaveBeenCalledWith("listApiConfigMeta", [ + { name: "test", id: "test-id", apiProvider: "openai" as ProviderName }, + { name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName }, + ]) + }) + + it("should return success: false when file content is not valid JSON", async () => { + ;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }]) + const mockInvalidJson = "{ this is not valid JSON }" ;(fs.readFile as jest.Mock).mockResolvedValue(mockInvalidJson) const result = await importSettings({ @@ -216,17 +225,14 @@ describe("importExport", () => { customModesManager: mockCustomModesManager, }) - expect(result).toEqual({ success: false }) + expect(result).toEqual({ success: false, error: "Expected property name or '}' in JSON at position 2" }) expect(fs.readFile).toHaveBeenCalledWith("/mock/path/settings.json", "utf-8") expect(mockProviderSettingsManager.import).not.toHaveBeenCalled() expect(mockContextProxy.setValues).not.toHaveBeenCalled() }) it("should return success: false when reading file fails", async () => { - // Mock successful file selection ;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }]) - - // Mock file read error ;(fs.readFile as jest.Mock).mockRejectedValue(new Error("File read error")) const result = await importSettings({ @@ -235,7 +241,7 @@ describe("importExport", () => { customModesManager: mockCustomModesManager, }) - expect(result).toEqual({ success: false }) + expect(result).toEqual({ success: false, error: "File read error" }) expect(fs.readFile).toHaveBeenCalledWith("/mock/path/settings.json", "utf-8") expect(mockProviderSettingsManager.import).not.toHaveBeenCalled() expect(mockContextProxy.setValues).not.toHaveBeenCalled() @@ -277,43 +283,35 @@ describe("importExport", () => { it("should call updateCustomMode for each custom mode in config", async () => { ;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }]) + const customModes = [ - { - slug: "mode1", - name: "Mode One", - roleDefinition: "Custom role one", - groups: [], - }, - { - slug: "mode2", - name: "Mode Two", - roleDefinition: "Custom role two", - groups: [], - }, + { slug: "mode1", name: "Mode One", roleDefinition: "Custom role one", groups: [] }, + { slug: "mode2", name: "Mode Two", roleDefinition: "Custom role two", groups: [] }, ] + const mockFileContent = JSON.stringify({ - providerProfiles: { - currentApiConfigName: "test", - apiConfigs: {}, - }, - globalSettings: { - mode: "code", - customModes, - }, + providerProfiles: { currentApiConfigName: "test", apiConfigs: {} }, + globalSettings: { mode: "code", customModes }, }) + ;(fs.readFile as jest.Mock).mockResolvedValue(mockFileContent) + mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "test", apiConfigs: {}, }) + mockProviderSettingsManager.listConfig.mockResolvedValue([]) + const result = await importSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, customModesManager: mockCustomModesManager, }) + expect(result.success).toBe(true) expect(mockCustomModesManager.updateCustomMode).toHaveBeenCalledTimes(customModes.length) + customModes.forEach((mode) => { expect(mockCustomModesManager.updateCustomMode).toHaveBeenCalledWith(mode.slug, mode) }) @@ -321,7 +319,6 @@ describe("importExport", () => { describe("exportSettings", () => { it("should not export settings when user cancels file selection", async () => { - // Mock user canceling file selection ;(vscode.window.showSaveDialog as jest.Mock).mockResolvedValue(undefined) await exportSettings({ @@ -333,37 +330,25 @@ describe("importExport", () => { filters: { JSON: ["json"] }, defaultUri: expect.anything(), }) + expect(mockProviderSettingsManager.export).not.toHaveBeenCalled() expect(mockContextProxy.export).not.toHaveBeenCalled() expect(fs.writeFile).not.toHaveBeenCalled() }) it("should export settings to the selected file location", async () => { - // Mock successful file location selection ;(vscode.window.showSaveDialog as jest.Mock).mockResolvedValue({ fsPath: "/mock/path/roo-code-settings.json", }) - // Mock providerProfiles data const mockProviderProfiles = { currentApiConfigName: "test", - apiConfigs: { - test: { - apiProvider: "openai" as ProviderName, - id: "test-id", - }, - }, - migrations: { - rateLimitSecondsMigrated: false, - }, + apiConfigs: { test: { apiProvider: "openai" as ProviderName, id: "test-id" } }, + migrations: { rateLimitSecondsMigrated: false }, } - mockProviderSettingsManager.export.mockResolvedValue(mockProviderProfiles) - // Mock globalSettings data - const mockGlobalSettings = { - mode: "code", - autoApprovalEnabled: true, - } + mockProviderSettingsManager.export.mockResolvedValue(mockProviderProfiles) + const mockGlobalSettings = { mode: "code", autoApprovalEnabled: true } mockContextProxy.export.mockResolvedValue(mockGlobalSettings) await exportSettings({ @@ -375,52 +360,32 @@ describe("importExport", () => { filters: { JSON: ["json"] }, defaultUri: expect.anything(), }) + expect(mockProviderSettingsManager.export).toHaveBeenCalled() expect(mockContextProxy.export).toHaveBeenCalled() expect(fs.mkdir).toHaveBeenCalledWith("/mock/path", { recursive: true }) + expect(fs.writeFile).toHaveBeenCalledWith( "/mock/path/roo-code-settings.json", - JSON.stringify( - { - providerProfiles: mockProviderProfiles, - globalSettings: mockGlobalSettings, - }, - null, - 2, - ), + JSON.stringify({ providerProfiles: mockProviderProfiles, globalSettings: mockGlobalSettings }, null, 2), "utf-8", ) }) it("should handle errors during the export process", async () => { - // Mock successful file location selection ;(vscode.window.showSaveDialog as jest.Mock).mockResolvedValue({ fsPath: "/mock/path/roo-code-settings.json", }) - // Mock provider profiles mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "test", - apiConfigs: { - test: { - apiProvider: "openai" as ProviderName, - id: "test-id", - }, - }, - migrations: { - rateLimitSecondsMigrated: false, - }, + apiConfigs: { test: { apiProvider: "openai" as ProviderName, id: "test-id" } }, + migrations: { rateLimitSecondsMigrated: false }, }) - // Mock global settings - mockContextProxy.export.mockResolvedValue({ - mode: "code", - }) - - // Mock file write error + mockContextProxy.export.mockResolvedValue({ mode: "code" }) ;(fs.writeFile as jest.Mock).mockRejectedValue(new Error("Write error")) - // The function catches errors internally and doesn't throw or return anything await exportSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, @@ -431,38 +396,23 @@ describe("importExport", () => { expect(mockContextProxy.export).toHaveBeenCalled() expect(fs.mkdir).toHaveBeenCalledWith("/mock/path", { recursive: true }) expect(fs.writeFile).toHaveBeenCalled() - // The error is caught and the function exits silently + // The error is caught and the function exits silently. }) it("should handle errors during directory creation", async () => { - // Mock successful file location selection ;(vscode.window.showSaveDialog as jest.Mock).mockResolvedValue({ fsPath: "/mock/path/roo-code-settings.json", }) - // Mock provider profiles mockProviderSettingsManager.export.mockResolvedValue({ currentApiConfigName: "test", - apiConfigs: { - test: { - apiProvider: "openai" as ProviderName, - id: "test-id", - }, - }, - migrations: { - rateLimitSecondsMigrated: false, - }, + apiConfigs: { test: { apiProvider: "openai" as ProviderName, id: "test-id" } }, + migrations: { rateLimitSecondsMigrated: false }, }) - // Mock global settings - mockContextProxy.export.mockResolvedValue({ - mode: "code", - }) - - // Mock directory creation error + mockContextProxy.export.mockResolvedValue({ mode: "code" }) ;(fs.mkdir as jest.Mock).mockRejectedValue(new Error("Directory creation error")) - // The function catches errors internally and doesn't throw or return anything await exportSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, @@ -472,26 +422,22 @@ describe("importExport", () => { expect(mockProviderSettingsManager.export).toHaveBeenCalled() expect(mockContextProxy.export).toHaveBeenCalled() expect(fs.mkdir).toHaveBeenCalled() - expect(fs.writeFile).not.toHaveBeenCalled() // Should not be called since mkdir failed + expect(fs.writeFile).not.toHaveBeenCalled() // Should not be called since mkdir failed. }) it("should use the correct default save location", async () => { - // Mock user cancels to avoid full execution ;(vscode.window.showSaveDialog as jest.Mock).mockResolvedValue(undefined) - // Call the function await exportSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, }) - // Verify the default save location expect(vscode.window.showSaveDialog).toHaveBeenCalledWith({ filters: { JSON: ["json"] }, defaultUri: expect.anything(), }) - // Verify Uri.file was called with the correct path expect(vscode.Uri.file).toHaveBeenCalledWith(path.join("/mock/home", "Documents", "roo-code-settings.json")) }) }) diff --git a/src/core/config/importExport.ts b/src/core/config/importExport.ts index f2456c7cabe..457e91fa370 100644 --- a/src/core/config/importExport.ts +++ b/src/core/config/importExport.ts @@ -3,13 +3,14 @@ import * as path from "path" import fs from "fs/promises" import * as vscode from "vscode" -import { z } from "zod" +import { z, ZodError } from "zod" import { globalSettingsSchema } from "../../schemas" import { ProviderSettingsManager, providerProfilesSchema } from "./ProviderSettingsManager" import { ContextProxy } from "./ContextProxy" import { CustomModesManager } from "./CustomModesManager" +import { telemetryService } from "../../services/telemetry/TelemetryService" type ImportOptions = { providerSettingsManager: ProviderSettingsManager @@ -34,15 +35,14 @@ export const importSettings = async ({ providerSettingsManager, contextProxy, cu const schema = z.object({ providerProfiles: providerProfilesSchema, - globalSettings: globalSettingsSchema, + globalSettings: globalSettingsSchema.optional(), }) try { const previousProviderProfiles = await providerSettingsManager.export() - const { providerProfiles: newProviderProfiles, globalSettings } = schema.parse( - JSON.parse(await fs.readFile(uris[0].fsPath, "utf-8")), - ) + const data = JSON.parse(await fs.readFile(uris[0].fsPath, "utf-8")) + const { providerProfiles: newProviderProfiles, globalSettings = {} } = schema.parse(data) const providerProfiles = { currentApiConfigName: newProviderProfiles.currentApiConfigName, @@ -79,7 +79,16 @@ export const importSettings = async ({ providerSettingsManager, contextProxy, cu return { providerProfiles, globalSettings, success: true } } catch (e) { - return { success: false } + let error = "Unknown error" + + if (e instanceof ZodError) { + error = e.issues.map((issue) => `[${issue.path.join(".")}]: ${issue.message}`).join("\n") + telemetryService.captureSchemaValidationError({ schemaName: "ImportExport", error: e }) + } else if (e instanceof Error) { + error = e.message + } + + return { success: false, error } } } @@ -97,6 +106,14 @@ export const exportSettings = async ({ providerSettingsManager, contextProxy }: const providerProfiles = await providerSettingsManager.export() const globalSettings = await contextProxy.export() + // It's okay if there are no global settings, but if there are no + // provider profile configured then don't export. If we wanted to + // support this case then the `importSettings` function would need to + // be updated to handle the case where there are no provider profiles. + if (typeof providerProfiles === "undefined") { + return + } + const dirname = path.dirname(uri.fsPath) await fs.mkdir(dirname, { recursive: true }) await fs.writeFile(uri.fsPath, JSON.stringify({ providerProfiles, globalSettings }, null, 2), "utf-8") diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 1f17d221a62..9c8b90ea8a9 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -245,20 +245,23 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We case "exportTaskWithId": provider.exportTaskWithId(message.text!) break - case "importSettings": - const { success } = await importSettings({ + case "importSettings": { + const result = await importSettings({ providerSettingsManager: provider.providerSettingsManager, contextProxy: provider.contextProxy, customModesManager: provider.customModesManager, }) - if (success) { + if (result.success) { provider.settingsImportedAt = Date.now() await provider.postStateToWebview() await vscode.window.showInformationMessage(t("common:info.settings_imported")) + } else if (result.error) { + await vscode.window.showErrorMessage(t("common:errors.settings_import_failed", { error: result.error })) } break + } case "exportSettings": await exportSettings({ providerSettingsManager: provider.providerSettingsManager, diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 2bd61dfd930..c9994fdeaad 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -71,7 +71,8 @@ "mcp_server_not_found": "Servidor \"{{serverName}}\" no trobat a la configuració", "custom_storage_path_set": "Ruta d'emmagatzematge personalitzada establerta: {{path}}", "default_storage_path": "S'ha reprès l'ús de la ruta d'emmagatzematge predeterminada", - "settings_imported": "Configuració importada correctament." + "settings_imported": "Configuració importada correctament.", + "settings_import_failed": "Ha fallat la importació de la configuració: {{error}}." }, "answers": { "yes": "Sí", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 5eb6b05e8ff..982dc96da90 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Server \"{{serverName}}\" nicht in der Konfiguration gefunden", "custom_storage_path_set": "Benutzerdefinierter Speicherpfad festgelegt: {{path}}", "default_storage_path": "Auf Standardspeicherpfad zurückgesetzt", - "settings_imported": "Einstellungen erfolgreich importiert." + "settings_imported": "Einstellungen erfolgreich importiert.", + "settings_import_failed": "Fehler beim Importieren der Einstellungen: {{error}}." }, "answers": { "yes": "Ja", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index ecd5a4c4133..06b371624b6 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Server \"{{serverName}}\" not found in configuration", "custom_storage_path_set": "Custom storage path set: {{path}}", "default_storage_path": "Reverted to using default storage path", - "settings_imported": "Settings imported successfully." + "settings_imported": "Settings imported successfully.", + "settings_import_failed": "Settings import failed: {{error}}." }, "answers": { "yes": "Yes", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index beea1ba3a9d..ce4acbc7447 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Servidor \"{{serverName}}\" no encontrado en la configuración", "custom_storage_path_set": "Ruta de almacenamiento personalizada establecida: {{path}}", "default_storage_path": "Se ha vuelto a usar la ruta de almacenamiento predeterminada", - "settings_imported": "Configuración importada correctamente." + "settings_imported": "Configuración importada correctamente.", + "settings_import_failed": "Error al importar la configuración: {{error}}." }, "answers": { "yes": "Sí", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index c34d0105a44..262ef2659ff 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Serveur \"{{serverName}}\" introuvable dans la configuration", "custom_storage_path_set": "Chemin de stockage personnalisé défini : {{path}}", "default_storage_path": "Retour au chemin de stockage par défaut", - "settings_imported": "Paramètres importés avec succès." + "settings_imported": "Paramètres importés avec succès.", + "settings_import_failed": "Échec de l'importation des paramètres : {{error}}." }, "answers": { "yes": "Oui", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index 07438c873b1..9959b85f6e6 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "सर्वर \"{{serverName}}\" कॉन्फ़िगरेशन में नहीं मिला", "custom_storage_path_set": "कस्टम स्टोरेज पाथ सेट किया गया: {{path}}", "default_storage_path": "डिफ़ॉल्ट स्टोरेज पाथ का उपयोग पुनः शुरू किया गया", - "settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।" + "settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।", + "settings_import_failed": "सेटिंग्स इम्पोर्ट करने में विफल: {{error}}।" }, "answers": { "yes": "हां", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 476285169f5..f3f9a0ce196 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Server \"{{serverName}}\" non trovato nella configurazione", "custom_storage_path_set": "Percorso di archiviazione personalizzato impostato: {{path}}", "default_storage_path": "Tornato al percorso di archiviazione predefinito", - "settings_imported": "Impostazioni importate con successo." + "settings_imported": "Impostazioni importate con successo.", + "settings_import_failed": "Importazione delle impostazioni fallita: {{error}}." }, "answers": { "yes": "Sì", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index f44469d0c90..fd1860a1de7 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "サーバー\"{{serverName}}\"が設定内に見つかりません", "custom_storage_path_set": "カスタムストレージパスが設定されました:{{path}}", "default_storage_path": "デフォルトのストレージパスに戻りました", - "settings_imported": "設定が正常にインポートされました。" + "settings_imported": "設定が正常にインポートされました。", + "settings_import_failed": "設定のインポートに失敗しました:{{error}}。" }, "answers": { "yes": "はい", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 944d9ba19b8..4bea1be3b4f 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "구성에서 서버 \"{{serverName}}\"을(를) 찾을 수 없습니다", "custom_storage_path_set": "사용자 지정 저장 경로 설정됨: {{path}}", "default_storage_path": "기본 저장 경로로 되돌아갔습니다", - "settings_imported": "설정이 성공적으로 가져와졌습니다." + "settings_imported": "설정이 성공적으로 가져와졌습니다.", + "settings_import_failed": "설정 가져오기 실패: {{error}}." }, "answers": { "yes": "예", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index e676235dc2c..a758d149fa3 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Server \"{{serverName}}\" niet gevonden in configuratie", "custom_storage_path_set": "Aangepast opslagpad ingesteld: {{path}}", "default_storage_path": "Terug naar standaard opslagpad", - "settings_imported": "Instellingen succesvol geïmporteerd." + "settings_imported": "Instellingen succesvol geïmporteerd.", + "settings_import_failed": "Importeren van instellingen mislukt: {{error}}." }, "answers": { "yes": "Ja", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 46ed243b496..1c4152f02d2 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Serwer \"{{serverName}}\" nie znaleziony w konfiguracji", "custom_storage_path_set": "Ustawiono niestandardową ścieżkę przechowywania: {{path}}", "default_storage_path": "Wznowiono używanie domyślnej ścieżki przechowywania", - "settings_imported": "Ustawienia zaimportowane pomyślnie." + "settings_imported": "Ustawienia zaimportowane pomyślnie.", + "settings_import_failed": "Nie udało się zaimportować ustawień: {{error}}." }, "answers": { "yes": "Tak", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index a6588b2fdad..d80d5eaf12d 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -71,7 +71,8 @@ "mcp_server_not_found": "Servidor \"{{serverName}}\" não encontrado na configuração", "custom_storage_path_set": "Caminho de armazenamento personalizado definido: {{path}}", "default_storage_path": "Retornado ao caminho de armazenamento padrão", - "settings_imported": "Configurações importadas com sucesso." + "settings_imported": "Configurações importadas com sucesso.", + "settings_import_failed": "Falha ao importar configurações: {{error}}." }, "answers": { "yes": "Sim", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index baef76ea78c..c4c1b01de5f 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Сервер \"{{serverName}}\" не найден в конфигурации", "custom_storage_path_set": "Установлен пользовательский путь хранения: {{path}}", "default_storage_path": "Возвращено использование пути хранения по умолчанию", - "settings_imported": "Настройки успешно импортированы." + "settings_imported": "Настройки успешно импортированы.", + "settings_import_failed": "Не удалось импортировать настройки: {{error}}." }, "answers": { "yes": "Да", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 2c4ec8b3548..07d96db0c91 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Yapılandırmada \"{{serverName}}\" sunucusu bulunamadı", "custom_storage_path_set": "Özel depolama yolu ayarlandı: {{path}}", "default_storage_path": "Varsayılan depolama yoluna geri dönüldü", - "settings_imported": "Ayarlar başarıyla içe aktarıldı." + "settings_imported": "Ayarlar başarıyla içe aktarıldı.", + "settings_import_failed": "Ayarlar içe aktarılamadı: {{error}}." }, "answers": { "yes": "Evet", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 499309df750..3d6aac504c9 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "Không tìm thấy máy chủ \"{{serverName}}\" trong cấu hình", "custom_storage_path_set": "Đã thiết lập đường dẫn lưu trữ tùy chỉnh: {{path}}", "default_storage_path": "Đã quay lại sử dụng đường dẫn lưu trữ mặc định", - "settings_imported": "Cài đặt đã được nhập thành công." + "settings_imported": "Cài đặt đã được nhập thành công.", + "settings_import_failed": "Nhập cài đặt thất bại: {{error}}." }, "answers": { "yes": "Có", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index ac3754ccd64..62dcbbb6663 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "在配置中未找到服务器\"{{serverName}}\"", "custom_storage_path_set": "自定义存储路径已设置:{{path}}", "default_storage_path": "已恢复使用默认存储路径", - "settings_imported": "设置已成功导入。" + "settings_imported": "设置已成功导入。", + "settings_import_failed": "设置导入失败:{{error}}。" }, "answers": { "yes": "是", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 93c59acbbbb..b40f9cb678f 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -67,7 +67,8 @@ "mcp_server_not_found": "在設定中沒有找到伺服器\"{{serverName}}\"", "custom_storage_path_set": "自訂儲存路徑已設定:{{path}}", "default_storage_path": "已恢復使用預設儲存路徑", - "settings_imported": "設定已成功匯入。" + "settings_imported": "設定已成功匯入。", + "settings_import_failed": "設定匯入失敗:{{error}}。" }, "answers": { "yes": "是",