Import settings bug fix / improvements (#3657)

This commit is contained in:
Chris Estreich 2025-05-15 23:21:55 -07:00 committed by hannesrudolph
parent 8f0746397f
commit 69d63b9502
21 changed files with 182 additions and 194 deletions

View file

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

View file

@ -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,64 +76,118 @@ 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,
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" } },
})
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 },
])
})
it("should return success: false when file content is invalid", async () => {
;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }])
// Invalid content (missing required fields).
const mockInvalidContent = JSON.stringify({
providerProfiles: { apiConfigs: {} },
globalSettings: {},
})
;(fs.readFile as jest.Mock).mockResolvedValue(mockInvalidContent)
const result = await importSettings({
providerSettingsManager: mockProviderSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})
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 import settings successfully when globalSettings key is missing", async () => {
;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.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)
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,
@ -157,17 +201,12 @@ describe("importExport", () => {
...previousProviderProfiles,
currentApiConfigName: "test",
apiConfigs: {
test: {
apiProvider: "openai" as ProviderName,
apiKey: "test-key",
id: "test-id",
},
test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" },
},
})
expect(mockContextProxy.setValues).toHaveBeenCalledWith({
mode: "code",
autoApprovalEnabled: true,
})
// 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 },
@ -175,39 +214,9 @@ 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)
const mockInvalidContent = JSON.stringify({
providerProfiles: { apiConfigs: {} },
globalSettings: {},
})
// Mock reading file
;(fs.readFile as jest.Mock).mockResolvedValue(mockInvalidContent)
const result = await importSettings({
providerSettingsManager: mockProviderSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})
expect(result).toEqual({ success: false })
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
;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }])
// Invalid JSON
const mockInvalidJson = "{ this is not valid JSON }"
// Mock reading file
;(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"))
})
})

View file

@ -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")

View file

@ -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,

View file

@ -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í",

View file

@ -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",

View file

@ -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",

View file

@ -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í",

View file

@ -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",

View file

@ -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": "हां",

View file

@ -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ì",

View file

@ -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": "はい",

View file

@ -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": "예",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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": "Да",

View file

@ -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",

View file

@ -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ó",

View file

@ -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": "是",

View file

@ -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": "是",