Browse Source

Import settings bug fix / improvements (#3657)

Chris Estreich 7 months ago
parent
commit
dc694efdb2

+ 5 - 0
.changeset/young-dancers-join.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Fix settings import when global settings are omitted

+ 96 - 150
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<CustomModesManager>
 
 	beforeEach(() => {
-		// Reset all mocks
 		jest.clearAllMocks()
 
-		// Setup providerSettingsManager mock
 		mockProviderSettingsManager = {
 			export: jest.fn(),
 			import: jest.fn(),
 			listConfig: jest.fn(),
 		} as unknown as jest.Mocked<ProviderSettingsManager>
 
-		// 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<ContextProxy>
 
-		// Setup customModesManager mock
-		mockCustomModesManager = {
-			updateCustomMode: jest.fn(),
-		} as unknown as jest.Mocked<CustomModesManager>
+		mockCustomModesManager = { updateCustomMode: jest.fn() } as unknown as jest.Mocked<CustomModesManager>
 
 		const map = new Map<string, string>()
 
@@ -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"))
 		})
 	})

+ 23 - 6
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")

+ 6 - 3
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,

+ 2 - 1
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í",

+ 2 - 1
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",

+ 2 - 1
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",

+ 2 - 1
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í",

+ 2 - 1
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",

+ 2 - 1
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": "हां",

+ 2 - 1
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ì",

+ 2 - 1
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": "はい",

+ 2 - 1
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": "예",

+ 2 - 1
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",

+ 2 - 1
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",

+ 2 - 1
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",

+ 2 - 1
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": "Да",

+ 2 - 1
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",

+ 2 - 1
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ó",

+ 2 - 1
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": "是",

+ 2 - 1
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": "是",