diff --git a/frontend/src/i18n/catalog.test.ts b/frontend/src/i18n/catalog.test.ts index 65695f0..a2de74d 100644 --- a/frontend/src/i18n/catalog.test.ts +++ b/frontend/src/i18n/catalog.test.ts @@ -56,6 +56,9 @@ const readDataGridV2DdlWorkspaceSource = (): string => const readQueryEditorSource = (): string => readFileSync(new URL("../components/QueryEditor.tsx", import.meta.url), "utf8"); +const readQueryEditorResultsPanelSource = (): string => + readFileSync(new URL("../components/QueryEditorResultsPanel.tsx", import.meta.url), "utf8"); + const readSqlDialectSource = (): string => readFileSync(new URL("../utils/sqlDialect.ts", import.meta.url), "utf8"); @@ -594,11 +597,13 @@ describe("i18n catalog", () => { "data_grid.message.change_set_build_failed_detail", "data_grid.message.preview_sql_failed_detail", "data_grid.message.commit_failed", + "data_grid.message.rollback_failed", ]; const noPlaceholderKeys = [ "data_grid.message.change_set_build_failed", "data_grid.message.preview_sql_failed", "data_grid.message.transaction_committed", + "data_grid.message.transaction_rolled_back", "data_grid.message.no_changes_to_commit", "data_grid.message.copied_to_clipboard", "data_grid.message.no_field_name", @@ -639,6 +644,7 @@ describe("i18n catalog", () => { } expect(t("en-US", "data_grid.message.commit_failed", { detail: "" })).toContain(""); + expect(t("en-US", "data_grid.message.rollback_failed", { detail: "" })).toContain(""); expect(t("zh-CN", "data_grid.message.preview_sql_failed_detail", { detail: "" })).toContain(""); expect(t("de-DE", "data_grid.copy_sql.error.missing_table_name", { mode: "UPDATE" })).toContain("UPDATE"); }); @@ -1990,7 +1996,7 @@ describe("i18n catalog", () => { const aiContextSource = sliceBetween( source, "const buildQueryEditorAiContextPrompt = (connection: any, database: string): string => {", - "// SQL 常用内置函数(通用,适用于 MySQL/PostgreSQL/Oracle/SQL Server 等主流数据源)", + "// HMR 重载时释放旧注册避免补全和 hover 内容重复", ); for (const language of SUPPORTED_LANGUAGES) { @@ -2126,8 +2132,8 @@ describe("i18n catalog", () => { ); const diagnosePromptSource = sliceBetween( source, - "const prompt = translate('query_editor.ai_prompt.diagnose', {", - "{translate('query_editor.result.ai_diagnose')}", + " const handleDiagnoseExecutionError = () => {", + " const sqlEditorTransactionToolbar = (", ); const toolbarAndDiagnoseSource = `${toolbarPromptSource}\n${diagnosePromptSource}`; @@ -2169,12 +2175,7 @@ describe("i18n catalog", () => { sliceBetween( source, " // Register runQuery shortcut inside Monaco so it overrides Monaco's default keybinding", - " // HMR 重载时释放旧注册避免补全项重复", - ), - sliceBetween( - source, - " objectHoverActionRef.current?.dispose?.();", - " }, [languagePreference, showObjectInfoAtPosition]);", + " // HMR 重载或测试重置时,以全局状态为准,避免本地闭包状态和 provider 列表不同步。", ), sliceBetween( source, @@ -2265,11 +2266,11 @@ describe("i18n catalog", () => { "query_editor.empty_state.title", "query_editor.empty_state.description", ] as const; - const source = readQueryEditorSource(); + const source = readQueryEditorResultsPanelSource(); const emptyStateSource = sliceBetween( source, "
\r\n\r\n ", ); for (const language of SUPPORTED_LANGUAGES) { diff --git a/internal/db/batch_insert.go b/internal/db/batch_insert.go index 3bdd9cf..d2e7efc 100644 --- a/internal/db/batch_insert.go +++ b/internal/db/batch_insert.go @@ -38,16 +38,16 @@ func execParameterizedInsertBatches(config parameterizedInsertConfig) error { return nil } if strings.TrimSpace(config.Table) == "" { - return fmt.Errorf("表名不能为空") + return localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } if config.QuoteColumn == nil { - return fmt.Errorf("列名引用函数不能为空") + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_quote_column_required", nil) } if config.Placeholder == nil { - return fmt.Errorf("占位符函数不能为空") + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_placeholder_required", nil) } if config.Exec == nil { - return fmt.Errorf("执行函数不能为空") + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_exec_required", nil) } if config.Value == nil { config.Value = func(_ string, value interface{}) (interface{}, bool) { return value, false } @@ -70,7 +70,7 @@ func execParameterizedInsertBatches(config parameterizedInsertConfig) error { for range rows { res, err := config.Exec(config.EmptyInsertSQL(config.Table)) if err != nil { - return fmt.Errorf("插入失败:%v", err) + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_failed", map[string]any{"detail": err.Error()}) } if config.RequireAffected { if err := requireInsertAffected(res); err != nil { @@ -163,7 +163,7 @@ func execParameterizedInsertBatch(config parameterizedInsertConfig, rows []prepa ) res, err := config.Exec(query, args...) if err != nil { - return fmt.Errorf("插入失败:%v", err) + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_failed", map[string]any{"detail": err.Error()}) } if config.RequireAffected { if err := requireInsertAffected(res); err != nil { @@ -178,7 +178,7 @@ func requireInsertAffected(result sql.Result) error { return nil } if affected, err := result.RowsAffected(); err == nil && affected == 0 { - return fmt.Errorf("插入未生效:未影响任何行") + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_no_rows_affected", nil) } return nil } @@ -219,16 +219,16 @@ func execLiteralInsertBatches(config literalInsertConfig) error { return nil } if strings.TrimSpace(config.Table) == "" { - return fmt.Errorf("表名不能为空") + return localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } if config.QuoteColumn == nil { - return fmt.Errorf("列名引用函数不能为空") + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_quote_column_required", nil) } if config.Literal == nil { - return fmt.Errorf("字面量函数不能为空") + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_literal_required", nil) } if config.Exec == nil { - return fmt.Errorf("执行函数不能为空") + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_exec_required", nil) } if config.RowSeparator == "" { config.RowSeparator = ", " @@ -282,7 +282,10 @@ func execLiteralInsertBatch(config literalInsertConfig, rows []preparedInsertRow ) res, err := config.Exec(query) if err != nil { - return fmt.Errorf("插入失败:%v; sql=%s", err, query) + return localizedDatabaseRuntimeError("db.backend.error.batch_insert_failed_with_sql", map[string]any{ + "detail": err.Error(), + "sql": query, + }) } if config.RequireAffected { if err := requireInsertAffected(res); err != nil { diff --git a/internal/db/batch_insert_test.go b/internal/db/batch_insert_test.go index 44b96b1..58214cc 100644 --- a/internal/db/batch_insert_test.go +++ b/internal/db/batch_insert_test.go @@ -3,9 +3,13 @@ package db import ( "database/sql" "database/sql/driver" + "errors" "fmt" + "os" "strings" "testing" + + "GoNavi-Wails/shared/i18n" ) func TestExecParameterizedInsertBatchesGroupsRowsByColumnSet(t *testing.T) { @@ -229,3 +233,218 @@ func TestExecParameterizedInsertBatchesRunsEmptyInsertSQLWhenAllColumnsOmitted(t } } } + +func TestBatchInsertErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + baseErr := errors.New("driver insert failed") + rows := []map[string]interface{}{{"id": 1}} + quoteColumn := func(column string) string { return `"` + column + `"` } + placeholder := func(int) string { return "?" } + + cases := []struct { + name string + call func() error + want string + }{ + { + name: "parameterized table name required", + call: func() error { + return execParameterizedInsertBatches(parameterizedInsertConfig{ + Table: " ", + Rows: rows, + QuoteColumn: quoteColumn, + Placeholder: placeholder, + Exec: func(string, ...interface{}) (sql.Result, error) { + return driver.RowsAffected(1), nil + }, + }) + }, + want: "Table name is required", + }, + { + name: "parameterized quote function required", + call: func() error { + return execParameterizedInsertBatches(parameterizedInsertConfig{ + Table: `"users"`, + Rows: rows, + Placeholder: placeholder, + Exec: func(string, ...interface{}) (sql.Result, error) { + return driver.RowsAffected(1), nil + }, + }) + }, + want: "Column quoting function is required", + }, + { + name: "parameterized placeholder function required", + call: func() error { + return execParameterizedInsertBatches(parameterizedInsertConfig{ + Table: `"users"`, + Rows: rows, + QuoteColumn: quoteColumn, + Exec: func(string, ...interface{}) (sql.Result, error) { + return driver.RowsAffected(1), nil + }, + }) + }, + want: "Placeholder function is required", + }, + { + name: "parameterized exec function required", + call: func() error { + return execParameterizedInsertBatches(parameterizedInsertConfig{ + Table: `"users"`, + Rows: rows, + QuoteColumn: quoteColumn, + Placeholder: placeholder, + }) + }, + want: "Execution function is required", + }, + { + name: "parameterized insert failed keeps raw detail", + call: func() error { + return execParameterizedInsertBatches(parameterizedInsertConfig{ + Table: `"users"`, + Rows: rows, + QuoteColumn: quoteColumn, + Placeholder: placeholder, + Exec: func(string, ...interface{}) (sql.Result, error) { + return nil, baseErr + }, + }) + }, + want: "Insert failed: driver insert failed", + }, + { + name: "parameterized insert no rows affected", + call: func() error { + return execParameterizedInsertBatches(parameterizedInsertConfig{ + Table: `"users"`, + Rows: rows, + QuoteColumn: quoteColumn, + Placeholder: placeholder, + RequireAffected: true, + Exec: func(string, ...interface{}) (sql.Result, error) { + return driver.RowsAffected(0), nil + }, + }) + }, + want: "Insert did not take effect: no rows were affected", + }, + { + name: "literal function required", + call: func() error { + return execLiteralInsertBatches(literalInsertConfig{ + Table: `"users"`, + Rows: rows, + QuoteColumn: quoteColumn, + Exec: func(string) (sql.Result, error) { + return driver.RowsAffected(1), nil + }, + }) + }, + want: "Literal function is required", + }, + { + name: "literal insert failed keeps raw detail and sql", + call: func() error { + return execLiteralInsertBatches(literalInsertConfig{ + Table: `"users"`, + Rows: rows, + QuoteColumn: quoteColumn, + Literal: func(value interface{}) string { return fmt.Sprintf("%v", value) }, + Exec: func(string) (sql.Result, error) { + return nil, baseErr + }, + }) + }, + want: `Insert failed: driver insert failed; SQL=INSERT INTO "users" ("id") VALUES (1)`, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected batch insert error") + } + if err.Error() != tc.want { + t.Fatalf("expected %q, got %q", tc.want, err.Error()) + } + for _, raw := range []string{"表名不能为空", "列名引用函数不能为空", "占位符函数不能为空", "执行函数不能为空", "字面量函数不能为空", "插入失败", "插入未生效"} { + if strings.Contains(err.Error(), raw) { + t.Fatalf("expected no raw Chinese batch insert text %q in %q", raw, err.Error()) + } + } + }) + } +} + +func TestBatchInsertErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("batch_insert.go") + if err != nil { + t.Fatalf("read batch_insert.go: %v", err) + } + source := string(sourceBytes) + + for _, rawMessage := range []string{ + `fmt.Errorf("表名不能为空")`, + `fmt.Errorf("列名引用函数不能为空")`, + `fmt.Errorf("占位符函数不能为空")`, + `fmt.Errorf("执行函数不能为空")`, + `fmt.Errorf("字面量函数不能为空")`, + `fmt.Errorf("插入失败:%v", err)`, + `fmt.Errorf("插入失败:%v; sql=%s", err, query)`, + `fmt.Errorf("插入未生效:未影响任何行")`, + } { + if strings.Contains(source, rawMessage) { + t.Fatalf("batch_insert.go still contains raw batch insert text %q", rawMessage) + } + } + + for _, key := range []string{ + "db.backend.error.table_name_required", + "db.backend.error.batch_insert_quote_column_required", + "db.backend.error.batch_insert_placeholder_required", + "db.backend.error.batch_insert_exec_required", + "db.backend.error.batch_insert_literal_required", + "db.backend.error.batch_insert_failed", + "db.backend.error.batch_insert_failed_with_sql", + "db.backend.error.batch_insert_no_rows_affected", + } { + if !strings.Contains(source, key) { + t.Fatalf("batch_insert.go does not reference i18n key %q", key) + } + } +} + +func TestBatchInsertErrorCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + keys := []string{ + "db.backend.error.table_name_required", + "db.backend.error.batch_insert_quote_column_required", + "db.backend.error.batch_insert_placeholder_required", + "db.backend.error.batch_insert_exec_required", + "db.backend.error.batch_insert_literal_required", + "db.backend.error.batch_insert_failed", + "db.backend.error.batch_insert_failed_with_sql", + "db.backend.error.batch_insert_no_rows_affected", + } + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range keys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing batch insert key %q", language, key) + } + } + } +} diff --git a/internal/db/clickhouse_impl.go b/internal/db/clickhouse_impl.go index ff12e72..31f773b 100644 --- a/internal/db/clickhouse_impl.go +++ b/internal/db/clickhouse_impl.go @@ -508,33 +508,55 @@ func sanitizeClickHouseErrorMessage(err error) string { func clickHouseAttemptFailureMessage(protocol clickhouse.Protocol, err error) string { if protocol == clickhouse.HTTP && isClickHouseHTTPClientProtocolVersionUnsupported(err) { - return "当前 ClickHouse HTTP 端口不支持 client_protocol_version(常见于 ClickHouse 22.8),将使用 HTTP 兼容模式重试;如仍失败请确认连接协议和端口" + return localizedDriverRuntimeText("db.backend.error.clickhouse_http_client_protocol_version_unsupported", nil) } if isClickHouseProtocolMismatch(err) { if protocol == clickhouse.Native { - return "服务端响应不像 Native 握手,当前端口更像 HTTP/HTTPS 端口;请选择 HTTP 协议,或确认 ClickHouse Native 端口" + return localizedDriverRuntimeText("db.backend.error.clickhouse_native_protocol_mismatch", nil) } - return "服务端响应不像 HTTP 响应,当前端口更像 Native 端口;请选择 Native 协议,或确认 ClickHouse HTTP 端口" + return localizedDriverRuntimeText("db.backend.error.clickhouse_http_protocol_mismatch", nil) } message := sanitizeClickHouseErrorMessage(err) if message == "" { - return "未知错误" + return localizedDriverRuntimeText("db.backend.error.clickhouse_unknown_error", nil) } return message } +func clickHouseTLSConfigFailedMessage(attempt int, protocol string, err error) string { + return localizedDriverRuntimeText("db.backend.error.clickhouse_attempt_tls_config_failed", map[string]any{ + "attempt": attempt, + "protocol": protocol, + "detail": err, + }) +} + +func clickHouseAttemptValidationFailedMessage(attempt int, protocol string, detail string) string { + return localizedDriverRuntimeText("db.backend.error.clickhouse_attempt_validation_failed", map[string]any{ + "attempt": attempt, + "protocol": protocol, + "detail": detail, + }) +} + func clickHouseConnectFailureSummary(config connection.ConnectionConfig, failures []string) string { protocolMode := normalizeClickHouseProtocol(config.ClickHouseProtocol) - detail := strings.Join(failures, ";") + detail := strings.Join(failures, "; ") if strings.TrimSpace(detail) == "" { - detail = "未获取到驱动返回的错误详情" + detail = localizedDriverRuntimeText("db.backend.error.clickhouse_driver_detail_missing", nil) } if protocolMode != clickHouseProtocolAuto { - return fmt.Sprintf("ClickHouse 连接验证失败:已按用户选择使用 %s 协议连接 %s:%d。%s", - strings.ToUpper(protocolMode), config.Host, config.Port, detail) + return localizedDriverRuntimeText("db.backend.error.clickhouse_validation_failed_manual", map[string]any{ + "protocol": strings.ToUpper(protocolMode), + "host": config.Host, + "port": config.Port, + "detail": detail, + }) } - return fmt.Sprintf("ClickHouse 连接验证失败:自动协议探测未成功(Native 常见端口 9000/9440,HTTP 常见端口 %s;非标端口建议在连接协议中手动指定)。%s", - clickHouseHTTPPortHint, detail) + return localizedDriverRuntimeText("db.backend.error.clickhouse_validation_failed_auto", map[string]any{ + "httpPorts": clickHouseHTTPPortHint, + "detail": detail, + }) } func withClickHouseProtocol(config connection.ConnectionConfig, protocol clickhouse.Protocol) connection.ConnectionConfig { @@ -568,7 +590,7 @@ func clickHouseProtocolsForAttempt(config connection.ConnectionConfig) []clickho func (c *ClickHouseDB) Connect(config connection.ConnectionConfig) error { if supported, reason := DriverRuntimeSupportStatus("clickhouse"); !supported { if strings.TrimSpace(reason) == "" { - reason = "ClickHouse 纯 Go 驱动未启用,请先在驱动管理中安装启用" + reason = localizedDriverRuntimeText("driver_manager.backend.status.optional_disabled", map[string]any{"name": "ClickHouse"}) } return fmt.Errorf("%s", reason) } @@ -636,7 +658,7 @@ func (c *ClickHouseDB) Connect(config connection.ConnectionConfig) error { idx+1, len(attempts), clickHouseProtocolName(protocol), protocolConfig.Host, protocolConfig.Port, protocolConfig.UseSSL, stripHTTPClientProtocolVersion) opts, err := c.buildClickHouseOptionsWithHTTPCompatibility(protocolConfig, stripHTTPClientProtocolVersion) if err != nil { - failures = append(failures, fmt.Sprintf("第%d次 TLS 配置失败(protocol=%s): %v", idx+1, protocol.String(), err)) + failures = append(failures, clickHouseTLSConfigFailedMessage(idx+1, protocol.String(), err)) logger.Warnf("ClickHouse TLS 配置失败:第%d组/%d 协议=%s 地址=%s:%d SSL=%t 原因=%v", idx+1, len(attempts), clickHouseProtocolName(protocol), protocolConfig.Host, protocolConfig.Port, protocolConfig.UseSSL, err) lastProtocolErr = err @@ -646,7 +668,7 @@ func (c *ClickHouseDB) Connect(config connection.ConnectionConfig) error { if err := c.Ping(); err != nil { lastProtocolErr = err failureMessage := clickHouseAttemptFailureMessage(protocol, err) - failures = append(failures, fmt.Sprintf("第%d次连接验证失败(protocol=%s): %s", idx+1, protocol.String(), failureMessage)) + failures = append(failures, clickHouseAttemptValidationFailedMessage(idx+1, protocol.String(), failureMessage)) logger.Warnf("ClickHouse 连接尝试失败:第%d组/%d 协议=%s 地址=%s:%d SSL=%t HTTP兼容=%t 原因=%s", idx+1, len(attempts), clickHouseProtocolName(protocol), protocolConfig.Host, protocolConfig.Port, protocolConfig.UseSSL, stripHTTPClientProtocolVersion, failureMessage) if c.conn != nil { @@ -911,7 +933,7 @@ func (c *ClickHouseDB) GetCreateStatement(dbName, tableName string) (string, err return "", err } if len(data) == 0 { - return "", fmt.Errorf("未找到建表语句") + return "", localizedDatabaseRuntimeError("db.backend.error.create_table_statement_not_found", nil) } row := data[0] if val, ok := getClickHouseValueFromRow(row, "statement", "create_statement", "sql", "query"); ok { @@ -934,7 +956,7 @@ func (c *ClickHouseDB) GetCreateStatement(dbName, tableName string) (string, err if longest != "" { return longest, nil } - return "", fmt.Errorf("未找到建表语句") + return "", localizedDatabaseRuntimeError("db.backend.error.create_table_statement_not_found", nil) } func (c *ClickHouseDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -1093,7 +1115,7 @@ func (c *ClickHouseDB) GetTriggers(dbName, tableName string) ([]connection.Trigg func (c *ClickHouseDB) resolveDatabaseAndTable(dbName, tableName string) (string, string, error) { rawTable := strings.TrimSpace(tableName) if rawTable == "" { - return "", "", fmt.Errorf("表名不能为空") + return "", "", localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } resolvedDB := strings.TrimSpace(dbName) @@ -1114,7 +1136,7 @@ func (c *ClickHouseDB) resolveDatabaseAndTable(dbName, tableName string) (string resolvedDB = defaultClickHouseDatabase } if resolvedTable == "" { - return "", "", fmt.Errorf("表名不能为空") + return "", "", localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } return resolvedDB, resolvedTable, nil } @@ -1209,7 +1231,10 @@ func (c *ClickHouseDB) ApplyChanges(tableName string, changes connection.ChangeS } query := fmt.Sprintf("ALTER TABLE %s DELETE WHERE %s", qualifiedTable, whereExpr) if _, err := c.conn.Exec(query); err != nil { - return fmt.Errorf("delete error: %v; sql=%s", err, query) + return localizedDatabaseRuntimeError("db.backend.error.clickhouse_delete_failed_with_sql", map[string]any{ + "detail": err.Error(), + "sql": query, + }) } } @@ -1221,7 +1246,10 @@ func (c *ClickHouseDB) ApplyChanges(tableName string, changes connection.ChangeS } query := fmt.Sprintf("ALTER TABLE %s UPDATE %s WHERE %s", qualifiedTable, setExpr, whereExpr) if _, err := c.conn.Exec(query); err != nil { - return fmt.Errorf("update error: %v; sql=%s", err, query) + return localizedDatabaseRuntimeError("db.backend.error.clickhouse_update_failed_with_sql", map[string]any{ + "detail": err.Error(), + "sql": query, + }) } } diff --git a/internal/db/clickhouse_impl_test.go b/internal/db/clickhouse_impl_test.go index db19a5e..7cd25ab 100644 --- a/internal/db/clickhouse_impl_test.go +++ b/internal/db/clickhouse_impl_test.go @@ -9,31 +9,52 @@ import ( "errors" "io" "net/http" + "os" "strings" "sync" "testing" "time" "GoNavi-Wails/internal/connection" + "GoNavi-Wails/shared/i18n" clickhouse "github.com/ClickHouse/clickhouse-go/v2" ) const fakeClickHouseDriverName = "gonavi-fake-clickhouse" +var clickHouseProtocolFailureI18nKeys = []string{ + "db.backend.error.clickhouse_http_client_protocol_version_unsupported", + "db.backend.error.clickhouse_native_protocol_mismatch", + "db.backend.error.clickhouse_http_protocol_mismatch", + "db.backend.error.clickhouse_unknown_error", + "db.backend.error.clickhouse_driver_detail_missing", + "db.backend.error.clickhouse_attempt_tls_config_failed", + "db.backend.error.clickhouse_attempt_validation_failed", + "db.backend.error.clickhouse_validation_failed_manual", + "db.backend.error.clickhouse_validation_failed_auto", +} + +const rawClickHouseCreateStatementNotFoundText = "未找到建表语句" + var ( registerFakeClickHouseDriverOnce sync.Once fakeClickHouseStateMu sync.Mutex fakeClickHouseState = struct { pingErr error queryErr error + execErr error queryResults map[string]fakeClickHouseQueryResult lastQuery string queries []string + lastExec string + execQueries []string }{ lastQuery: "", queryResults: map[string]fakeClickHouseQueryResult{}, queries: nil, + lastExec: "", + execQueries: nil, } ) @@ -134,6 +155,229 @@ func TestClickHouseGetDatabasesFallsBackToCurrentDatabase(t *testing.T) { } } +func TestClickHouseCreateStatementNotFoundUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + tests := []struct { + name string + result fakeClickHouseQueryResult + }{ + { + name: "empty rows", + result: fakeClickHouseQueryResult{ + columns: []string{"statement"}, + rows: nil, + }, + }, + { + name: "row without CREATE statement", + result: fakeClickHouseQueryResult{ + columns: []string{"note"}, + rows: [][]driver.Value{ + {"SELECT 1"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + registerFakeClickHouseDriverOnce.Do(func() { + sql.Register(fakeClickHouseDriverName, fakeClickHouseDriver{}) + }) + + conn, err := sql.Open(fakeClickHouseDriverName, "") + if err != nil { + t.Fatalf("open fake clickhouse db failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + + const showCreateSQL = "SHOW CREATE TABLE `app`.`orders`" + fakeClickHouseStateMu.Lock() + fakeClickHouseState.pingErr = nil + fakeClickHouseState.queryErr = nil + fakeClickHouseState.queryResults = map[string]fakeClickHouseQueryResult{ + showCreateSQL: tt.result, + } + fakeClickHouseState.lastQuery = "" + fakeClickHouseState.queries = nil + fakeClickHouseStateMu.Unlock() + + clickhouseDB := &ClickHouseDB{conn: conn} + _, err = clickhouseDB.GetCreateStatement("app", "orders") + if err == nil { + t.Fatal("expected ClickHouse GetCreateStatement to fail") + } + if err.Error() != "The CREATE TABLE statement was not found" { + t.Fatalf("expected English create-statement error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawClickHouseCreateStatementNotFoundText) { + t.Fatalf("expected no raw Chinese create-statement text, got %q", err.Error()) + } + }) + } +} + +func TestClickHouseCreateStatementSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("clickhouse_impl.go") + if err != nil { + t.Fatalf("read clickhouse_impl.go: %v", err) + } + source := string(sourceBytes) + + rawMessage := `fmt.Errorf("` + rawClickHouseCreateStatementNotFoundText + `")` + if strings.Contains(source, rawMessage) { + t.Fatalf("clickhouse_impl.go still contains raw create-statement text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.create_table_statement_not_found") { + t.Fatal("clickhouse_impl.go does not reference db.backend.error.create_table_statement_not_found") + } +} + +func TestClickHouseApplyChangesErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + registerFakeClickHouseDriverOnce.Do(func() { + sql.Register(fakeClickHouseDriverName, fakeClickHouseDriver{}) + }) + + tests := []struct { + name string + changes connection.ChangeSet + wantText string + forbiddenRaw []string + }{ + { + name: "delete failure", + changes: connection.ChangeSet{ + Deletes: []map[string]interface{}{ + {"id": int64(42)}, + }, + }, + wantText: "Failed to delete ClickHouse rows", + forbiddenRaw: []string{"delete error", "删除失败"}, + }, + { + name: "update failure", + changes: connection.ChangeSet{ + Updates: []connection.UpdateRow{ + { + Keys: map[string]interface{}{"id": int64(42)}, + Values: map[string]interface{}{"name": "Alice"}, + }, + }, + }, + wantText: "Failed to update ClickHouse rows", + forbiddenRaw: []string{"update error", "更新失败"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + conn, err := sql.Open(fakeClickHouseDriverName, "") + if err != nil { + t.Fatalf("open fake clickhouse db failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + + fakeClickHouseStateMu.Lock() + fakeClickHouseState.execErr = errors.New("driver raw failure") + fakeClickHouseState.lastExec = "" + fakeClickHouseState.execQueries = nil + fakeClickHouseStateMu.Unlock() + + clickhouseDB := &ClickHouseDB{conn: conn, database: "analytics"} + err = clickhouseDB.ApplyChanges("orders", tt.changes) + if err == nil { + t.Fatal("expected ApplyChanges to fail") + } + got := err.Error() + if !strings.Contains(got, tt.wantText) { + t.Fatalf("expected localized wrapper %q, got %q", tt.wantText, got) + } + if !strings.Contains(got, "driver raw failure") { + t.Fatalf("expected raw driver detail to remain, got %q", got) + } + if !strings.Contains(got, "ALTER TABLE `analytics`.`orders`") { + t.Fatalf("expected raw SQL to remain, got %q", got) + } + for _, raw := range tt.forbiddenRaw { + if strings.Contains(got, raw) { + t.Fatalf("expected no raw wrapper %q, got %q", raw, got) + } + } + }) + } +} + +func TestClickHouseTableNameRequiredUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + clickhouseDB := &ClickHouseDB{} + _, _, err := clickhouseDB.resolveDatabaseAndTable("", " ") + if err == nil { + t.Fatal("expected table-name-required error") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawClickHouseTableNameRequiredText()) { + t.Fatalf("expected no raw Chinese table-name-required text, got %q", err.Error()) + } +} + +func TestClickHouseApplyChangesErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("clickhouse_impl.go") + if err != nil { + t.Fatalf("read clickhouse_impl.go: %v", err) + } + source := string(sourceBytes) + + for _, rawMessage := range []string{ + `fmt.Errorf("` + rawClickHouseTableNameRequiredText() + `")`, + `fmt.Errorf("delete error: %v; sql=%s", err, query)`, + `fmt.Errorf("update error: %v; sql=%s", err, query)`, + } { + if strings.Contains(source, rawMessage) { + t.Fatalf("clickhouse_impl.go still contains raw ApplyChanges text %q", rawMessage) + } + } + for _, key := range clickHouseApplyChangesI18nKeys() { + if !strings.Contains(source, key) { + t.Fatalf("clickhouse_impl.go does not reference i18n key %q", key) + } + } +} + +func TestClickHouseApplyChangesCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range clickHouseApplyChangesI18nKeys() { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing ClickHouse ApplyChanges key %q", language, key) + } + } + } +} + func TestDetectClickHouseProtocolTreatsHTTPPortsAsHTTP(t *testing.T) { tests := []struct { name string @@ -323,6 +567,131 @@ func TestClickHouseHTTPClientProtocolVersionUnsupportedEnablesCompatibilityRetry } } +func TestClickHouseProtocolFailureMessagesUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + clientProtocolErr := errors.New(`Code: 115. DB::Exception: Unknown setting client_protocol_version. (UNKNOWN_SETTING)`) + compatMessage := clickHouseAttemptFailureMessage(clickhouse.HTTP, clientProtocolErr) + if !strings.Contains(compatMessage, "client_protocol_version") || !strings.Contains(compatMessage, "HTTP compatibility mode") { + t.Fatalf("expected English compatibility hint, got %q", compatMessage) + } + if strings.Contains(compatMessage, "兼容模式") || strings.Contains(compatMessage, "当前") { + t.Fatalf("expected no Chinese compatibility hint, got %q", compatMessage) + } + + nativeMismatch := clickHouseAttemptFailureMessage(clickhouse.Native, errors.New("code: 27, message: Cannot parse input: expected '(' before: '\x02\x00\x01\x00'")) + if !strings.Contains(nativeMismatch, "does not look like a Native handshake") { + t.Fatalf("expected English native mismatch hint, got %q", nativeMismatch) + } + if strings.Contains(nativeMismatch, "不像 Native") { + t.Fatalf("expected no Chinese native mismatch hint, got %q", nativeMismatch) + } + + httpMismatch := clickHouseAttemptFailureMessage(clickhouse.HTTP, errors.New("malformed HTTP response")) + if !strings.Contains(httpMismatch, "does not look like an HTTP response") { + t.Fatalf("expected English HTTP mismatch hint, got %q", httpMismatch) + } + if strings.Contains(httpMismatch, "不像 HTTP") { + t.Fatalf("expected no Chinese HTTP mismatch hint, got %q", httpMismatch) + } + + unknownMessage := clickHouseAttemptFailureMessage(clickhouse.HTTP, nil) + if unknownMessage != "Unknown error" { + t.Fatalf("expected localized unknown error, got %q", unknownMessage) + } +} + +func TestClickHouseConnectFailureSummaryUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + manual := clickHouseConnectFailureSummary(connection.ConnectionConfig{ + Host: "clickhouse.local", + Port: 9000, + ClickHouseProtocol: clickHouseProtocolNative, + }, []string{"driver raw detail"}) + if !strings.Contains(manual, "ClickHouse connection validation failed") || + !strings.Contains(manual, "used user-selected NATIVE protocol") || + !strings.Contains(manual, "driver raw detail") { + t.Fatalf("expected English manual protocol failure summary with raw detail, got %q", manual) + } + if strings.Contains(manual, "连接验证失败") || strings.Contains(manual, "用户选择") || strings.Contains(manual, "第1次") { + t.Fatalf("expected no Chinese manual summary, got %q", manual) + } + + manualWithMultipleDetails := clickHouseConnectFailureSummary(connection.ConnectionConfig{ + Host: "clickhouse.local", + Port: 9000, + ClickHouseProtocol: clickHouseProtocolNative, + }, []string{"first raw detail", "second raw detail"}) + if !strings.Contains(manualWithMultipleDetails, "first raw detail; second raw detail") { + t.Fatalf("expected ASCII separator between raw details, got %q", manualWithMultipleDetails) + } + if strings.Contains(manualWithMultipleDetails, ";") { + t.Fatalf("expected no Chinese separator between raw details, got %q", manualWithMultipleDetails) + } + + auto := clickHouseConnectFailureSummary(connection.ConnectionConfig{ + Host: "clickhouse.local", + Port: 8123, + }, nil) + if !strings.Contains(auto, "Automatic protocol detection failed") || + !strings.Contains(auto, "No driver error details were returned") { + t.Fatalf("expected English auto protocol failure summary, got %q", auto) + } + if strings.Contains(auto, "自动协议探测") || strings.Contains(auto, "未获取到") { + t.Fatalf("expected no Chinese auto summary, got %q", auto) + } +} + +func TestClickHouseProtocolFailureSourceUsesI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("clickhouse_impl.go") + if err != nil { + t.Fatalf("read clickhouse_impl.go: %v", err) + } + source := string(sourceBytes) + for _, rawMessage := range []string{ + "当前 ClickHouse HTTP 端口不支持 client_protocol_version", + "服务端响应不像 Native 握手", + "服务端响应不像 HTTP 响应", + "未知错误", + "未获取到驱动返回的错误详情", + "ClickHouse 连接验证失败", + "第%d次 TLS 配置失败", + "第%d次连接验证失败", + } { + if strings.Contains(source, rawMessage) { + t.Fatalf("clickhouse_impl.go still contains raw user-facing ClickHouse protocol text %q", rawMessage) + } + } + for _, key := range clickHouseProtocolFailureI18nKeys { + if !strings.Contains(source, key) { + t.Fatalf("clickhouse_impl.go does not reference i18n key %q", key) + } + } +} + +func TestClickHouseProtocolFailureCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range clickHouseProtocolFailureI18nKeys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing ClickHouse protocol failure key %q", language, key) + } + } + } +} + func TestClickHouseHTTPClientProtocolVersionStripperRemovesDriverQueryParam(t *testing.T) { var seenQuery string stripper := clickHouseHTTPClientProtocolVersionStripper{ @@ -446,6 +815,18 @@ func protocolNames(protocols []clickhouse.Protocol) []string { return names } +func rawClickHouseTableNameRequiredText() string { + return string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) +} + +func clickHouseApplyChangesI18nKeys() []string { + return []string{ + "db.backend.error.table_name_required", + "db.backend.error.clickhouse_delete_failed_with_sql", + "db.backend.error.clickhouse_update_failed_with_sql", + } +} + type fakeClickHouseDriver struct{} func (fakeClickHouseDriver) Open(name string) (driver.Conn, error) { @@ -489,6 +870,17 @@ func (fakeClickHouseConn) QueryContext(ctx context.Context, query string, args [ return &fakeClickHouseRows{}, nil } +func (fakeClickHouseConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + fakeClickHouseStateMu.Lock() + defer fakeClickHouseStateMu.Unlock() + fakeClickHouseState.lastExec = query + fakeClickHouseState.execQueries = append(fakeClickHouseState.execQueries, query) + if fakeClickHouseState.execErr != nil { + return nil, fakeClickHouseState.execErr + } + return driver.RowsAffected(1), nil +} + type fakeClickHouseRows struct { columns []string rows [][]driver.Value diff --git a/internal/db/custom_impl.go b/internal/db/custom_impl.go index 0ad1ba8..fcf9007 100644 --- a/internal/db/custom_impl.go +++ b/internal/db/custom_impl.go @@ -38,7 +38,7 @@ func (c *CustomDB) Connect(config connection.ConnectionConfig) error { c.driver = driver c.pingTimeout = getConnectTimeout(config) if err := c.Ping(); err != nil { - return fmt.Errorf("连接建立后验证失败:%w", err) + return wrapDatabaseConnectionVerifyError(err) } return nil } @@ -49,11 +49,15 @@ func formatCustomDriverOpenError(driver string, err error) error { } if strings.Contains(strings.ToLower(err.Error()), "unknown driver") { if isLikelySystemODBCDriverName(driver) { - return fmt.Errorf("打开数据库连接失败:自定义连接不支持直接填写系统 ODBC/JDBC 驱动名 %q;请填写 GoNavi 已注册的 Go database/sql 驱动名。当前版本未注册通用 ODBC 驱动,因此暂不支持通过 %q 连接 InterSystems IRIS:%w", driver, driver, err) + return fmt.Errorf("%s%w", localizedDriverRuntimeText("db.backend.error.custom_driver_system_odbc_unsupported_prefix", map[string]any{ + "driver": driver, + }), err) } - return fmt.Errorf("打开数据库连接失败:自定义连接驱动 %q 未在 GoNavi 中注册;请填写已注册的 Go database/sql 驱动名,不能填写系统 ODBC/JDBC 驱动名:%w", driver, err) + return fmt.Errorf("%s%w", localizedDriverRuntimeText("db.backend.error.custom_driver_unregistered_prefix", map[string]any{ + "driver": driver, + }), err) } - return fmt.Errorf("打开数据库连接失败:%w", err) + return wrapDatabaseConnectionOpenError(err) } func isLikelySystemODBCDriverName(driver string) bool { @@ -73,7 +77,7 @@ func (c *CustomDB) Close() error { func (c *CustomDB) Ping() error { if c.conn == nil { - return fmt.Errorf("连接未打开") + return localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } timeout := c.pingTimeout if timeout <= 0 { @@ -86,7 +90,7 @@ func (c *CustomDB) Ping() error { func (c *CustomDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if c.conn == nil { - return nil, nil, fmt.Errorf("连接未打开") + return nil, nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } rows, err := c.conn.QueryContext(ctx, query) @@ -100,7 +104,7 @@ func (c *CustomDB) QueryContext(ctx context.Context, query string) ([]map[string func (c *CustomDB) Query(query string) ([]map[string]interface{}, []string, error) { if c.conn == nil { - return nil, nil, fmt.Errorf("连接未打开") + return nil, nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } rows, err := c.conn.Query(query) @@ -120,7 +124,7 @@ func (c *CustomDB) scanDialect() string { func (c *CustomDB) ExecContext(ctx context.Context, query string) (int64, error) { if c.conn == nil { - return 0, fmt.Errorf("连接未打开") + return 0, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } res, err := c.conn.ExecContext(ctx, query) if err != nil { @@ -131,7 +135,7 @@ func (c *CustomDB) ExecContext(ctx context.Context, query string) (int64, error) func (c *CustomDB) Exec(query string) (int64, error) { if c.conn == nil { - return 0, fmt.Errorf("连接未打开") + return 0, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } res, err := c.conn.Exec(query) if err != nil { @@ -384,7 +388,7 @@ func (c *CustomDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDe func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if c.conn == nil { - return fmt.Errorf("连接未打开") + return localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } tx, err := c.conn.Begin() @@ -464,7 +468,7 @@ func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("删除失败:%v", err) + return localizedDatabaseRuntimeError("db.backend.error.row_delete_failed", map[string]any{"detail": err.Error()}) } } @@ -492,12 +496,12 @@ func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) } if len(wheres) == 0 { - return fmt.Errorf("更新操作需要主键条件") + return localizedDatabaseRuntimeError("db.backend.error.row_update_key_conditions_required", nil) } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("更新失败:%v", err) + return localizedDatabaseRuntimeError("db.backend.error.row_update_failed", map[string]any{"detail": err.Error()}) } } diff --git a/internal/db/custom_impl_test.go b/internal/db/custom_impl_test.go index a43d923..e9bf819 100644 --- a/internal/db/custom_impl_test.go +++ b/internal/db/custom_impl_test.go @@ -1,18 +1,34 @@ package db import ( + "context" "database/sql" "database/sql/driver" + "errors" + "os" "strings" + "sync" "testing" "GoNavi-Wails/internal/connection" + "GoNavi-Wails/shared/i18n" ) const customMySQLDSNRecordingDriverName = "custom-mysql-dsn-recording" var customMySQLDSNRecordingLastDSN string +const customApplyChangesI18nDriverName = "custom-applychanges-i18n" + +var ( + registerCustomApplyChangesI18nDriverOnce sync.Once + customApplyChangesI18nStateMu sync.Mutex + customApplyChangesI18nState = struct { + failPrefix string + err error + }{} +) + type customMySQLDSNRecordingDriver struct{} func (d customMySQLDSNRecordingDriver) Open(name string) (driver.Conn, error) { @@ -34,10 +50,316 @@ func (c customMySQLDSNRecordingConn) Begin() (driver.Tx, error) { return nil, driver.ErrSkip } +type customApplyChangesI18nDriver struct{} + +type customApplyChangesI18nConn struct{} + +type customApplyChangesI18nTx struct{} + +func (d customApplyChangesI18nDriver) Open(name string) (driver.Conn, error) { + return customApplyChangesI18nConn{}, nil +} + +func (c customApplyChangesI18nConn) Prepare(query string) (driver.Stmt, error) { + return nil, errors.New("prepare not implemented") +} + +func (c customApplyChangesI18nConn) Close() error { + return nil +} + +func (c customApplyChangesI18nConn) Begin() (driver.Tx, error) { + return customApplyChangesI18nTx{}, nil +} + +func (c customApplyChangesI18nConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { + return customApplyChangesI18nTx{}, nil +} + +func (c customApplyChangesI18nConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + customApplyChangesI18nStateMu.Lock() + defer customApplyChangesI18nStateMu.Unlock() + + normalizedQuery := strings.ToUpper(strings.TrimSpace(query)) + if customApplyChangesI18nState.err != nil && strings.HasPrefix(normalizedQuery, customApplyChangesI18nState.failPrefix) { + return nil, customApplyChangesI18nState.err + } + return driver.RowsAffected(1), nil +} + +func (tx customApplyChangesI18nTx) Commit() error { + return nil +} + +func (tx customApplyChangesI18nTx) Rollback() error { + return nil +} + func init() { sql.Register(customMySQLDSNRecordingDriverName, customMySQLDSNRecordingDriver{}) } +func openCustomApplyChangesI18nDB(t *testing.T, failPrefix string, err error) *sql.DB { + t.Helper() + + registerCustomApplyChangesI18nDriverOnce.Do(func() { + sql.Register(customApplyChangesI18nDriverName, customApplyChangesI18nDriver{}) + }) + + customApplyChangesI18nStateMu.Lock() + customApplyChangesI18nState.failPrefix = failPrefix + customApplyChangesI18nState.err = err + customApplyChangesI18nStateMu.Unlock() + + db, openErr := sql.Open(customApplyChangesI18nDriverName, "") + if openErr != nil { + t.Fatalf("open custom ApplyChanges i18n test DB failed: %v", openErr) + } + t.Cleanup(func() { + _ = db.Close() + customApplyChangesI18nStateMu.Lock() + customApplyChangesI18nState.failPrefix = "" + customApplyChangesI18nState.err = nil + customApplyChangesI18nStateMu.Unlock() + }) + return db +} + +func TestCustomDBApplyChangesErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + rawConnectionNotOpenText := string([]rune{0x8fde, 0x63a5, 0x672a, 0x6253, 0x5f00}) + rawDeleteFailedText := string([]rune{0x5220, 0x9664, 0x5931, 0x8d25}) + rawUpdateKeyConditionsRequiredText := string([]rune{0x66f4, 0x65b0, 0x64cd, 0x4f5c, 0x9700, 0x8981, 0x4e3b, 0x952e, 0x6761, 0x4ef6}) + rawUpdateFailedText := string([]rune{0x66f4, 0x65b0, 0x5931, 0x8d25}) + + t.Run("connection not open", func(t *testing.T) { + err := (&CustomDB{}).ApplyChanges("orders", connection.ChangeSet{}) + if err == nil { + t.Fatal("expected connection-not-open error") + } + if err.Error() != "Connection is not open" { + t.Fatalf("expected English connection-not-open error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawConnectionNotOpenText) { + t.Fatalf("expected no raw connection-not-open text, got %q", err.Error()) + } + }) + + t.Run("delete failure", func(t *testing.T) { + rawErr := errors.New("driver raw delete failure") + customDB := &CustomDB{conn: openCustomApplyChangesI18nDB(t, "DELETE", rawErr), driver: "mysql"} + + err := customDB.ApplyChanges("orders", connection.ChangeSet{ + Deletes: []map[string]interface{}{ + {"id": int64(42)}, + }, + }) + if err == nil { + t.Fatal("expected delete failure") + } + if err.Error() != "Delete failed: driver raw delete failure" { + t.Fatalf("expected English delete failure, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawDeleteFailedText) { + t.Fatalf("expected no raw delete wrapper, got %q", err.Error()) + } + }) + + t.Run("update key condition required", func(t *testing.T) { + customDB := &CustomDB{conn: openCustomApplyChangesI18nDB(t, "", nil), driver: "mysql"} + + err := customDB.ApplyChanges("orders", connection.ChangeSet{ + Updates: []connection.UpdateRow{{ + Values: map[string]interface{}{ + "name": "Alice", + }, + }}, + }) + if err == nil { + t.Fatal("expected update-key-condition error") + } + if err.Error() != "Update operation requires key conditions" { + t.Fatalf("expected English update-key-condition error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawUpdateKeyConditionsRequiredText) { + t.Fatalf("expected no raw update-key-condition text, got %q", err.Error()) + } + }) + + t.Run("update failure", func(t *testing.T) { + rawErr := errors.New("driver raw update failure") + customDB := &CustomDB{conn: openCustomApplyChangesI18nDB(t, "UPDATE", rawErr), driver: "mysql"} + + err := customDB.ApplyChanges("orders", connection.ChangeSet{ + Updates: []connection.UpdateRow{{ + Keys: map[string]interface{}{ + "id": int64(42), + }, + Values: map[string]interface{}{ + "name": "Alice", + }, + }}, + }) + if err == nil { + t.Fatal("expected update failure") + } + if err.Error() != "Update failed: driver raw update failure" { + t.Fatalf("expected English update failure, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawUpdateFailedText) { + t.Fatalf("expected no raw update wrapper, got %q", err.Error()) + } + }) +} + +func TestCustomDBApplyChangesErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("custom_impl.go") + if err != nil { + t.Fatalf("read custom_impl.go: %v", err) + } + source := string(sourceBytes) + functionSource := databaseFunctionSource(t, source, "func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) error") + + rawConnectionNotOpenText := string([]rune{0x8fde, 0x63a5, 0x672a, 0x6253, 0x5f00}) + rawDeleteFailedText := string([]rune{0x5220, 0x9664, 0x5931, 0x8d25}) + rawUpdateKeyConditionsRequiredText := string([]rune{0x66f4, 0x65b0, 0x64cd, 0x4f5c, 0x9700, 0x8981, 0x4e3b, 0x952e, 0x6761, 0x4ef6}) + rawUpdateFailedText := string([]rune{0x66f4, 0x65b0, 0x5931, 0x8d25}) + + for _, rawMessage := range []string{ + `fmt.Errorf("` + rawConnectionNotOpenText + `")`, + `fmt.Errorf("` + rawDeleteFailedText + `:%v", err)`, + `fmt.Errorf("` + rawUpdateKeyConditionsRequiredText + `")`, + `fmt.Errorf("` + rawUpdateFailedText + `:%v", err)`, + } { + if strings.Contains(functionSource, rawMessage) { + t.Fatalf("CustomDB ApplyChanges still contains raw user-visible text %q", rawMessage) + } + } + + for _, key := range customDBApplyChangesI18nKeys() { + if !strings.Contains(functionSource, key) { + t.Fatalf("CustomDB ApplyChanges does not reference i18n key %q", key) + } + } +} + +func TestCustomDBApplyChangesCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range customDBApplyChangesI18nKeys() { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing CustomDB ApplyChanges key %q", language, key) + } + } + } +} + +func customDBApplyChangesI18nKeys() []string { + return []string{ + "db.backend.error.connection_not_open", + "db.backend.error.row_delete_failed", + "db.backend.error.row_update_key_conditions_required", + "db.backend.error.row_update_failed", + } +} + +func TestCustomDBBasicExecutionConnectionNotOpenUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + rawConnectionNotOpenText := string([]rune{0x8fde, 0x63a5, 0x672a, 0x6253, 0x5f00}) + customDB := &CustomDB{} + + cases := []struct { + name string + run func() error + }{ + { + name: "Ping", + run: customDB.Ping, + }, + { + name: "Query", + run: func() error { + _, _, err := customDB.Query("SELECT 1") + return err + }, + }, + { + name: "QueryContext", + run: func() error { + _, _, err := customDB.QueryContext(context.Background(), "SELECT 1") + return err + }, + }, + { + name: "Exec", + run: func() error { + _, err := customDB.Exec("UPDATE demo SET name = 'raw'") + return err + }, + }, + { + name: "ExecContext", + run: func() error { + _, err := customDB.ExecContext(context.Background(), "UPDATE demo SET name = 'raw'") + return err + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.run() + if err == nil { + t.Fatal("expected connection-not-open error") + } + if err.Error() != "Connection is not open" { + t.Fatalf("expected English connection-not-open error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawConnectionNotOpenText) { + t.Fatalf("expected no raw connection-not-open text, got %q", err.Error()) + } + }) + } +} + +func TestCustomDBBasicExecutionConnectionNotOpenSourcesUseI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("custom_impl.go") + if err != nil { + t.Fatalf("read custom_impl.go: %v", err) + } + source := string(sourceBytes) + rawConnectionNotOpenText := string([]rune{0x8fde, 0x63a5, 0x672a, 0x6253, 0x5f00}) + + for _, signature := range []string{ + "func (c *CustomDB) Ping() error", + "func (c *CustomDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error)", + "func (c *CustomDB) Query(query string) ([]map[string]interface{}, []string, error)", + "func (c *CustomDB) ExecContext(ctx context.Context, query string) (int64, error)", + "func (c *CustomDB) Exec(query string) (int64, error)", + } { + functionSource := databaseFunctionSource(t, source, signature) + if strings.Contains(functionSource, `fmt.Errorf("`+rawConnectionNotOpenText+`")`) { + t.Fatalf("%s still contains raw connection-not-open text", signature) + } + if !strings.Contains(functionSource, "db.backend.error.connection_not_open") { + t.Fatalf("%s does not reference connection-not-open i18n key", signature) + } + } +} + func TestCustomDBConnectReportsUnsupportedODBCDriverName(t *testing.T) { db := &CustomDB{} diff --git a/internal/db/dameng_i18n_test.go b/internal/db/dameng_i18n_test.go new file mode 100644 index 0000000..b63f6f4 --- /dev/null +++ b/internal/db/dameng_i18n_test.go @@ -0,0 +1,116 @@ +//go:build gonavi_full_drivers || gonavi_dameng_driver + +package db + +import ( + "database/sql" + "database/sql/driver" + "io" + "os" + "strings" + "sync" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +type damengI18nEmptyRowsDriver struct{} + +type damengI18nEmptyRowsConn struct{} + +type damengI18nEmptyRowsStmt struct{} + +type damengI18nEmptyRowsRows struct{} + +var registerDamengI18nEmptyRowsDriverOnce sync.Once + +var rawDamengCreateStatementNotFoundText = string([]rune{0x672a, 0x627e, 0x5230, 0x5efa, 0x8868, 0x8bed, 0x53e5}) + +func (damengI18nEmptyRowsDriver) Open(name string) (driver.Conn, error) { + return damengI18nEmptyRowsConn{}, nil +} + +func (damengI18nEmptyRowsConn) Prepare(query string) (driver.Stmt, error) { + return damengI18nEmptyRowsStmt{}, nil +} + +func (damengI18nEmptyRowsConn) Close() error { return nil } + +func (damengI18nEmptyRowsConn) Begin() (driver.Tx, error) { + return nil, localizedDatabaseRuntimeError("db.backend.error.transaction_not_open", nil) +} + +func (damengI18nEmptyRowsStmt) Close() error { return nil } + +func (damengI18nEmptyRowsStmt) NumInput() int { return -1 } + +func (damengI18nEmptyRowsStmt) Exec(args []driver.Value) (driver.Result, error) { + return driver.RowsAffected(0), nil +} + +func (damengI18nEmptyRowsStmt) Query(args []driver.Value) (driver.Rows, error) { + return damengI18nEmptyRowsRows{}, nil +} + +func (damengI18nEmptyRowsRows) Columns() []string { + return []string{"DDL"} +} + +func (damengI18nEmptyRowsRows) Close() error { return nil } + +func (damengI18nEmptyRowsRows) Next(dest []driver.Value) error { + return io.EOF +} + +func openDamengI18nEmptyRowsDB(t *testing.T) *sql.DB { + t.Helper() + + registerDamengI18nEmptyRowsDriverOnce.Do(func() { + sql.Register("dameng_i18n_empty_rows", damengI18nEmptyRowsDriver{}) + }) + + conn, err := sql.Open("dameng_i18n_empty_rows", "") + if err != nil { + t.Fatalf("open dameng_i18n_empty_rows test DB failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + return conn +} + +func TestDamengCreateStatementNotFoundUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + damengDB := &DamengDB{conn: openDamengI18nEmptyRowsDB(t)} + + _, err := damengDB.GetCreateStatement("app", "orders") + if err == nil { + t.Fatal("expected Dameng GetCreateStatement to fail") + } + if err.Error() != "The CREATE TABLE statement was not found" { + t.Fatalf("expected English create-statement error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawDamengCreateStatementNotFoundText) { + t.Fatalf("expected no raw Chinese create-statement text, got %q", err.Error()) + } +} + +func TestDamengCreateStatementSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("dameng_impl.go") + if err != nil { + t.Fatalf("read dameng_impl.go: %v", err) + } + source := string(sourceBytes) + + rawMessage := `fmt.Errorf("` + rawDamengCreateStatementNotFoundText + `")` + if strings.Contains(source, rawMessage) { + t.Fatalf("dameng_impl.go still contains raw create-statement text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.create_table_statement_not_found") { + t.Fatal("dameng_impl.go does not reference db.backend.error.create_table_statement_not_found") + } +} diff --git a/internal/db/dameng_impl.go b/internal/db/dameng_impl.go index d9ce83b..c9eada2 100644 --- a/internal/db/dameng_impl.go +++ b/internal/db/dameng_impl.go @@ -263,7 +263,7 @@ func (d *DamengDB) GetCreateStatement(dbName, tableName string) (string, error) return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("未找到建表语句") + return "", localizedDatabaseRuntimeError("db.backend.error.create_table_statement_not_found", nil) } func (d *DamengDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { diff --git a/internal/db/database.go b/internal/db/database.go index a0dd411..3831f65 100644 --- a/internal/db/database.go +++ b/internal/db/database.go @@ -147,9 +147,27 @@ func NewSQLConnStatementExecerWithDialect(conn *sql.Conn, scanDialect string) St return &sqlConnStatementExecer{conn: conn, scanDialect: scanDialect} } +func localizedDatabaseRuntimeError(key string, params map[string]any) error { + return fmt.Errorf("%s", localizedDriverRuntimeText(key, params)) +} + +func wrapDatabaseConnectionOpenError(err error) error { + if err == nil { + return nil + } + return fmt.Errorf("%s%w", localizedDriverRuntimeText("db.backend.error.connection_open_failed_prefix", nil), err) +} + +func wrapDatabaseConnectionVerifyError(err error) error { + if err == nil { + return nil + } + return fmt.Errorf("%s%w", localizedDriverRuntimeText("db.backend.error.connection_verify_failed_prefix", nil), err) +} + func (e *sqlConnStatementExecer) ExecContext(ctx context.Context, query string) (int64, error) { if e == nil || e.conn == nil { - return 0, fmt.Errorf("连接未打开") + return 0, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } res, err := e.conn.ExecContext(ctx, query) if err != nil { @@ -164,7 +182,7 @@ func (e *sqlConnStatementExecer) Exec(query string) (int64, error) { func (e *sqlConnStatementExecer) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if e == nil || e.conn == nil { - return nil, nil, fmt.Errorf("连接未打开") + return nil, nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } rows, err := e.conn.QueryContext(ctx, query) if err != nil { @@ -180,7 +198,7 @@ func (e *sqlConnStatementExecer) Query(query string) ([]map[string]interface{}, func (e *sqlConnStatementExecer) QueryMultiContext(ctx context.Context, query string) ([]connection.ResultSetData, error) { if e == nil || e.conn == nil { - return nil, fmt.Errorf("连接未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } rows, err := e.conn.QueryContext(ctx, query) if err != nil { @@ -229,15 +247,15 @@ func NewSQLConnTransactionExecerWithDialect(conn *sql.Conn, commitSQL string, ro func (e *sqlConnTransactionExecer) activeConn() (*sql.Conn, error) { if e == nil { - return nil, fmt.Errorf("连接未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } e.mu.Lock() defer e.mu.Unlock() if e.conn == nil { - return nil, fmt.Errorf("连接未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } if e.done { - return nil, fmt.Errorf("事务已结束") + return nil, localizedDatabaseRuntimeError("db.backend.error.transaction_already_finished", nil) } return e.conn, nil } @@ -358,12 +376,12 @@ func NewSQLTxStatementExecer(tx *sql.Tx) TransactionExecer { func (e *sqlTxStatementExecer) activeTx() (*sql.Tx, error) { if e == nil || e.tx == nil { - return nil, fmt.Errorf("事务未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.transaction_not_open", nil) } e.mu.Lock() defer e.mu.Unlock() if e.done { - return nil, fmt.Errorf("事务已结束") + return nil, localizedDatabaseRuntimeError("db.backend.error.transaction_already_finished", nil) } return e.tx, nil } @@ -462,16 +480,43 @@ type ChangePreviewer interface { PreviewChanges(tableName string, changes connection.ChangeSet) (deletes, updates, inserts []string) } -func requireSingleRowAffected(result sql.Result, action string) error { +type rowMutationAction string + +const ( + rowMutationActionDelete rowMutationAction = "delete" + rowMutationActionUpdate rowMutationAction = "update" +) + +func localizedRowMutationAction(action rowMutationAction) string { + switch action { + case rowMutationActionDelete: + return localizedDriverRuntimeText("db.backend.action.delete", nil) + case rowMutationActionUpdate: + return localizedDriverRuntimeText("db.backend.action.update", nil) + default: + return strings.TrimSpace(string(action)) + } +} + +func requireSingleRowAffected(result sql.Result, action rowMutationAction) error { + actionLabel := localizedRowMutationAction(action) affected, err := result.RowsAffected() if err != nil { - return fmt.Errorf("%s未生效:无法确认影响行数:%v", action, err) + return fmt.Errorf("%s", localizedDriverRuntimeText("db.backend.error.row_action_not_effective_rows_affected_unknown", map[string]any{ + "action": actionLabel, + "detail": err.Error(), + })) } if affected == 0 { - return fmt.Errorf("%s未生效:未匹配到任何行", action) + return fmt.Errorf("%s", localizedDriverRuntimeText("db.backend.error.row_action_not_effective_no_rows_matched", map[string]any{ + "action": actionLabel, + })) } if affected != 1 { - return fmt.Errorf("%s未生效:影响了 %d 行,期望只影响 1 行", action, affected) + return fmt.Errorf("%s", localizedDriverRuntimeText("db.backend.error.row_action_not_effective_multiple_rows", map[string]any{ + "action": actionLabel, + "count": affected, + })) } return nil } @@ -575,7 +620,7 @@ func NewDatabase(dbType string) (Database, error) { } factory, ok := databaseFactories[normalized] if !ok { - return nil, fmt.Errorf("不支持的数据库类型:%s", dbType) + return nil, localizedDatabaseRuntimeError("db.backend.error.unsupported_database_type", map[string]any{"dbType": dbType}) } return factory(), nil } diff --git a/internal/db/database_i18n_test.go b/internal/db/database_i18n_test.go new file mode 100644 index 0000000..041e328 --- /dev/null +++ b/internal/db/database_i18n_test.go @@ -0,0 +1,677 @@ +package db + +import ( + "context" + "database/sql" + "errors" + "os" + "strings" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +type fakeRowsAffectedResult struct { + affected int64 + err error +} + +const ( + rawTransactionAlreadyFinishedText = "\u4e8b\u52a1\u5df2\u7ed3\u675f" + rawTransactionNotOpenText = "\u4e8b\u52a1\u672a\u6253\u5f00" +) + +func (r fakeRowsAffectedResult) LastInsertId() (int64, error) { + return 0, nil +} + +func (r fakeRowsAffectedResult) RowsAffected() (int64, error) { + if r.err != nil { + return 0, r.err + } + return r.affected, nil +} + +func databaseFunctionSource(t *testing.T, source string, signature string) string { + t.Helper() + start := strings.Index(source, signature) + if start < 0 { + t.Fatalf("database.go missing function signature %q", signature) + } + rest := source[start+len(signature):] + end := strings.Index(rest, "\nfunc ") + if end < 0 { + return source[start:] + } + return source[start : start+len(signature)+end] +} + +func TestRequireSingleRowAffectedUsesLocalizedText(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + cases := []struct { + name string + result fakeRowsAffectedResult + action rowMutationAction + want string + }{ + { + name: "delete rows affected unavailable", + result: fakeRowsAffectedResult{err: errors.New("rows affected unsupported")}, + action: rowMutationActionDelete, + want: "Delete did not take effect: could not determine affected rows: rows affected unsupported", + }, + { + name: "delete no rows matched", + result: fakeRowsAffectedResult{affected: 0}, + action: rowMutationActionDelete, + want: "Delete did not take effect: no rows matched", + }, + { + name: "update multiple rows affected", + result: fakeRowsAffectedResult{affected: 2}, + action: rowMutationActionUpdate, + want: "Update did not take effect: affected 2 rows; expected exactly 1", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := requireSingleRowAffected(tc.result, tc.action) + if err == nil { + t.Fatal("expected row affected validation error") + } + if err.Error() != tc.want { + t.Fatalf("expected %q, got %q", tc.want, err.Error()) + } + }) + } +} + +func TestRequireSingleRowAffectedSourceUsesI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("database.go") + if err != nil { + t.Fatalf("read database.go: %v", err) + } + source := string(sourceBytes) + functionSource := databaseFunctionSource(t, source, "func requireSingleRowAffected(result sql.Result, action rowMutationAction) error") + actionSource := databaseFunctionSource(t, source, "func localizedRowMutationAction(action rowMutationAction) string") + + for _, rawMessage := range []string{ + `fmt.Errorf("%s未生效:无法确认影响行数:%v", action, err)`, + `fmt.Errorf("%s未生效:未匹配到任何行", action)`, + `fmt.Errorf("%s未生效:影响了 %d 行,期望只影响 1 行", action, affected)`, + } { + if strings.Contains(functionSource, rawMessage) { + t.Fatalf("requireSingleRowAffected still contains raw row affected text %q", rawMessage) + } + } + + for _, key := range []string{ + "db.backend.error.row_action_not_effective_rows_affected_unknown", + "db.backend.error.row_action_not_effective_no_rows_matched", + "db.backend.error.row_action_not_effective_multiple_rows", + } { + if !strings.Contains(functionSource, key) { + t.Fatalf("requireSingleRowAffected does not reference i18n key %q", key) + } + } + for _, key := range []string{ + "db.backend.action.delete", + "db.backend.action.update", + } { + if !strings.Contains(actionSource, key) { + t.Fatalf("localizedRowMutationAction does not reference i18n key %q", key) + } + } +} + +func TestDatabaseRowAffectedCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + keys := []string{ + "db.backend.action.delete", + "db.backend.action.update", + "db.backend.error.row_action_not_effective_rows_affected_unknown", + "db.backend.error.row_action_not_effective_no_rows_matched", + "db.backend.error.row_action_not_effective_multiple_rows", + } + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range keys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing database row-affected key %q", language, key) + } + } + } +} + +func TestSQLConnStatementExecerUsesCurrentLanguageForConnectionNotOpen(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + execer := &sqlConnStatementExecer{} + cases := []struct { + name string + call func() error + }{ + { + name: "exec", + call: func() error { + _, err := execer.ExecContext(context.Background(), "SELECT 1") + return err + }, + }, + { + name: "query", + call: func() error { + _, _, err := execer.QueryContext(context.Background(), "SELECT 1") + return err + }, + }, + { + name: "query_multi", + call: func() error { + _, err := execer.QueryMultiContext(context.Background(), "SELECT 1") + return err + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected connection-not-open error") + } + if err.Error() != "Connection is not open" { + t.Fatalf("expected English connection-not-open error, got %q", err.Error()) + } + if strings.Contains(err.Error(), "连接未打开") { + t.Fatalf("expected no Chinese connection-not-open text, got %q", err.Error()) + } + }) + } +} + +func TestSQLConnTransactionExecerUsesCurrentLanguageForConnectionNotOpen(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + execer := &sqlConnTransactionExecer{} + _, err := execer.activeConn() + if err == nil { + t.Fatal("expected connection-not-open error") + } + if err.Error() != "Connection is not open" { + t.Fatalf("expected English connection-not-open error, got %q", err.Error()) + } + if strings.Contains(err.Error(), "连接未打开") { + t.Fatalf("expected no Chinese connection-not-open text, got %q", err.Error()) + } +} + +func TestDatabaseConnectionNotOpenSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("database.go") + if err != nil { + t.Fatalf("read database.go: %v", err) + } + source := string(sourceBytes) + + checks := map[string]string{ + "func (e *sqlConnStatementExecer) ExecContext(ctx context.Context, query string) (int64, error)": "db.backend.error.connection_not_open", + "func (e *sqlConnStatementExecer) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error)": "db.backend.error.connection_not_open", + "func (e *sqlConnStatementExecer) QueryMultiContext(ctx context.Context, query string) ([]connection.ResultSetData, error)": "db.backend.error.connection_not_open", + "func (e *sqlConnTransactionExecer) activeConn() (*sql.Conn, error)": "db.backend.error.connection_not_open", + } + + for signature, key := range checks { + functionSource := databaseFunctionSource(t, source, signature) + if strings.Contains(functionSource, `fmt.Errorf("连接未打开")`) { + t.Fatalf("%s still contains raw connection-not-open text", signature) + } + if !strings.Contains(functionSource, key) { + t.Fatalf("%s does not reference i18n key %q", signature, key) + } + } +} + +func TestDatabaseConnectionNotOpenCatalogKeyExists(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + if strings.TrimSpace(catalog["db.backend.error.connection_not_open"]) == "" { + t.Fatalf("%s catalog missing database connection-not-open key", language) + } + } +} + +func TestWrapDatabaseConnectionErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + baseErr := errors.New("driver unavailable") + + cases := []struct { + name string + call func(error) error + want string + }{ + { + name: "open", + call: wrapDatabaseConnectionOpenError, + want: "Failed to open database connection: driver unavailable", + }, + { + name: "verify", + call: wrapDatabaseConnectionVerifyError, + want: "Failed to verify the established connection: driver unavailable", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.call(baseErr) + if err == nil { + t.Fatal("expected wrapped database connection error") + } + if err.Error() != tc.want { + t.Fatalf("expected %q, got %q", tc.want, err.Error()) + } + if !errors.Is(err, baseErr) { + t.Fatal("expected wrapped error to preserve cause") + } + if strings.Contains(err.Error(), "打开数据库连接失败") || strings.Contains(err.Error(), "连接建立后验证失败") { + t.Fatalf("expected no raw Chinese connection wrapper text, got %q", err.Error()) + } + }) + } +} + +func TestDatabaseConnectionWrapperHelperUsesI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("database.go") + if err != nil { + t.Fatalf("read database.go: %v", err) + } + source := string(sourceBytes) + + checks := map[string]string{ + "func wrapDatabaseConnectionOpenError(err error) error": "db.backend.error.connection_open_failed_prefix", + "func wrapDatabaseConnectionVerifyError(err error) error": "db.backend.error.connection_verify_failed_prefix", + } + + for signature, key := range checks { + functionSource := databaseFunctionSource(t, source, signature) + if strings.Contains(functionSource, `fmt.Errorf("打开数据库连接失败:%w", err)`) || strings.Contains(functionSource, `fmt.Errorf("连接建立后验证失败:%w", err)`) { + t.Fatalf("%s still contains raw database connection wrapper text", signature) + } + if !strings.Contains(functionSource, key) { + t.Fatalf("%s does not reference i18n key %q", signature, key) + } + } +} + +func TestDatabaseConnectionWrapperSourcesUseI18nHelpers(t *testing.T) { + type sourceCheck struct { + path string + requiredTexts []string + } + + checks := []sourceCheck{ + { + path: "custom_impl.go", + requiredTexts: []string{ + "wrapDatabaseConnectionOpenError(err)", + "wrapDatabaseConnectionVerifyError(err)", + }, + }, + { + path: "mariadb_impl.go", + requiredTexts: []string{ + "wrapDatabaseConnectionOpenError(err)", + "wrapDatabaseConnectionVerifyError(err)", + }, + }, + { + path: "sqlite_impl.go", + requiredTexts: []string{ + "wrapDatabaseConnectionOpenError(err)", + "wrapDatabaseConnectionVerifyError(err)", + }, + }, + { + path: "sqlserver_impl.go", + requiredTexts: []string{ + "wrapDatabaseConnectionOpenError(err)", + "wrapDatabaseConnectionVerifyError(err)", + }, + }, + { + path: "iris_impl.go", + requiredTexts: []string{ + "wrapDatabaseConnectionOpenError(err)", + "wrapDatabaseConnectionVerifyError(err)", + }, + }, + } + + for _, check := range checks { + sourceBytes, err := os.ReadFile(check.path) + if err != nil { + t.Fatalf("read %s: %v", check.path, err) + } + source := string(sourceBytes) + if strings.Contains(source, `fmt.Errorf("打开数据库连接失败:%w", err)`) { + t.Fatalf("%s still contains raw open-connection wrapper", check.path) + } + if strings.Contains(source, `fmt.Errorf("连接建立后验证失败:%w", err)`) { + t.Fatalf("%s still contains raw verify-connection wrapper", check.path) + } + for _, required := range check.requiredTexts { + if !strings.Contains(source, required) { + t.Fatalf("%s does not reference i18n helper %q", check.path, required) + } + } + } +} + +func TestDatabaseConnectionWrapperCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + keys := []string{ + "db.backend.error.connection_open_failed_prefix", + "db.backend.error.connection_verify_failed_prefix", + } + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range keys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing database connection wrapper key %q", language, key) + } + } + } +} + +func TestFormatCustomDriverOpenErrorUsesCurrentLanguageForUnknownDrivers(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + cases := []struct { + name string + driver string + base error + want string + }{ + { + name: "system odbc driver", + driver: "InterSystems IRIS ODBC35", + base: errors.New(`sql: unknown driver "InterSystems IRIS ODBC35" (forgotten import?)`), + want: `Failed to open database connection: custom connections do not support entering the system ODBC/JDBC driver name "InterSystems IRIS ODBC35" directly. Enter a Go database/sql driver name already registered by GoNavi. The current build does not register a generic ODBC driver, so connecting to InterSystems IRIS through "InterSystems IRIS ODBC35" is not supported yet: sql: unknown driver "InterSystems IRIS ODBC35" (forgotten import?)`, + }, + { + name: "unregistered go driver", + driver: "not-a-registered-go-driver", + base: errors.New(`sql: unknown driver "not-a-registered-go-driver" (forgotten import?)`), + want: `Failed to open database connection: the custom connection driver "not-a-registered-go-driver" is not registered in GoNavi. Enter a registered Go database/sql driver name instead of a system ODBC/JDBC driver name: sql: unknown driver "not-a-registered-go-driver" (forgotten import?)`, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := formatCustomDriverOpenError(tc.driver, tc.base) + if err == nil { + t.Fatal("expected wrapped custom driver open error") + } + if err.Error() != tc.want { + t.Fatalf("expected %q, got %q", tc.want, err.Error()) + } + if !errors.Is(err, tc.base) { + t.Fatal("expected wrapped custom driver error to preserve cause") + } + if strings.Contains(err.Error(), "自定义连接") || strings.Contains(err.Error(), "未注册通用 ODBC 驱动") { + t.Fatalf("expected no raw Chinese custom driver guidance, got %q", err.Error()) + } + }) + } +} + +func TestFormatCustomDriverOpenErrorSourceUsesI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("custom_impl.go") + if err != nil { + t.Fatalf("read custom_impl.go: %v", err) + } + source := string(sourceBytes) + functionSource := databaseFunctionSource(t, source, "func formatCustomDriverOpenError(driver string, err error) error") + + for _, rawMessage := range []string{ + `fmt.Errorf("打开数据库连接失败:自定义连接不支持直接填写系统 ODBC/JDBC 驱动名 %q;请填写 GoNavi 已注册的 Go database/sql 驱动名。当前版本未注册通用 ODBC 驱动,因此暂不支持通过 %q 连接 InterSystems IRIS:%w", driver, driver, err)`, + `fmt.Errorf("打开数据库连接失败:自定义连接驱动 %q 未在 GoNavi 中注册;请填写已注册的 Go database/sql 驱动名,不能填写系统 ODBC/JDBC 驱动名:%w", driver, err)`, + } { + if strings.Contains(functionSource, rawMessage) { + t.Fatalf("formatCustomDriverOpenError still contains raw custom driver guidance %q", rawMessage) + } + } + + for _, key := range []string{ + "db.backend.error.custom_driver_system_odbc_unsupported_prefix", + "db.backend.error.custom_driver_unregistered_prefix", + } { + if !strings.Contains(functionSource, key) { + t.Fatalf("formatCustomDriverOpenError does not reference i18n key %q", key) + } + } +} + +func TestCustomDriverOpenErrorCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + keys := []string{ + "db.backend.error.custom_driver_system_odbc_unsupported_prefix", + "db.backend.error.custom_driver_unregistered_prefix", + } + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range keys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing custom driver open error key %q", language, key) + } + } + } +} + +func TestNewDatabaseUnsupportedTypeUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + dbType := "mystery-driver" + _, err := NewDatabase(dbType) + if err == nil { + t.Fatal("expected unsupported database type error") + } + + want := "Unsupported database type: mystery-driver" + if err.Error() != want { + t.Fatalf("expected localized unsupported database type error %q, got %q", want, err.Error()) + } + rawUnsupportedDatabaseTypeText := "\u4e0d\u652f\u6301\u7684\u6570\u636e\u5e93\u7c7b\u578b" + if strings.Contains(err.Error(), rawUnsupportedDatabaseTypeText) { + t.Fatalf("expected no Chinese unsupported database type text, got %q", err.Error()) + } +} + +func TestNewDatabaseUnsupportedTypeSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("database.go") + if err != nil { + t.Fatalf("read database.go: %v", err) + } + source := string(sourceBytes) + functionSource := databaseFunctionSource(t, source, "func NewDatabase(dbType string) (Database, error)") + + rawUnsupportedDatabaseTypeText := "\u4e0d\u652f\u6301\u7684\u6570\u636e\u5e93\u7c7b\u578b" + rawUnsupportedDatabaseTypeSnippet := `fmt.Errorf("` + rawUnsupportedDatabaseTypeText + `:%s", dbType)` + if strings.Contains(functionSource, rawUnsupportedDatabaseTypeSnippet) { + t.Fatal("NewDatabase still contains raw unsupported database type text") + } + if !strings.Contains(functionSource, "db.backend.error.unsupported_database_type") { + t.Fatal("NewDatabase does not reference db.backend.error.unsupported_database_type") + } +} + +func TestNewDatabaseUnsupportedTypeCatalogKeyExists(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + if strings.TrimSpace(catalog["db.backend.error.unsupported_database_type"]) == "" { + t.Fatalf("%s catalog missing unsupported database type key", language) + } + } +} + +func TestTransactionExecerStateErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + cases := []struct { + name string + call func() error + want string + unexpected string + }{ + { + name: "sql conn transaction already finished", + call: func() error { + _, err := (&sqlConnTransactionExecer{ + conn: new(sql.Conn), + done: true, + }).activeConn() + return err + }, + want: "Transaction has already finished", + unexpected: rawTransactionAlreadyFinishedText, + }, + { + name: "sql tx transaction not open", + call: func() error { + _, err := (&sqlTxStatementExecer{}).activeTx() + return err + }, + want: "Transaction is not open", + unexpected: rawTransactionNotOpenText, + }, + { + name: "sql tx transaction already finished", + call: func() error { + _, err := (&sqlTxStatementExecer{ + tx: new(sql.Tx), + done: true, + }).activeTx() + return err + }, + want: "Transaction has already finished", + unexpected: rawTransactionAlreadyFinishedText, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected localized transaction state error") + } + if err.Error() != tc.want { + t.Fatalf("expected %q, got %q", tc.want, err.Error()) + } + if strings.Contains(err.Error(), tc.unexpected) { + t.Fatalf("expected no raw Chinese transaction state text, got %q", err.Error()) + } + }) + } +} + +func TestDatabaseTransactionStateSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("database.go") + if err != nil { + t.Fatalf("read database.go: %v", err) + } + source := string(sourceBytes) + + checks := map[string][]string{ + "func (e *sqlConnTransactionExecer) activeConn() (*sql.Conn, error)": { + "db.backend.error.connection_not_open", + "db.backend.error.transaction_already_finished", + }, + "func (e *sqlTxStatementExecer) activeTx() (*sql.Tx, error)": { + "db.backend.error.transaction_not_open", + "db.backend.error.transaction_already_finished", + }, + } + + for signature, keys := range checks { + functionSource := databaseFunctionSource(t, source, signature) + for _, rawMessage := range []string{ + `fmt.Errorf("` + rawTransactionNotOpenText + `")`, + `fmt.Errorf("` + rawTransactionAlreadyFinishedText + `")`, + } { + if strings.Contains(functionSource, rawMessage) { + t.Fatalf("%s still contains raw transaction state text %q", signature, rawMessage) + } + } + for _, key := range keys { + if !strings.Contains(functionSource, key) { + t.Fatalf("%s does not reference i18n key %q", signature, key) + } + } + } +} + +func TestDatabaseTransactionStateCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + keys := []string{ + "db.backend.error.transaction_not_open", + "db.backend.error.transaction_already_finished", + } + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range keys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing database transaction state key %q", language, key) + } + } + } +} diff --git a/internal/db/driver_agent_binary_check.go b/internal/db/driver_agent_binary_check.go index 2184bb7..4df54af 100644 --- a/internal/db/driver_agent_binary_check.go +++ b/internal/db/driver_agent_binary_check.go @@ -20,6 +20,18 @@ const ( peCOFFHeaderSize = 20 ) +type driverAgentArchMismatchError struct { + fileLabel string + processLabel string +} + +func (e *driverAgentArchMismatchError) Error() string { + if e == nil { + return "driver agent architecture is incompatible" + } + return fmt.Sprintf("driver agent architecture is incompatible (file=%s, current process=%s)", e.fileLabel, e.processLabel) +} + func windowsMachineLabel(machine uint16) string { switch machine { case peMachineI386: @@ -61,7 +73,10 @@ func validateWindowsExecutableMachineForArch(pathText string, goarch string) err return nil } if machine != expectedMachine { - return fmt.Errorf("可执行文件架构不兼容(文件=%s,当前进程=%s)", windowsMachineLabel(machine), expectedLabel) + return &driverAgentArchMismatchError{ + fileLabel: windowsMachineLabel(machine), + processLabel: expectedLabel, + } } return nil } diff --git a/internal/db/driver_i18n.go b/internal/db/driver_i18n.go new file mode 100644 index 0000000..24e97f4 --- /dev/null +++ b/internal/db/driver_i18n.go @@ -0,0 +1,56 @@ +package db + +import ( + "sync" + + "GoNavi-Wails/shared/i18n" +) + +var ( + driverRuntimeTextMu sync.RWMutex + driverRuntimeTextLanguage = i18n.LanguageZhCN + driverRuntimeTextLocalizer *i18n.Localizer +) + +func SetBackendLanguage(language i18n.Language) { + normalized, ok := i18n.NormalizeLanguage(string(language)) + if !ok { + return + } + + driverRuntimeTextMu.Lock() + defer driverRuntimeTextMu.Unlock() + + driverRuntimeTextLanguage = normalized + if driverRuntimeTextLocalizer == nil { + localizer, err := i18n.NewLocalizer(normalized) + if err != nil { + return + } + driverRuntimeTextLocalizer = localizer + return + } + driverRuntimeTextLocalizer.SetLanguage(normalized) +} + +func localizedDriverRuntimeText(key string, params map[string]any) string { + driverRuntimeTextMu.RLock() + if driverRuntimeTextLocalizer != nil { + text := driverRuntimeTextLocalizer.T(key, params) + driverRuntimeTextMu.RUnlock() + return text + } + driverRuntimeTextMu.RUnlock() + + driverRuntimeTextMu.Lock() + defer driverRuntimeTextMu.Unlock() + + if driverRuntimeTextLocalizer == nil { + localizer, err := i18n.NewLocalizer(driverRuntimeTextLanguage) + if err != nil { + return key + } + driverRuntimeTextLocalizer = localizer + } + return driverRuntimeTextLocalizer.T(key, params) +} diff --git a/internal/db/driver_support.go b/internal/db/driver_support.go index 664fd19..e7aaf0d 100644 --- a/internal/db/driver_support.go +++ b/internal/db/driver_support.go @@ -1,6 +1,7 @@ package db import ( + "errors" "fmt" "os" "path/filepath" @@ -208,7 +209,9 @@ func resolveExternalDriverRoot(downloadDir string) (string, error) { root = abs } if err := os.MkdirAll(root, 0o755); err != nil { - return "", fmt.Errorf("创建驱动目录失败:%w", err) + return "", fmt.Errorf("%s%w", localizedDriverRuntimeText("driver_manager.backend.error.create_directory_failed", map[string]any{ + "detail": "", + }), err) } return root, nil } @@ -265,16 +268,28 @@ func optionalGoDriverRuntimeReady(driverType string) (bool, string) { if !IsOptionalGoDriver(normalized) { return true, "" } + displayName := driverDisplayName(normalized) executablePath, err := ResolveOptionalDriverAgentExecutablePath("", normalized) if err != nil { - return false, fmt.Sprintf("%s 驱动代理路径解析失败,请在驱动管理中重新安装启用", driverDisplayName(normalized)) + return false, localizedDriverRuntimeText("driver_manager.backend.status.agent_path_failed", map[string]any{"name": displayName}) } info, statErr := os.Stat(executablePath) if statErr != nil || info.IsDir() { - return false, fmt.Sprintf("%s 驱动代理缺失,请在驱动管理中重新安装启用", driverDisplayName(normalized)) + return false, localizedDriverRuntimeText("driver_manager.backend.status.agent_missing", map[string]any{"name": displayName}) } if validateErr := ValidateOptionalDriverAgentExecutable(normalized, executablePath); validateErr != nil { - return false, fmt.Sprintf("%s;请在驱动管理中重新安装启用", validateErr.Error()) + var archErr *driverAgentArchMismatchError + if errors.As(validateErr, &archErr) { + return false, localizedDriverRuntimeText("driver_manager.backend.status.agent_arch_incompatible_detail", map[string]any{ + "name": displayName, + "file": archErr.fileLabel, + "process": archErr.processLabel, + }) + } + return false, localizedDriverRuntimeText("driver_manager.backend.status.agent_unavailable_reinstall", map[string]any{ + "name": displayName, + "detail": validateErr.Error(), + }) } return true, "" } @@ -283,7 +298,7 @@ func optionalGoDriverRuntimeReady(driverType string) (bool, string) { func DriverRuntimeSupportStatus(driverType string) (bool, string) { normalized := normalizeRuntimeDriverType(driverType) if normalized == "" { - return false, "未识别的数据源类型" + return false, localizedDriverRuntimeText("driver_manager.backend.status.unrecognized_driver_type", nil) } if normalized == "custom" { return true, "" @@ -292,8 +307,9 @@ func DriverRuntimeSupportStatus(driverType string) (bool, string) { return true, "" } if IsOptionalGoDriver(normalized) { + displayName := driverDisplayName(normalized) if !IsOptionalGoDriverBuildIncluded(normalized) { - return false, fmt.Sprintf("%s 当前发行包为精简构建,未内置该驱动;如需使用请安装 Full 版", driverDisplayName(normalized)) + return false, localizedDriverRuntimeText("driver_manager.backend.status.slim_build_required", map[string]any{"name": displayName}) } if optionalGoDriverInstalled(normalized) { if ready, reason := optionalGoDriverRuntimeReady(normalized); !ready { @@ -301,7 +317,7 @@ func DriverRuntimeSupportStatus(driverType string) (bool, string) { } return true, "" } - return false, fmt.Sprintf("%s 纯 Go 驱动未启用,请先在驱动管理中点击“安装启用”", driverDisplayName(normalized)) + return false, localizedDriverRuntimeText("driver_manager.backend.status.optional_disabled", map[string]any{"name": displayName}) } return true, "" } diff --git a/internal/db/driver_support_test.go b/internal/db/driver_support_test.go index ff32e4b..5687ea3 100644 --- a/internal/db/driver_support_test.go +++ b/internal/db/driver_support_test.go @@ -4,9 +4,26 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" + + "GoNavi-Wails/shared/i18n" ) +func driverSupportFunctionSource(t *testing.T, source string, signature string) string { + t.Helper() + start := strings.Index(source, signature) + if start < 0 { + t.Fatalf("driver_support.go missing function signature %q", signature) + } + rest := source[start+len(signature):] + end := strings.Index(rest, "\nfunc ") + if end < 0 { + return source[start:] + } + return source[start : start+len(signature)+end] +} + func TestPostgresRuntimeSupportRequiresInstallMarker(t *testing.T) { tmpDir := t.TempDir() SetExternalDriverDownloadDirectory(tmpDir) @@ -166,3 +183,117 @@ func TestGoldenDBBuiltinDatabaseFactoryUsesMySQLImplementation(t *testing.T) { t.Fatalf("expected goldendb to reuse MySQLDB implementation, got %T", dbInst) } } + +func TestDriverRuntimeSupportStatusUsesCurrentLanguageForUnrecognizedDriverType(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + supported, reason := DriverRuntimeSupportStatus(" ") + if supported { + t.Fatal("expected blank driver type to be unsupported") + } + if reason != "Unrecognized data source type" { + t.Fatalf("expected English unrecognized-driver reason, got %q", reason) + } +} + +func TestDriverRuntimeSupportStatusUsesCurrentLanguageForOptionalDriverDisabledState(t *testing.T) { + tmpDir := t.TempDir() + SetExternalDriverDownloadDirectory(tmpDir) + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + supported, reason := DriverRuntimeSupportStatus("mariadb") + if supported { + t.Fatal("expected mariadb to stay unavailable without installation marker") + } + if !IsOptionalGoDriverBuildIncluded("mariadb") { + want := "MariaDB is not included in the current slim build. Install the Full edition to use this driver." + if reason != want { + t.Fatalf("expected English slim-build reason %q, got %q", want, reason) + } + return + } + want := "MariaDB Go driver is not enabled; install and enable it in Driver Manager." + if reason != want { + t.Fatalf("expected English disabled-driver reason %q, got %q", want, reason) + } +} + +func TestDriverRuntimeSupportStatusUsesCurrentLanguageForMissingOptionalDriverAgent(t *testing.T) { + if !IsOptionalGoDriverBuildIncluded("mariadb") { + t.Skip("mariadb is not included in the current slim build") + } + + tmpDir := t.TempDir() + SetExternalDriverDownloadDirectory(tmpDir) + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + markerPath, err := ResolveOptionalGoDriverMarkerPath(tmpDir, "mariadb") + if err != nil { + t.Fatalf("resolve marker path failed: %v", err) + } + if err := os.MkdirAll(filepath.Dir(markerPath), 0o755); err != nil { + t.Fatalf("create marker directory failed: %v", err) + } + if err := os.WriteFile(markerPath, []byte("{}"), 0o644); err != nil { + t.Fatalf("write marker failed: %v", err) + } + + supported, reason := DriverRuntimeSupportStatus("mariadb") + if supported { + t.Fatal("expected mariadb to stay unavailable when the driver agent executable is missing") + } + want := "MariaDB driver agent is missing; reinstall and enable it in Driver Manager." + if reason != want { + t.Fatalf("expected English missing-agent reason %q, got %q", want, reason) + } +} + +func TestResolveExternalDriverRootSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("driver_support.go") + if err != nil { + t.Fatalf("read driver_support.go: %v", err) + } + source := string(sourceBytes) + functionSource := driverSupportFunctionSource(t, source, "func resolveExternalDriverRoot(downloadDir string) (string, error)") + rawCreateDirectoryWrapper := "fmt.Errorf(\"\\u521b\\u5efa\\u9a71\\u52a8\\u76ee\\u5f55\\u5931\\u8d25\\uff1a%w\", err)" + + if strings.Contains(functionSource, rawCreateDirectoryWrapper) { + t.Fatal("resolveExternalDriverRoot still contains raw Chinese create-directory wrapper") + } + if !strings.Contains(functionSource, "driver_manager.backend.error.create_directory_failed") { + t.Fatal("resolveExternalDriverRoot does not reference driver_manager.backend.error.create_directory_failed") + } +} + +func TestResolveExternalDriverRootUsesCurrentLanguageForCreateDirectoryFailure(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + tmpDir := t.TempDir() + blocker := filepath.Join(tmpDir, "driver-root-blocker") + if err := os.WriteFile(blocker, []byte("blocker"), 0o644); err != nil { + t.Fatalf("write blocker file: %v", err) + } + + _, err := ResolveExternalDriverRoot(filepath.Join(blocker, "nested")) + if err == nil { + t.Fatal("expected create-directory failure") + } + if !strings.Contains(err.Error(), "Failed to create driver directory:") { + t.Fatalf("expected English create-directory wrapper, got %q", err.Error()) + } + if strings.Contains(err.Error(), "\u521b\u5efa\u9a71\u52a8\u76ee\u5f55\u5931\u8d25") { + t.Fatalf("expected no Chinese create-directory wrapper in en-US mode, got %q", err.Error()) + } +} diff --git a/internal/db/duckdb_applychanges_test.go b/internal/db/duckdb_applychanges_test.go index 65a8d67..5503494 100644 --- a/internal/db/duckdb_applychanges_test.go +++ b/internal/db/duckdb_applychanges_test.go @@ -7,6 +7,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "strings" "sync" "testing" @@ -26,6 +27,8 @@ type duckdbRecordingState struct { mu sync.Mutex execQueries []string execArgs [][]driver.NamedValue + failDelete error + failUpdate error } func (s *duckdbRecordingState) snapshotExecQueries() []string { @@ -73,6 +76,12 @@ func (c *duckdbRecordingConn) ExecContext(_ context.Context, query string, args defer c.state.mu.Unlock() c.state.execQueries = append(c.state.execQueries, query) c.state.execArgs = append(c.state.execArgs, append([]driver.NamedValue(nil), args...)) + if strings.HasPrefix(query, "DELETE FROM ") && c.state.failDelete != nil { + return nil, c.state.failDelete + } + if strings.HasPrefix(query, "UPDATE ") && c.state.failUpdate != nil { + return nil, c.state.failUpdate + } return driver.RowsAffected(1), nil } @@ -151,10 +160,13 @@ func TestDuckDBApplyChangesUsesUnquotedRowIDLocator(t *testing.T) { if len(args) != 2 || len(args[0]) != 1 || len(args[1]) != 2 { t.Fatalf("执行参数数量不符合预期: %#v", args) } - if args[0][0].Value != 21 { + if got, ok := args[0][0].Value.(int64); !ok || got != 21 { t.Fatalf("删除 rowid 参数错误: %#v", args[0]) } - if args[1][0].Value != "renamed" || args[1][1].Value != 17 { + if args[1][0].Value != "renamed" { + t.Fatalf("更新参数错误: %#v", args[1]) + } + if got, ok := args[1][1].Value.(int64); !ok || got != 17 { t.Fatalf("更新参数错误: %#v", args[1]) } } diff --git a/internal/db/duckdb_i18n_test.go b/internal/db/duckdb_i18n_test.go new file mode 100644 index 0000000..bf96a59 --- /dev/null +++ b/internal/db/duckdb_i18n_test.go @@ -0,0 +1,290 @@ +//go:build gonavi_full_drivers || gonavi_duckdb_driver + +package db + +import ( + "errors" + "database/sql" + "database/sql/driver" + "fmt" + "io" + "os" + "runtime" + "strings" + "sync" + "testing" + + "GoNavi-Wails/internal/connection" + "GoNavi-Wails/shared/i18n" +) + +type duckDBI18nEmptyRowsDriver struct{} + +type duckDBI18nEmptyRowsConn struct{} + +type duckDBI18nEmptyRowsStmt struct{} + +type duckDBI18nEmptyRowsRows struct{} + +var registerDuckDBI18nEmptyRowsDriverOnce sync.Once + +func (duckDBI18nEmptyRowsDriver) Open(name string) (driver.Conn, error) { + return duckDBI18nEmptyRowsConn{}, nil +} + +func (duckDBI18nEmptyRowsConn) Prepare(query string) (driver.Stmt, error) { + return duckDBI18nEmptyRowsStmt{}, nil +} + +func (duckDBI18nEmptyRowsConn) Close() error { return nil } + +func (duckDBI18nEmptyRowsConn) Begin() (driver.Tx, error) { + return nil, fmt.Errorf("transactions are not supported in duckdb i18n empty rows test driver") +} + +func (duckDBI18nEmptyRowsStmt) Close() error { return nil } + +func (duckDBI18nEmptyRowsStmt) NumInput() int { return -1 } + +func (duckDBI18nEmptyRowsStmt) Exec(args []driver.Value) (driver.Result, error) { + return nil, fmt.Errorf("exec is not supported in duckdb i18n empty rows test driver") +} + +func (duckDBI18nEmptyRowsStmt) Query(args []driver.Value) (driver.Rows, error) { + return duckDBI18nEmptyRowsRows{}, nil +} + +func (duckDBI18nEmptyRowsRows) Columns() []string { + return []string{"sql"} +} + +func (duckDBI18nEmptyRowsRows) Close() error { return nil } + +func (duckDBI18nEmptyRowsRows) Next(dest []driver.Value) error { + return io.EOF +} + +func openDuckDBI18nEmptyRowsDB(t *testing.T) *sql.DB { + t.Helper() + + registerDuckDBI18nEmptyRowsDriverOnce.Do(func() { + sql.Register("duckdb_i18n_empty_rows", duckDBI18nEmptyRowsDriver{}) + }) + + conn, err := sql.Open("duckdb_i18n_empty_rows", "") + if err != nil { + t.Fatalf("open duckdb_i18n_empty_rows test DB failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + return conn +} + +func TestDuckDBRuntimeUsesCurrentLanguageForConnectionNotOpen(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + err := (&DuckDB{}).Ping() + if err == nil { + t.Fatal("expected Ping to fail when DuckDB connection is not open") + } + if err.Error() != "Connection is not open" { + t.Fatalf("expected English not-open error, got %q", err.Error()) + } +} + +func TestDuckDBBuildSupportStatusUsesCurrentLanguageWhenDriverIsUnavailable(t *testing.T) { + if supported, _ := duckDBBuildSupportStatus(); supported { + t.Skip("current build already includes DuckDB runtime support") + } + + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + wantReason := fmt.Sprintf( + "The current build does not include the DuckDB driver (platform=%s/%s). Enable CGO and use a supported platform (darwin/linux amd64|arm64, windows/amd64), or provide a custom library via -tags duckdb_use_lib / duckdb_use_static_lib", + runtime.GOOS, + runtime.GOARCH, + ) + + supported, reason := duckDBBuildSupportStatus() + if supported { + t.Fatal("expected DuckDB build support to stay unavailable in this test environment") + } + if reason != wantReason { + t.Fatalf("expected English DuckDB-unavailable reason %q, got %q", wantReason, reason) + } + + err := (&DuckDB{}).Connect(connection.ConnectionConfig{Type: "duckdb"}) + if err == nil { + t.Fatal("expected DuckDB connect to fail when runtime support is unavailable") + } + + wantConnectError := "DuckDB driver is unavailable: " + wantReason + if err.Error() != wantConnectError { + t.Fatalf("expected English DuckDB connect error %q, got %q", wantConnectError, err.Error()) + } +} + +func TestDuckDBDDLMetadataErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + duck := &DuckDB{} + + tests := []struct { + name string + call func() error + want string + }{ + { + name: "create statement table name required", + call: func() error { + _, err := duck.GetCreateStatement("", " ") + return err + }, + want: "Table name is required", + }, + { + name: "create statement not found", + call: func() error { + _, err := (&DuckDB{conn: openDuckDBI18nEmptyRowsDB(t)}).GetCreateStatement("main", "orders") + return err + }, + want: "The CREATE TABLE statement was not found", + }, + { + name: "columns table name required", + call: func() error { + _, err := duck.GetColumns("", " ") + return err + }, + want: "Table name is required", + }, + { + name: "indexes table name required", + call: func() error { + _, err := duck.GetIndexes("", " ") + return err + }, + want: "Table name is required", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected DuckDB DDL metadata call to fail") + } + if err.Error() != tc.want { + t.Fatalf("expected %q, got %q", tc.want, err.Error()) + } + }) + } +} + +func TestDuckDBApplyChangesErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + t.Run("delete failure", func(t *testing.T) { + dbConn, state := openDuckDBRecordingDB(t) + state.failDelete = errors.New("delete blocked") + duckdb := &DuckDB{conn: dbConn} + + err := duckdb.ApplyChanges("main.events", connection.ChangeSet{ + Deletes: []map[string]interface{}{ + {"id": 1}, + }, + }) + if err == nil { + t.Fatal("expected delete failure to bubble up") + } + if err.Error() != "Delete failed: delete blocked" { + t.Fatalf("expected English delete failure, got %q", err.Error()) + } + }) + + t.Run("update key condition required", func(t *testing.T) { + dbConn, _ := openDuckDBRecordingDB(t) + duckdb := &DuckDB{conn: dbConn} + + err := duckdb.ApplyChanges("main.events", connection.ChangeSet{ + Updates: []connection.UpdateRow{{ + Values: map[string]interface{}{ + "name": "renamed", + }, + }}, + }) + if err == nil { + t.Fatal("expected update without keys to fail") + } + if err.Error() != "Update operation requires key conditions" { + t.Fatalf("expected English update-key failure, got %q", err.Error()) + } + }) + + t.Run("update failure", func(t *testing.T) { + dbConn, state := openDuckDBRecordingDB(t) + state.failUpdate = errors.New("update blocked") + duckdb := &DuckDB{conn: dbConn} + + err := duckdb.ApplyChanges("main.events", connection.ChangeSet{ + Updates: []connection.UpdateRow{{ + Keys: map[string]interface{}{ + "id": 1, + }, + Values: map[string]interface{}{ + "name": "renamed", + }, + }}, + }) + if err == nil { + t.Fatal("expected update failure to bubble up") + } + if err.Error() != "Update failed: update blocked" { + t.Fatalf("expected English update failure, got %q", err.Error()) + } + }) +} + +func TestDuckDBUserVisibleRuntimeErrorsDoNotReintroduceInlineChinese(t *testing.T) { + t.Helper() + + files := []string{"duckdb_impl.go", "duckdb_platform_unsupported.go"} + disallowed := []string{ + string([]rune{0x44, 0x75, 0x63, 0x6b, 0x44, 0x42, 0x20, 0x9a71, 0x52a8, 0x4e0d, 0x53ef, 0x7528}), + string([]rune{0x6253, 0x5f00, 0x6570, 0x636e, 0x5e93, 0x8fde, 0x63a5, 0x5931, 0x8d25}), + string([]rune{0x8fde, 0x63a5, 0x5efa, 0x7acb, 0x540e, 0x9a8c, 0x8bc1, 0x5931, 0x8d25}), + string([]rune{0x8fde, 0x63a5, 0x672a, 0x6253, 0x5f00}), + string([]rune{0x5f53, 0x524d, 0x6784, 0x5efa, 0x4e0d, 0x5305, 0x542b, 0x20, 0x44, 0x75, 0x63, 0x6b, 0x44, 0x42, 0x20, 0x9a71, 0x52a8}), + string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}), + string([]rune{0x672a, 0x627e, 0x5230, 0x5efa, 0x8868, 0x8bed, 0x53e5}), + string([]rune{0x5220, 0x9664, 0x5931, 0x8d25}), + string([]rune{0x66f4, 0x65b0, 0x64cd, 0x4f5c, 0x9700, 0x8981, 0x4e3b, 0x952e, 0x6761, 0x4ef6}), + string([]rune{0x66f4, 0x65b0, 0x5931, 0x8d25}), + } + + for _, file := range files { + content, err := os.ReadFile(file) + if err != nil { + t.Fatalf("read %s failed: %v", file, err) + } + source := string(content) + for _, raw := range disallowed { + if strings.Contains(source, raw) { + t.Fatalf("%s still contains inline user-visible Chinese raw: %s", file, raw) + } + } + } +} diff --git a/internal/db/duckdb_impl.go b/internal/db/duckdb_impl.go index 008e8bd..aa727fc 100644 --- a/internal/db/duckdb_impl.go +++ b/internal/db/duckdb_impl.go @@ -5,6 +5,7 @@ package db import ( "context" "database/sql" + "errors" "fmt" "strings" "time" @@ -18,9 +19,41 @@ type DuckDB struct { pingTimeout time.Duration } +func duckDBRuntimeError(key string, params map[string]any) error { + return errors.New(localizedDriverRuntimeText(key, params)) +} + +func duckDBWrapRuntimeError(prefixKey string, err error) error { + return fmt.Errorf("%s%w", localizedDriverRuntimeText(prefixKey, nil), err) +} + +func duckDBConnectionNotOpenError() error { + return duckDBRuntimeError("db.backend.error.connection_not_open", nil) +} + +func duckDBTableNameRequiredError() error { + return duckDBRuntimeError("db.backend.error.table_name_required", nil) +} + +func duckDBCreateTableStatementNotFoundError() error { + return duckDBRuntimeError("db.backend.error.create_table_statement_not_found", nil) +} + +func duckDBDeleteFailedError(err error) error { + return duckDBRuntimeError("db.backend.error.row_delete_failed", map[string]any{"detail": err.Error()}) +} + +func duckDBUpdateKeyConditionsRequiredError() error { + return duckDBRuntimeError("db.backend.error.row_update_key_conditions_required", nil) +} + +func duckDBUpdateFailedError(err error) error { + return duckDBRuntimeError("db.backend.error.row_update_failed", map[string]any{"detail": err.Error()}) +} + func (d *DuckDB) Connect(config connection.ConnectionConfig) error { if supported, reason := duckDBBuildSupportStatus(); !supported { - return fmt.Errorf("DuckDB 驱动不可用:%s", reason) + return duckDBRuntimeError("db.backend.error.duckdb_driver_unavailable", map[string]any{"detail": reason}) } dsn := strings.TrimSpace(config.Host) @@ -33,7 +66,7 @@ func (d *DuckDB) Connect(config connection.ConnectionConfig) error { db, err := sql.Open("duckdb", dsn) if err != nil { - return fmt.Errorf("打开数据库连接失败:%w", err) + return duckDBWrapRuntimeError("db.backend.error.connection_open_failed_prefix", err) } d.conn = db d.pingTimeout = getConnectTimeout(config) @@ -41,7 +74,7 @@ func (d *DuckDB) Connect(config connection.ConnectionConfig) error { if err := d.Ping(); err != nil { _ = db.Close() d.conn = nil - return fmt.Errorf("连接建立后验证失败:%w", err) + return duckDBWrapRuntimeError("db.backend.error.connection_verify_failed_prefix", err) } return nil } @@ -55,7 +88,7 @@ func (d *DuckDB) Close() error { func (d *DuckDB) Ping() error { if d.conn == nil { - return fmt.Errorf("连接未打开") + return duckDBConnectionNotOpenError() } timeout := d.pingTimeout if timeout <= 0 { @@ -68,7 +101,7 @@ func (d *DuckDB) Ping() error { func (d *DuckDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if d.conn == nil { - return nil, nil, fmt.Errorf("连接未打开") + return nil, nil, duckDBConnectionNotOpenError() } rows, err := d.conn.QueryContext(ctx, query) if err != nil { @@ -80,7 +113,7 @@ func (d *DuckDB) QueryContext(ctx context.Context, query string) ([]map[string]i func (d *DuckDB) Query(query string) ([]map[string]interface{}, []string, error) { if d.conn == nil { - return nil, nil, fmt.Errorf("连接未打开") + return nil, nil, duckDBConnectionNotOpenError() } rows, err := d.conn.Query(query) if err != nil { @@ -92,7 +125,7 @@ func (d *DuckDB) Query(query string) ([]map[string]interface{}, []string, error) func (d *DuckDB) ExecBatchContext(ctx context.Context, query string) (int64, error) { if d.conn == nil { - return 0, fmt.Errorf("连接未打开") + return 0, duckDBConnectionNotOpenError() } res, err := d.conn.ExecContext(ctx, query) if err != nil { @@ -103,7 +136,7 @@ func (d *DuckDB) ExecBatchContext(ctx context.Context, query string) (int64, err func (d *DuckDB) OpenSessionExecer(ctx context.Context) (StatementExecer, error) { if d.conn == nil { - return nil, fmt.Errorf("连接未打开") + return nil, duckDBConnectionNotOpenError() } conn, err := d.conn.Conn(ctx) if err != nil { @@ -114,7 +147,7 @@ func (d *DuckDB) OpenSessionExecer(ctx context.Context) (StatementExecer, error) func (d *DuckDB) ExecContext(ctx context.Context, query string) (int64, error) { if d.conn == nil { - return 0, fmt.Errorf("连接未打开") + return 0, duckDBConnectionNotOpenError() } res, err := d.conn.ExecContext(ctx, query) if err != nil { @@ -125,7 +158,7 @@ func (d *DuckDB) ExecContext(ctx context.Context, query string) (int64, error) { func (d *DuckDB) Exec(query string) (int64, error) { if d.conn == nil { - return 0, fmt.Errorf("连接未打开") + return 0, duckDBConnectionNotOpenError() } res, err := d.conn.Exec(query) if err != nil { @@ -210,7 +243,7 @@ ORDER BY table_catalog, table_schema, table_name`, escapeDuckDBLiteral(path.Cata func (d *DuckDB) GetCreateStatement(dbName, tableName string) (string, error) { path := normalizeDuckDBObjectPath(dbName, tableName) if path.Object == "" { - return "", fmt.Errorf("表名不能为空") + return "", duckDBTableNameRequiredError() } escapedTable := escapeDuckDBLiteral(path.Object) @@ -251,13 +284,13 @@ func (d *DuckDB) GetCreateStatement(dbName, tableName string) (string, error) { } } - return "", fmt.Errorf("未找到建表语句") + return "", duckDBCreateTableStatementNotFoundError() } func (d *DuckDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { path := normalizeDuckDBObjectPath(dbName, tableName) if path.Object == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, duckDBTableNameRequiredError() } query := fmt.Sprintf(` @@ -353,7 +386,7 @@ ORDER BY table_catalog, table_schema, table_name, ordinal_position`, escapeDuckD func (d *DuckDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { path := normalizeDuckDBObjectPath(dbName, tableName) if path.Object == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, duckDBTableNameRequiredError() } constraintQuery := buildDuckDBConstraintMetadataQuery(path, true) @@ -395,7 +428,7 @@ func (d *DuckDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDefi func (d *DuckDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if d.conn == nil { - return fmt.Errorf("连接未打开") + return duckDBConnectionNotOpenError() } tx, err := d.conn.Begin() @@ -446,7 +479,7 @@ func (d *DuckDB) ApplyChanges(tableName string, changes connection.ChangeSet) er } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("删除失败:%v", err) + return duckDBDeleteFailedError(err) } } @@ -464,12 +497,12 @@ func (d *DuckDB) ApplyChanges(tableName string, changes connection.ChangeSet) er wheres, whereArgs := buildWhere(update.Keys) args = append(args, whereArgs...) if len(wheres) == 0 { - return fmt.Errorf("更新操作需要主键条件") + return duckDBUpdateKeyConditionsRequiredError() } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("更新失败:%v", err) + return duckDBUpdateFailedError(err) } } diff --git a/internal/db/duckdb_platform_unsupported.go b/internal/db/duckdb_platform_unsupported.go index 54326be..b9249f9 100644 --- a/internal/db/duckdb_platform_unsupported.go +++ b/internal/db/duckdb_platform_unsupported.go @@ -3,10 +3,11 @@ package db import ( - "fmt" "runtime" ) func duckDBBuildSupportStatus() (bool, string) { - return false, fmt.Sprintf("当前构建不包含 DuckDB 驱动(平台=%s/%s)。需要启用 CGO,并使用受支持平台(darwin/linux amd64|arm64、windows/amd64)或通过 -tags duckdb_use_lib / duckdb_use_static_lib 提供自定义库", runtime.GOOS, runtime.GOARCH) + return false, localizedDriverRuntimeText("db.backend.error.duckdb_build_unavailable", map[string]any{ + "platform": runtime.GOOS + "/" + runtime.GOARCH, + }) } diff --git a/internal/db/elasticsearch_i18n_test.go b/internal/db/elasticsearch_i18n_test.go new file mode 100644 index 0000000..b98fb65 --- /dev/null +++ b/internal/db/elasticsearch_i18n_test.go @@ -0,0 +1,127 @@ +//go:build gonavi_full_drivers || gonavi_elasticsearch_driver + +package db + +import ( + "context" + "os" + "strings" + "testing" + + "GoNavi-Wails/internal/connection" + "GoNavi-Wails/shared/i18n" +) + +const rawElasticsearchConnectionNotOpenText = "\u8fde\u63a5\u672a\u6253\u5f00" + +func TestElasticsearchConnectionNotOpenUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + elasticsearchDB := &ElasticsearchDB{} + cases := []struct { + name string + call func() error + }{ + { + name: "ping", + call: func() error { + return elasticsearchDB.Ping() + }, + }, + { + name: "query", + call: func() error { + _, _, err := elasticsearchDB.Query("test") + return err + }, + }, + { + name: "query_context", + call: func() error { + _, _, err := elasticsearchDB.QueryContext(context.Background(), "test") + return err + }, + }, + { + name: "get_databases", + call: func() error { + _, err := elasticsearchDB.GetDatabases() + return err + }, + }, + { + name: "get_tables", + call: func() error { + _, err := elasticsearchDB.GetTables("test") + return err + }, + }, + { + name: "get_create_statement", + call: func() error { + _, err := elasticsearchDB.GetCreateStatement("test", "") + return err + }, + }, + { + name: "get_columns", + call: func() error { + _, err := elasticsearchDB.GetColumns("test", "") + return err + }, + }, + { + name: "get_all_columns", + call: func() error { + _, err := elasticsearchDB.GetAllColumns("test") + return err + }, + }, + { + name: "get_indexes", + call: func() error { + _, err := elasticsearchDB.GetIndexes("test", "") + return err + }, + }, + { + name: "apply_changes", + call: func() error { + return elasticsearchDB.ApplyChanges("test", connection.ChangeSet{}) + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected connection-not-open error") + } + if err.Error() != "Connection is not open" { + t.Fatalf("expected English connection-not-open error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawElasticsearchConnectionNotOpenText) { + t.Fatalf("expected no raw Chinese connection-not-open text, got %q", err.Error()) + } + }) + } +} + +func TestElasticsearchConnectionNotOpenSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("elasticsearch_impl.go") + if err != nil { + t.Fatalf("read elasticsearch_impl.go: %v", err) + } + source := string(sourceBytes) + + if strings.Contains(source, `fmt.Errorf("`+rawElasticsearchConnectionNotOpenText+`")`) { + t.Fatal("elasticsearch_impl.go still contains raw connection-not-open text") + } + if !strings.Contains(source, "db.backend.error.connection_not_open") { + t.Fatal("elasticsearch_impl.go does not reference db.backend.error.connection_not_open") + } +} diff --git a/internal/db/elasticsearch_impl.go b/internal/db/elasticsearch_impl.go index 40632fe..cd98e90 100644 --- a/internal/db/elasticsearch_impl.go +++ b/internal/db/elasticsearch_impl.go @@ -138,7 +138,7 @@ func (e *ElasticsearchDB) Close() error { // Ping 检测 Elasticsearch 连通性。 func (e *ElasticsearchDB) Ping() error { if e.client == nil { - return fmt.Errorf("连接未打开") + return localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } timeout := e.pingTimeout if timeout <= 0 { @@ -174,7 +174,7 @@ func (e *ElasticsearchDB) QueryContext(ctx context.Context, query string) ([]map // queryWithContext 查询的核心实现,被 Query 和 QueryContext 共用。 func (e *ElasticsearchDB) queryWithContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if e.client == nil { - return nil, nil, fmt.Errorf("连接未打开") + return nil, nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } query = strings.TrimSpace(query) @@ -325,7 +325,7 @@ func (e *ElasticsearchDB) ExecContext(_ context.Context, _ string) (int64, error // GetDatabases 列出所有 Elasticsearch 索引。 func (e *ElasticsearchDB) GetDatabases() ([]string, error) { if e.client == nil { - return nil, fmt.Errorf("连接未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -361,7 +361,7 @@ func (e *ElasticsearchDB) GetDatabases() ([]string, error) { // GetTables 对 ES 而言索引即表,返回索引自身名称及别名。 func (e *ElasticsearchDB) GetTables(dbName string) ([]string, error) { if e.client == nil { - return nil, fmt.Errorf("连接未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } target := strings.TrimSpace(dbName) @@ -381,7 +381,7 @@ func (e *ElasticsearchDB) GetTables(dbName string) ([]string, error) { // GetCreateStatement 返回索引的 settings + mappings 组合 JSON。 func (e *ElasticsearchDB) GetCreateStatement(dbName, tableName string) (string, error) { if e.client == nil { - return "", fmt.Errorf("连接未打开") + return "", localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } indexName := resolveEsIndexName(dbName, tableName, e.database) @@ -421,7 +421,7 @@ func (e *ElasticsearchDB) GetCreateStatement(dbName, tableName string) (string, // GetColumns 返回索引的 mapping 字段定义。 func (e *ElasticsearchDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { if e.client == nil { - return nil, fmt.Errorf("连接未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } indexName := resolveEsIndexName(dbName, tableName, e.database) @@ -439,7 +439,7 @@ func (e *ElasticsearchDB) GetColumns(dbName, tableName string) ([]connection.Col // GetAllColumns 返回索引的全部字段定义(带表名标识)。 func (e *ElasticsearchDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) { if e.client == nil { - return nil, fmt.Errorf("连接未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } target := strings.TrimSpace(dbName) @@ -471,7 +471,7 @@ func (e *ElasticsearchDB) GetAllColumns(dbName string) ([]connection.ColumnDefin // GetIndexes 返回索引的 settings 中定义的分片与副本信息。 func (e *ElasticsearchDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { if e.client == nil { - return nil, fmt.Errorf("连接未打开") + return nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } indexName := resolveEsIndexName(dbName, tableName, e.database) @@ -625,7 +625,7 @@ func isESMetaField(name string) bool { // ApplyChanges 实现 BatchApplier 接口,通过 ES _bulk API 批量提交增删改。 func (e *ElasticsearchDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if e.client == nil { - return fmt.Errorf("连接未打开") + return localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } indexName := resolveEsIndexName(tableName, "", e.database) diff --git a/internal/db/gaussdb_impl.go b/internal/db/gaussdb_impl.go index 88ba92b..78071d5 100644 --- a/internal/db/gaussdb_impl.go +++ b/internal/db/gaussdb_impl.go @@ -105,7 +105,7 @@ func (g *GaussDB) getDSN(config connection.ConnectionConfig) string { func (g *GaussDB) Connect(config connection.ConnectionConfig) error { if supported, reason := DriverRuntimeSupportStatus("gaussdb"); !supported { if strings.TrimSpace(reason) == "" { - reason = "GaussDB 纯 Go 驱动未启用,请先在驱动管理中安装启用" + reason = localizedDriverRuntimeText("driver_manager.backend.status.optional_disabled", map[string]any{"name": "GaussDB"}) } return fmt.Errorf("%s", reason) } diff --git a/internal/db/highgo_i18n_test.go b/internal/db/highgo_i18n_test.go new file mode 100644 index 0000000..85ecc11 --- /dev/null +++ b/internal/db/highgo_i18n_test.go @@ -0,0 +1,86 @@ +//go:build gonavi_full_drivers || gonavi_highgo_driver + +package db + +import ( + "os" + "strings" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +var rawHighGoTableNameRequiredText = string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) + +func TestHighGoMetadataErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + highgo := &HighGoDB{} + tests := []struct { + name string + call func() error + }{ + { + name: "columns table name required", + call: func() error { + _, err := highgo.GetColumns("", " ") + return err + }, + }, + { + name: "indexes table name required", + call: func() error { + _, err := highgo.GetIndexes("", " ") + return err + }, + }, + { + name: "foreign keys table name required", + call: func() error { + _, err := highgo.GetForeignKeys("", " ") + return err + }, + }, + { + name: "triggers table name required", + call: func() error { + _, err := highgo.GetTriggers("", " ") + return err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected HighGo metadata call to fail") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawHighGoTableNameRequiredText) { + t.Fatalf("expected no raw Chinese HighGo metadata text, got %q", err.Error()) + } + }) + } +} + +func TestHighGoMetadataErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("highgo_impl.go") + if err != nil { + t.Fatalf("read highgo_impl.go: %v", err) + } + source := string(sourceBytes) + rawMessage := `fmt.Errorf("` + rawHighGoTableNameRequiredText + `")` + + if strings.Contains(source, rawMessage) { + t.Fatalf("highgo_impl.go still contains raw HighGo metadata text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.table_name_required") { + t.Fatal("highgo_impl.go does not reference db.backend.error.table_name_required") + } +} diff --git a/internal/db/highgo_impl.go b/internal/db/highgo_impl.go index 19fb06c..1730525 100644 --- a/internal/db/highgo_impl.go +++ b/internal/db/highgo_impl.go @@ -303,7 +303,7 @@ func (h *HighGoDB) GetCreateStatement(dbName, tableName string) (string, error) func (h *HighGoDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { schema, table := normalizePGLikeMetadataTable(dbName, tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } data, _, err := h.Query(buildPGLikeColumnsMetadataQuery(schema, table)) @@ -317,7 +317,7 @@ func (h *HighGoDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefi func (h *HighGoDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { schema, table := normalizePGLikeMetadataTable(dbName, tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } data, _, err := h.Query(buildPGLikeIndexesMetadataQuery(schema, table)) @@ -335,7 +335,7 @@ func (h *HighGoDB) GetForeignKeys(dbName, tableName string) ([]connection.Foreig } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -395,7 +395,7 @@ func (h *HighGoDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDe } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } diff --git a/internal/db/iris_i18n_test.go b/internal/db/iris_i18n_test.go new file mode 100644 index 0000000..377a08a --- /dev/null +++ b/internal/db/iris_i18n_test.go @@ -0,0 +1,74 @@ +//go:build gonavi_full_drivers || gonavi_iris_driver + +package db + +import ( + "os" + "strings" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +var rawIRISTableNameRequiredText = string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) + +func TestIRISTableRefErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + tests := []struct { + name string + raw string + }{ + {name: "empty table", raw: " "}, + {name: "empty qualified table", raw: `"APP". `}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := parseIRISTableRef("USER", tc.raw) + if err == nil { + t.Fatal("expected IRIS table reference parsing to fail") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawIRISTableNameRequiredText) { + t.Fatalf("expected no raw Chinese table-name-required text, got %q", err.Error()) + } + }) + } +} + +func TestIRISTableNameRequiredSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("iris_impl.go") + if err != nil { + t.Fatalf("read iris_impl.go: %v", err) + } + source := string(sourceBytes) + functionSource := databaseFunctionSource(t, source, "func parseIRISTableRef(defaultSchema, raw string) (irisTableRef, error)") + rawMessage := `fmt.Errorf("` + rawIRISTableNameRequiredText + `")` + + if strings.Contains(functionSource, rawMessage) { + t.Fatalf("parseIRISTableRef still contains raw IRIS table-name-required text %q", rawMessage) + } + if !strings.Contains(functionSource, "db.backend.error.table_name_required") { + t.Fatal("parseIRISTableRef does not reference db.backend.error.table_name_required") + } +} + +func TestIRISTableNameRequiredCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + if strings.TrimSpace(catalog["db.backend.error.table_name_required"]) == "" { + t.Fatalf("%s catalog missing IRIS table-name-required key", language) + } + } +} diff --git a/internal/db/iris_impl.go b/internal/db/iris_impl.go index 44ed8bb..7e826e9 100644 --- a/internal/db/iris_impl.go +++ b/internal/db/iris_impl.go @@ -139,12 +139,12 @@ func (i *IrisDB) Connect(config connection.ConnectionConfig) error { db, err := sql.Open("iris", i.getDSN(runConfig)) if err != nil { - return fmt.Errorf("打开数据库连接失败:%w", err) + return wrapDatabaseConnectionOpenError(err) } i.conn = db i.pingTimeout = getConnectTimeout(runConfig) if err := i.Ping(); err != nil { - return fmt.Errorf("连接建立后验证失败:%w", err) + return wrapDatabaseConnectionVerifyError(err) } cleanupOnFailure = false return nil @@ -487,7 +487,7 @@ func (i *IrisDB) ApplyChanges(tableName string, changes connection.ChangeSet) er if err != nil { return fmt.Errorf("删除失败:%v", err) } - if err := requireSingleRowAffected(res, "删除"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionDelete); err != nil { return err } } @@ -504,7 +504,7 @@ func (i *IrisDB) ApplyChanges(tableName string, changes connection.ChangeSet) er if err != nil { return fmt.Errorf("更新失败:%v", err) } - if err := requireSingleRowAffected(res, "更新"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionUpdate); err != nil { return err } } @@ -544,13 +544,13 @@ func buildIRISInfoSchemaWhereQuery(table string, ref irisTableRef) string { func parseIRISTableRef(defaultSchema, raw string) (irisTableRef, error) { text := strings.TrimSpace(raw) if text == "" { - return irisTableRef{}, fmt.Errorf("表名不能为空") + return irisTableRef{}, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } if schemaPart, tablePart, ok := splitIRISTablePath(text); ok { schema := cleanIRISIdentifier(schemaPart) table := cleanIRISIdentifier(tablePart) if table == "" { - return irisTableRef{}, fmt.Errorf("表名不能为空") + return irisTableRef{}, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } return irisTableRef{Schema: schema, Table: table}, nil } diff --git a/internal/db/kingbase_i18n_test.go b/internal/db/kingbase_i18n_test.go new file mode 100644 index 0000000..169e4e0 --- /dev/null +++ b/internal/db/kingbase_i18n_test.go @@ -0,0 +1,86 @@ +//go:build gonavi_full_drivers || gonavi_kingbase_driver + +package db + +import ( + "os" + "strings" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +var rawKingbaseTableNameRequiredText = string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) + +func TestKingbaseMetadataErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + kingbase := &KingbaseDB{} + tests := []struct { + name string + call func() error + }{ + { + name: "columns table name required", + call: func() error { + _, err := kingbase.GetColumns("", " ") + return err + }, + }, + { + name: "indexes table name required", + call: func() error { + _, err := kingbase.GetIndexes("", " ") + return err + }, + }, + { + name: "foreign keys table name required", + call: func() error { + _, err := kingbase.GetForeignKeys("", " ") + return err + }, + }, + { + name: "triggers table name required", + call: func() error { + _, err := kingbase.GetTriggers("", " ") + return err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected Kingbase metadata call to fail") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawKingbaseTableNameRequiredText) { + t.Fatalf("expected no raw Chinese Kingbase metadata text, got %q", err.Error()) + } + }) + } +} + +func TestKingbaseTableNameRequiredSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("kingbase_impl.go") + if err != nil { + t.Fatalf("read kingbase_impl.go: %v", err) + } + source := string(sourceBytes) + rawMessage := `fmt.Errorf("` + rawKingbaseTableNameRequiredText + `")` + + if strings.Contains(source, rawMessage) { + t.Fatalf("kingbase_impl.go still contains raw Kingbase table-name-required text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.table_name_required") { + t.Fatal("kingbase_impl.go does not reference db.backend.error.table_name_required") + } +} diff --git a/internal/db/kingbase_impl.go b/internal/db/kingbase_impl.go index 1daa776..632ddbf 100644 --- a/internal/db/kingbase_impl.go +++ b/internal/db/kingbase_impl.go @@ -496,7 +496,7 @@ func (k *KingbaseDB) GetCreateStatement(dbName, tableName string) (string, error func (k *KingbaseDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { schema, table := normalizePGLikeMetadataTable(dbName, tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } data, _, err := k.Query(buildPGLikeColumnsMetadataQuery(schema, table)) @@ -510,7 +510,7 @@ func (k *KingbaseDB) GetColumns(dbName, tableName string) ([]connection.ColumnDe func (k *KingbaseDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { schema, table := normalizePGLikeMetadataTable(dbName, tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } data, _, err := k.Query(buildPGLikeIndexesMetadataQuery(schema, table)) @@ -537,7 +537,7 @@ func (k *KingbaseDB) GetForeignKeys(dbName, tableName string) ([]connection.Fore } if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } // 转义函数:处理单引号,移除双引号 @@ -619,7 +619,7 @@ func (k *KingbaseDB) GetTriggers(dbName, tableName string) ([]connection.Trigger } if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } // 转义函数:处理单引号,移除双引号 @@ -673,7 +673,7 @@ func (k *KingbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet schema, table := splitKingbaseQualifiedTable(tableName) if table == "" { - return fmt.Errorf("表名不能为空") + return localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } qualifiedTable := "" diff --git a/internal/db/mariadb_i18n_test.go b/internal/db/mariadb_i18n_test.go new file mode 100644 index 0000000..9022b39 --- /dev/null +++ b/internal/db/mariadb_i18n_test.go @@ -0,0 +1,153 @@ +//go:build gonavi_full_drivers || gonavi_mariadb_driver + +package db + +import ( + "database/sql" + "database/sql/driver" + "io" + "os" + "strings" + "sync" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +type mariaDBI18nEmptyRowsDriver struct{} + +type mariaDBI18nEmptyRowsConn struct{} + +type mariaDBI18nEmptyRowsStmt struct{} + +type mariaDBI18nEmptyRowsRows struct{} + +var registerMariaDBI18nEmptyRowsDriverOnce sync.Once + +var rawMariaDBCreateStatementNotFoundText = string([]rune{0x672a, 0x627e, 0x5230, 0x5efa, 0x8868, 0x8bed, 0x53e5}) +var rawMariaDBAllColumnsDatabaseRequiredText = string([]rune{0x83b7, 0x53d6, 0x5168, 0x90e8, 0x5217, 0x4fe1, 0x606f, 0x9700, 0x8981, 0x6307, 0x5b9a, 0x6570, 0x636e, 0x5e93, 0x540d, 0x79f0}) + +func (mariaDBI18nEmptyRowsDriver) Open(name string) (driver.Conn, error) { + return mariaDBI18nEmptyRowsConn{}, nil +} + +func (mariaDBI18nEmptyRowsConn) Prepare(query string) (driver.Stmt, error) { + return mariaDBI18nEmptyRowsStmt{}, nil +} + +func (mariaDBI18nEmptyRowsConn) Close() error { return nil } + +func (mariaDBI18nEmptyRowsConn) Begin() (driver.Tx, error) { + return nil, localizedDatabaseRuntimeError("db.backend.error.transaction_not_open", nil) +} + +func (mariaDBI18nEmptyRowsStmt) Close() error { return nil } + +func (mariaDBI18nEmptyRowsStmt) NumInput() int { return -1 } + +func (mariaDBI18nEmptyRowsStmt) Exec(args []driver.Value) (driver.Result, error) { + return driver.RowsAffected(0), nil +} + +func (mariaDBI18nEmptyRowsStmt) Query(args []driver.Value) (driver.Rows, error) { + return mariaDBI18nEmptyRowsRows{}, nil +} + +func (mariaDBI18nEmptyRowsRows) Columns() []string { + return []string{"Create Table"} +} + +func (mariaDBI18nEmptyRowsRows) Close() error { return nil } + +func (mariaDBI18nEmptyRowsRows) Next(dest []driver.Value) error { + return io.EOF +} + +func openMariaDBI18nEmptyRowsDB(t *testing.T) *sql.DB { + t.Helper() + + registerMariaDBI18nEmptyRowsDriverOnce.Do(func() { + sql.Register("mariadb_i18n_empty_rows", mariaDBI18nEmptyRowsDriver{}) + }) + + conn, err := sql.Open("mariadb_i18n_empty_rows", "") + if err != nil { + t.Fatalf("open mariadb_i18n_empty_rows test DB failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + return conn +} + +func TestMariaDBCreateStatementNotFoundUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + mariaDB := &MariaDB{conn: openMariaDBI18nEmptyRowsDB(t)} + + _, err := mariaDB.GetCreateStatement("app", "orders") + if err == nil { + t.Fatal("expected MariaDB GetCreateStatement to fail") + } + if err.Error() != "The CREATE TABLE statement was not found" { + t.Fatalf("expected English create-statement error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawMariaDBCreateStatementNotFoundText) { + t.Fatalf("expected no raw Chinese create-statement text, got %q", err.Error()) + } +} + +func TestMariaDBCreateStatementSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("mariadb_impl.go") + if err != nil { + t.Fatalf("read mariadb_impl.go: %v", err) + } + source := string(sourceBytes) + + rawMessage := `fmt.Errorf("` + rawMariaDBCreateStatementNotFoundText + `")` + if strings.Contains(source, rawMessage) { + t.Fatalf("mariadb_impl.go still contains raw create-statement text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.create_table_statement_not_found") { + t.Fatal("mariadb_impl.go does not reference db.backend.error.create_table_statement_not_found") + } +} + +func TestMariaDBGetAllColumnsDatabaseRequiredUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + mariaDB := &MariaDB{} + + _, err := mariaDB.GetAllColumns("") + if err == nil { + t.Fatal("expected MariaDB GetAllColumns to fail") + } + if err.Error() != "Database name is required" { + t.Fatalf("expected English database-name error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawMariaDBAllColumnsDatabaseRequiredText) { + t.Fatalf("expected no raw Chinese database-name text, got %q", err.Error()) + } +} + +func TestMariaDBGetAllColumnsDatabaseRequiredSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("mariadb_impl.go") + if err != nil { + t.Fatalf("read mariadb_impl.go: %v", err) + } + source := string(sourceBytes) + + rawMessage := `fmt.Errorf("` + rawMariaDBAllColumnsDatabaseRequiredText + `")` + if strings.Contains(source, rawMessage) { + t.Fatalf("mariadb_impl.go still contains raw database-name text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.database_name_required") { + t.Fatal("mariadb_impl.go does not reference db.backend.error.database_name_required") + } +} diff --git a/internal/db/mariadb_impl.go b/internal/db/mariadb_impl.go index 508fddb..13f08d2 100644 --- a/internal/db/mariadb_impl.go +++ b/internal/db/mariadb_impl.go @@ -47,13 +47,13 @@ func (m *MariaDB) Connect(config connection.ConnectionConfig) error { } db, err := sql.Open("mysql", dsn) if err != nil { - return fmt.Errorf("打开数据库连接失败:%w", err) + return wrapDatabaseConnectionOpenError(err) } m.conn = db m.pingTimeout = getConnectTimeout(config) if err := m.Ping(); err != nil { - return fmt.Errorf("连接建立后验证失败:%w", err) + return wrapDatabaseConnectionVerifyError(err) } return nil } @@ -224,7 +224,7 @@ func (m *MariaDB) GetCreateStatement(dbName, tableName string) (string, error) { return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("未找到建表语句") + return "", localizedDatabaseRuntimeError("db.backend.error.create_table_statement_not_found", nil) } func (m *MariaDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -438,7 +438,7 @@ func (m *MariaDB) ApplyChanges(tableName string, changes connection.ChangeSet) e func (m *MariaDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) { if dbName == "" { - return nil, fmt.Errorf("获取全部列信息需要指定数据库名称") + return nil, localizedDatabaseRuntimeError("db.backend.error.database_name_required", nil) } query := fmt.Sprintf("SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s'", strings.ReplaceAll(dbName, "'", "''")) diff --git a/internal/db/mqtt_i18n_test.go b/internal/db/mqtt_i18n_test.go new file mode 100644 index 0000000..860c566 --- /dev/null +++ b/internal/db/mqtt_i18n_test.go @@ -0,0 +1,115 @@ +package db + +import ( + "os" + "strings" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +func TestMQTTTimeoutMessagesUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + cases := []struct { + name string + key string + want string + raw string + }{ + { + name: "connect timeout", + key: "db.backend.error.mqtt_connect_timeout", + want: "MQTT connection timed out", + raw: "MQTT 连接超时", + }, + { + name: "subscribe timeout", + key: "db.backend.error.mqtt_subscribe_timeout", + want: "MQTT subscription timed out", + raw: "MQTT 订阅超时", + }, + { + name: "publish timeout", + key: "db.backend.error.mqtt_publish_timeout", + want: "MQTT publish timed out", + raw: "MQTT 发布超时", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := localizedDriverRuntimeText(tc.key, nil) + if got != tc.want { + t.Fatalf("expected %q, got %q", tc.want, got) + } + if strings.Contains(got, tc.raw) { + t.Fatalf("expected no raw Chinese timeout text, got %q", got) + } + }) + } +} + +func TestMQTTTimeoutSourceUsesI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("mqtt_impl.go") + if err != nil { + t.Fatalf("read mqtt_impl.go: %v", err) + } + source := string(sourceBytes) + + checks := []struct { + signature string + rawText string + key string + }{ + { + signature: "func newPahoMQTTRuntime(config connection.ConnectionConfig) (mqttRuntime, error)", + rawText: `fmt.Errorf("MQTT 连接超时")`, + key: "db.backend.error.mqtt_connect_timeout", + }, + { + signature: "func (r *pahoMQTTRuntime) FetchMessages(ctx context.Context, request mqttFetchRequest) ([]mqttMessageRecord, error)", + rawText: `fmt.Errorf("MQTT 订阅超时")`, + key: "db.backend.error.mqtt_subscribe_timeout", + }, + { + signature: "func (r *pahoMQTTRuntime) Publish(ctx context.Context, command mqttPublishCommand) (int64, error)", + rawText: `fmt.Errorf("MQTT 发布超时")`, + key: "db.backend.error.mqtt_publish_timeout", + }, + } + + for _, tc := range checks { + functionSource := databaseFunctionSource(t, source, tc.signature) + if strings.Contains(functionSource, tc.rawText) { + t.Fatalf("%s still contains raw MQTT timeout text %q", tc.signature, tc.rawText) + } + if !strings.Contains(functionSource, tc.key) { + t.Fatalf("%s does not reference i18n key %q", tc.signature, tc.key) + } + } +} + +func TestMQTTTimeoutCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + keys := []string{ + "db.backend.error.mqtt_connect_timeout", + "db.backend.error.mqtt_subscribe_timeout", + "db.backend.error.mqtt_publish_timeout", + } + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range keys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing MQTT timeout key %q", language, key) + } + } + } +} diff --git a/internal/db/mqtt_impl.go b/internal/db/mqtt_impl.go index ecc2103..7890071 100644 --- a/internal/db/mqtt_impl.go +++ b/internal/db/mqtt_impl.go @@ -745,7 +745,7 @@ func newPahoMQTTRuntime(config connection.ConnectionConfig) (mqttRuntime, error) client := pahomqtt.NewClient(options) token := client.Connect() if !token.WaitTimeout(timeout + 5*time.Second) { - return nil, fmt.Errorf("MQTT 连接超时") + return nil, localizedDatabaseRuntimeError("db.backend.error.mqtt_connect_timeout", nil) } if err := token.Error(); err != nil { return nil, err @@ -861,7 +861,7 @@ func (r *pahoMQTTRuntime) FetchMessages(ctx context.Context, request mqttFetchRe token := r.client.Subscribe(request.Topic, request.QoS, callback) if !token.WaitTimeout(r.timeout) { - return nil, fmt.Errorf("MQTT 订阅超时") + return nil, localizedDatabaseRuntimeError("db.backend.error.mqtt_subscribe_timeout", nil) } if err := token.Error(); err != nil { return nil, fmt.Errorf("MQTT 订阅失败:%w", err) @@ -920,7 +920,7 @@ func (r *pahoMQTTRuntime) Publish(ctx context.Context, command mqttPublishComman } } if !token.WaitTimeout(wait) { - return 0, fmt.Errorf("MQTT 发布超时") + return 0, localizedDatabaseRuntimeError("db.backend.error.mqtt_publish_timeout", nil) } if err := token.Error(); err != nil { return 0, err diff --git a/internal/db/mysql_i18n_test.go b/internal/db/mysql_i18n_test.go new file mode 100644 index 0000000..47b3ddb --- /dev/null +++ b/internal/db/mysql_i18n_test.go @@ -0,0 +1,151 @@ +package db + +import ( + "database/sql" + "database/sql/driver" + "io" + "os" + "strings" + "sync" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +type mysqlI18nEmptyRowsDriver struct{} + +type mysqlI18nEmptyRowsConn struct{} + +type mysqlI18nEmptyRowsStmt struct{} + +type mysqlI18nEmptyRowsRows struct{} + +var registerMySQLI18nEmptyRowsDriverOnce sync.Once + +var rawMySQLCreateStatementNotFoundText = string([]rune{0x672a, 0x627e, 0x5230, 0x5efa, 0x8868, 0x8bed, 0x53e5}) +var rawMySQLAllColumnsDatabaseRequiredText = string([]rune{0x83b7, 0x53d6, 0x5168, 0x90e8, 0x5217, 0x4fe1, 0x606f, 0x9700, 0x8981, 0x6307, 0x5b9a, 0x6570, 0x636e, 0x5e93, 0x540d, 0x79f0}) + +func (mysqlI18nEmptyRowsDriver) Open(name string) (driver.Conn, error) { + return mysqlI18nEmptyRowsConn{}, nil +} + +func (mysqlI18nEmptyRowsConn) Prepare(query string) (driver.Stmt, error) { + return mysqlI18nEmptyRowsStmt{}, nil +} + +func (mysqlI18nEmptyRowsConn) Close() error { return nil } + +func (mysqlI18nEmptyRowsConn) Begin() (driver.Tx, error) { + return nil, localizedDatabaseRuntimeError("db.backend.error.transaction_not_open", nil) +} + +func (mysqlI18nEmptyRowsStmt) Close() error { return nil } + +func (mysqlI18nEmptyRowsStmt) NumInput() int { return -1 } + +func (mysqlI18nEmptyRowsStmt) Exec(args []driver.Value) (driver.Result, error) { + return driver.RowsAffected(0), nil +} + +func (mysqlI18nEmptyRowsStmt) Query(args []driver.Value) (driver.Rows, error) { + return mysqlI18nEmptyRowsRows{}, nil +} + +func (mysqlI18nEmptyRowsRows) Columns() []string { + return []string{"Create Table"} +} + +func (mysqlI18nEmptyRowsRows) Close() error { return nil } + +func (mysqlI18nEmptyRowsRows) Next(dest []driver.Value) error { + return io.EOF +} + +func openMySQLI18nEmptyRowsDB(t *testing.T) *sql.DB { + t.Helper() + + registerMySQLI18nEmptyRowsDriverOnce.Do(func() { + sql.Register("mysql_i18n_empty_rows", mysqlI18nEmptyRowsDriver{}) + }) + + conn, err := sql.Open("mysql_i18n_empty_rows", "") + if err != nil { + t.Fatalf("open mysql_i18n_empty_rows test DB failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + return conn +} + +func TestMySQLCreateStatementNotFoundUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + mysqlDB := &MySQLDB{conn: openMySQLI18nEmptyRowsDB(t)} + + _, err := mysqlDB.GetCreateStatement("app", "orders") + if err == nil { + t.Fatal("expected MySQL GetCreateStatement to fail") + } + if err.Error() != "The CREATE TABLE statement was not found" { + t.Fatalf("expected English create-statement error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawMySQLCreateStatementNotFoundText) { + t.Fatalf("expected no raw Chinese create-statement text, got %q", err.Error()) + } +} + +func TestMySQLCreateStatementSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("mysql_impl.go") + if err != nil { + t.Fatalf("read mysql_impl.go: %v", err) + } + source := string(sourceBytes) + + rawMessage := `fmt.Errorf("` + rawMySQLCreateStatementNotFoundText + `")` + if strings.Contains(source, rawMessage) { + t.Fatalf("mysql_impl.go still contains raw create-statement text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.create_table_statement_not_found") { + t.Fatal("mysql_impl.go does not reference db.backend.error.create_table_statement_not_found") + } +} + +func TestMySQLGetAllColumnsDatabaseRequiredUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + mysqlDB := &MySQLDB{} + + _, err := mysqlDB.GetAllColumns("") + if err == nil { + t.Fatal("expected MySQL GetAllColumns to fail") + } + if err.Error() != "Database name is required" { + t.Fatalf("expected English database-name error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawMySQLAllColumnsDatabaseRequiredText) { + t.Fatalf("expected no raw Chinese database-name text, got %q", err.Error()) + } +} + +func TestMySQLGetAllColumnsDatabaseRequiredSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("mysql_impl.go") + if err != nil { + t.Fatalf("read mysql_impl.go: %v", err) + } + source := string(sourceBytes) + + rawMessage := `fmt.Errorf("` + rawMySQLAllColumnsDatabaseRequiredText + `")` + if strings.Contains(source, rawMessage) { + t.Fatalf("mysql_impl.go still contains raw database-name text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.database_name_required") { + t.Fatal("mysql_impl.go does not reference db.backend.error.database_name_required") + } +} diff --git a/internal/db/mysql_impl.go b/internal/db/mysql_impl.go index 2b4adf6..bb9d058 100644 --- a/internal/db/mysql_impl.go +++ b/internal/db/mysql_impl.go @@ -1080,7 +1080,7 @@ func (m *MySQLDB) GetCreateStatement(dbName, tableName string) (string, error) { return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("未找到建表语句") + return "", localizedDatabaseRuntimeError("db.backend.error.create_table_statement_not_found", nil) } func (m *MySQLDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -1241,7 +1241,7 @@ func (m *MySQLDB) ApplyChanges(tableName string, changes connection.ChangeSet) e if err != nil { return fmt.Errorf("删除失败:%v", err) } - if err := requireSingleRowAffected(res, "删除"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionDelete); err != nil { return err } } @@ -1275,7 +1275,7 @@ func (m *MySQLDB) ApplyChanges(tableName string, changes connection.ChangeSet) e if err != nil { return fmt.Errorf("更新失败:%v", err) } - if err := requireSingleRowAffected(res, "更新"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionUpdate); err != nil { return err } } @@ -1620,7 +1620,7 @@ func formatMySQLDateTime(t time.Time) string { func (m *MySQLDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) { if dbName == "" { - return nil, fmt.Errorf("获取全部列信息需要指定数据库名称") + return nil, localizedDatabaseRuntimeError("db.backend.error.database_name_required", nil) } query := fmt.Sprintf("SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s'", strings.ReplaceAll(dbName, "'", "''")) diff --git a/internal/db/oceanbase_impl.go b/internal/db/oceanbase_impl.go index 742f795..0906ff2 100644 --- a/internal/db/oceanbase_impl.go +++ b/internal/db/oceanbase_impl.go @@ -881,7 +881,10 @@ func (o *OceanBaseDB) GetCreateStatement(dbName, tableName string) (string, erro return showDDL, nil } if err != nil { - return "", fmt.Errorf("%w;OceanBase Oracle SHOW CREATE TABLE 兜底失败:%v", err, showErr) + return "", localizedDatabaseRuntimeError("db.backend.error.oceanbase_oracle_show_create_table_fallback_failed", map[string]any{ + "metadataDetail": err.Error(), + "showDetail": showErr.Error(), + }) } return "", showErr } @@ -906,7 +909,7 @@ func (o *OceanBaseDB) getOceanBaseOracleShowCreateStatement(dbName string, table if firstErr != nil { return "", firstErr } - return "", fmt.Errorf("未找到建表语句") + return "", localizedDatabaseRuntimeError("db.backend.error.create_table_statement_not_found", nil) } func buildOceanBaseOracleShowCreateTableQuery(schema string, table string) string { @@ -1100,7 +1103,7 @@ func (o *OceanBaseDB) applyOracleChangesMySQLWire(tableName string, changes conn if err != nil { return fmt.Errorf("删除失败:%v", err) } - if err := requireSingleRowAffected(res, "删除"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionDelete); err != nil { return err } } @@ -1131,7 +1134,7 @@ func (o *OceanBaseDB) applyOracleChangesMySQLWire(tableName string, changes conn if err != nil { return fmt.Errorf("更新失败:%v", err) } - if err := requireSingleRowAffected(res, "更新"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionUpdate); err != nil { return err } } diff --git a/internal/db/oceanbase_impl_test.go b/internal/db/oceanbase_impl_test.go index 44f01e2..61f7d94 100644 --- a/internal/db/oceanbase_impl_test.go +++ b/internal/db/oceanbase_impl_test.go @@ -8,6 +8,7 @@ import ( "errors" "net" "net/url" + "os" "slices" "strconv" "strings" @@ -15,6 +16,7 @@ import ( "time" "GoNavi-Wails/internal/connection" + "GoNavi-Wails/shared/i18n" mysqlDriver "github.com/go-sql-driver/mysql" ) @@ -859,6 +861,52 @@ func TestOceanBaseOracleGetCreateStatementFallsBackToShowCreateTable(t *testing. } } +func TestOceanBaseOracleCreateStatementFallbackErrorUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + defer SetBackendLanguage(i18n.LanguageZhCN) + + dbConn, _ := openOracleRecordingDB(t) + oceanbaseDB := &OceanBaseDB{} + oceanbaseDB.bindConnectedDatabase(dbConn, 0, oceanBaseProtocolOracle) + + _, err := oceanbaseDB.GetCreateStatement("SYS", "test") + if err == nil { + t.Fatal("GetCreateStatement() expected error") + } + + message := err.Error() + if strings.Contains(message, "未找到建表语句") || strings.Contains(message, "兜底失败") { + t.Fatalf("expected localized English fallback error, got %q", message) + } + if !strings.Contains(message, "The CREATE TABLE statement was not found") { + t.Fatalf("expected localized create-table-not-found detail, got %q", message) + } + if !strings.Contains(message, "OceanBase Oracle SHOW CREATE TABLE fallback failed") { + t.Fatalf("expected localized fallback wrapper, got %q", message) + } +} + +func TestOceanBaseOracleCreateStatementSourceUsesI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("oceanbase_impl.go") + if err != nil { + t.Fatalf("read oceanbase_impl.go: %v", err) + } + source := string(sourceBytes) + + if strings.Contains(source, `fmt.Errorf("未找到建表语句")`) { + t.Fatal("oceanbase_impl.go still contains raw create-statement-not-found error") + } + if strings.Contains(source, "OceanBase Oracle SHOW CREATE TABLE 兜底失败") { + t.Fatal("oceanbase_impl.go still contains raw OceanBase SHOW CREATE TABLE fallback wrapper") + } + if !strings.Contains(source, "db.backend.error.create_table_statement_not_found") { + t.Fatal("oceanbase_impl.go does not reference db.backend.error.create_table_statement_not_found") + } + if !strings.Contains(source, "db.backend.error.oceanbase_oracle_show_create_table_fallback_failed") { + t.Fatal("oceanbase_impl.go does not reference OceanBase SHOW CREATE TABLE fallback i18n key") + } +} + // 用户通过 ConnectionParams 设置 connectionAttributes 时,OceanBase MySQL wire 路径必须把 // 这些 attribute 透传到 go-sql-driver/mysql DSN,让 driver 在握手响应里发 CLIENT_CONNECT_ATTRS。 // 这是 OBClient 协议握手探索的入口:高级用户/DBA 可以试错不同 attribute 组合而不需要改 GoNavi 代码。 diff --git a/internal/db/oracle_i18n_test.go b/internal/db/oracle_i18n_test.go new file mode 100644 index 0000000..052655c --- /dev/null +++ b/internal/db/oracle_i18n_test.go @@ -0,0 +1,234 @@ +package db + +import ( + "database/sql" + "database/sql/driver" + "errors" + "io" + "os" + "strings" + "sync" + "testing" + + "GoNavi-Wails/internal/connection" + "GoNavi-Wails/shared/i18n" +) + +type oracleI18nQueryErrorDriver struct{} + +type oracleI18nQueryErrorConn struct{} + +type oracleI18nEmptyRowsDriver struct{} + +type oracleI18nEmptyRowsConn struct{} + +type oracleI18nEmptyRowsRows struct{} + +var registerOracleI18nQueryErrorDriverOnce sync.Once + +var registerOracleI18nEmptyRowsDriverOnce sync.Once + +var rawOracleCreateStatementNotFoundText = string([]rune{0x672a, 0x627e, 0x5230, 0x5efa, 0x8868, 0x8bed, 0x53e5}) + +func (oracleI18nQueryErrorDriver) Open(name string) (driver.Conn, error) { + return oracleI18nQueryErrorConn{}, nil +} + +func (oracleI18nQueryErrorConn) Prepare(query string) (driver.Stmt, error) { + return nil, errors.New("prepare is not supported in oracle i18n query error test driver") +} + +func (oracleI18nQueryErrorConn) Close() error { return nil } + +func (oracleI18nQueryErrorConn) Begin() (driver.Tx, error) { + return nil, errors.New("transactions are not supported in oracle i18n query error test driver") +} + +func (oracleI18nQueryErrorConn) Query(query string, args []driver.Value) (driver.Rows, error) { + return nil, errors.New("oracle metadata probe failed") +} + +func (oracleI18nEmptyRowsDriver) Open(name string) (driver.Conn, error) { + return oracleI18nEmptyRowsConn{}, nil +} + +func (oracleI18nEmptyRowsConn) Prepare(query string) (driver.Stmt, error) { + return nil, errors.New("prepare is not supported in oracle i18n empty rows test driver") +} + +func (oracleI18nEmptyRowsConn) Close() error { return nil } + +func (oracleI18nEmptyRowsConn) Begin() (driver.Tx, error) { + return nil, errors.New("transactions are not supported in oracle i18n empty rows test driver") +} + +func (oracleI18nEmptyRowsConn) Query(query string, args []driver.Value) (driver.Rows, error) { + return oracleI18nEmptyRowsRows{}, nil +} + +func (oracleI18nEmptyRowsRows) Columns() []string { + return []string{"DDL"} +} + +func (oracleI18nEmptyRowsRows) Close() error { return nil } + +func (oracleI18nEmptyRowsRows) Next(dest []driver.Value) error { + return io.EOF +} + +func openOracleI18nQueryErrorDB(t *testing.T) *sql.DB { + t.Helper() + + registerOracleI18nQueryErrorDriverOnce.Do(func() { + sql.Register("oracle_i18n_query_error", oracleI18nQueryErrorDriver{}) + }) + + conn, err := sql.Open("oracle_i18n_query_error", "") + if err != nil { + t.Fatalf("open oracle_i18n_query_error test DB failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + return conn +} + +func openOracleI18nEmptyRowsDB(t *testing.T) *sql.DB { + t.Helper() + + registerOracleI18nEmptyRowsDriverOnce.Do(func() { + sql.Register("oracle_i18n_empty_rows", oracleI18nEmptyRowsDriver{}) + }) + + conn, err := sql.Open("oracle_i18n_empty_rows", "") + if err != nil { + t.Fatalf("open oracle_i18n_empty_rows test DB failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + return conn +} + +func oracleFunctionSource(t *testing.T, source string, signature string) string { + t.Helper() + start := strings.Index(source, signature) + if start < 0 { + t.Fatalf("oracle_impl.go missing function signature %q", signature) + } + rest := source[start+len(signature):] + end := strings.Index(rest, "\nfunc ") + if end < 0 { + return source[start:] + } + return source[start : start+len(signature)+end] +} + +func TestOracleCreateStatementNotFoundUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + oracleDB := &OracleDB{conn: openOracleI18nEmptyRowsDB(t)} + + _, err := oracleDB.GetCreateStatement("APP", "ORDERS") + if err == nil { + t.Fatal("expected Oracle GetCreateStatement to fail") + } + if err.Error() != "The CREATE TABLE statement was not found" { + t.Fatalf("expected English create-statement error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawOracleCreateStatementNotFoundText) { + t.Fatalf("expected no raw Chinese create-statement text, got %q", err.Error()) + } +} + +func TestOracleMetadataErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + t.Run("indexes table name required", func(t *testing.T) { + _, err := (&OracleDB{}).GetIndexes("", " ") + if err == nil { + t.Fatal("expected Oracle GetIndexes to reject an empty table name") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + }) + + t.Run("apply changes wraps oracle column metadata load failure", func(t *testing.T) { + oracleDB := &OracleDB{conn: openOracleI18nQueryErrorDB(t)} + + err := oracleDB.ApplyChanges("APP.USERS", connection.ChangeSet{}) + if err == nil { + t.Fatal("expected Oracle ApplyChanges to surface column metadata load failure") + } + + want := "Failed to load column metadata (table=APP.USERS): oracle metadata probe failed; check ALL_TAB_COLUMNS query permission and whether the table exists" + if err.Error() != want { + t.Fatalf("expected English Oracle column-metadata error %q, got %q", want, err.Error()) + } + if strings.Contains(err.Error(), "加载列元数据失败") { + t.Fatalf("expected no legacy Chinese Oracle column-metadata prefix in en-US mode, got %q", err.Error()) + } + }) +} + +func TestOracleUserVisibleMetadataErrorsDoNotReintroduceInlineChinese(t *testing.T) { + sourceBytes, err := os.ReadFile("oracle_impl.go") + if err != nil { + t.Fatalf("read oracle_impl.go: %v", err) + } + source := string(sourceBytes) + + getIndexesSource := oracleFunctionSource(t, source, "func (o *OracleDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error)") + if strings.Contains(getIndexesSource, `fmt.Errorf("表名不能为空")`) { + t.Fatal("GetIndexes still contains raw Chinese table-name-required text") + } + if !strings.Contains(getIndexesSource, "db.backend.error.table_name_required") { + t.Fatal("GetIndexes does not reference db.backend.error.table_name_required") + } + + getCreateStatementSource := oracleFunctionSource(t, source, "func (o *OracleDB) GetCreateStatement(dbName, tableName string) (string, error)") + rawCreateStatementMessage := `fmt.Errorf("` + rawOracleCreateStatementNotFoundText + `")` + if strings.Contains(getCreateStatementSource, rawCreateStatementMessage) { + t.Fatalf("GetCreateStatement still contains raw create-statement text %q", rawCreateStatementMessage) + } + if !strings.Contains(getCreateStatementSource, "db.backend.error.create_table_statement_not_found") { + t.Fatal("GetCreateStatement does not reference db.backend.error.create_table_statement_not_found") + } + + loadColumnTypeMapSource := oracleFunctionSource(t, source, "func (o *OracleDB) loadColumnTypeMap(tableName string) (map[string]string, error)") + if strings.Contains(loadColumnTypeMapSource, `fmt.Errorf("加载列元数据失败(表=%s):%w;请检查 ALL_TAB_COLUMNS 查询权限与表是否存在", tableName, err)`) { + t.Fatal("loadColumnTypeMap still contains raw Chinese Oracle column-metadata failure text") + } + if !strings.Contains(loadColumnTypeMapSource, "db.backend.error.oracle_column_metadata_load_failed") { + t.Fatal("loadColumnTypeMap does not reference db.backend.error.oracle_column_metadata_load_failed") + } +} + +func TestOracleMetadataCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + keys := []string{ + "db.backend.error.create_table_statement_not_found", + "db.backend.error.table_name_required", + "db.backend.error.oracle_column_metadata_load_failed", + } + + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range keys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing Oracle metadata key %q", language, key) + } + } + } +} diff --git a/internal/db/oracle_impl.go b/internal/db/oracle_impl.go index 5373bdb..23ff44e 100644 --- a/internal/db/oracle_impl.go +++ b/internal/db/oracle_impl.go @@ -28,6 +28,10 @@ type OracleDB struct { var _ SessionExecerProvider = (*OracleDB)(nil) var _ TransactionExecerProvider = (*OracleDB)(nil) +func oracleRuntimeError(key string, params map[string]any) error { + return fmt.Errorf("%s", localizedDriverRuntimeText(key, params)) +} + func (o *OracleDB) getDSN(config connection.ConnectionConfig) string { // oracle://user:pass@host:port/service_name database := strings.TrimSpace(config.Database) @@ -357,7 +361,7 @@ func (o *OracleDB) GetCreateStatement(dbName, tableName string) (string, error) if firstErr != nil { return "", firstErr } - return "", fmt.Errorf("未找到建表语句") + return "", oracleRuntimeError("db.backend.error.create_table_statement_not_found", nil) } func (o *OracleDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -756,7 +760,7 @@ func parseOracleColumns(data []map[string]interface{}) []connection.ColumnDefini func (o *OracleDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { if strings.TrimSpace(tableName) == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, oracleRuntimeError("db.backend.error.table_name_required", nil) } for _, candidate := range oracleMetadataNamePairs(dbName, tableName) { @@ -954,7 +958,10 @@ func (o *OracleDB) loadColumnTypeMap(tableName string) (map[string]string, error columns, err := o.GetColumns(schema, table) if err != nil { - return nil, fmt.Errorf("加载列元数据失败(表=%s):%w;请检查 ALL_TAB_COLUMNS 查询权限与表是否存在", tableName, err) + return nil, oracleRuntimeError("db.backend.error.oracle_column_metadata_load_failed", map[string]any{ + "table": tableName, + "detail": err.Error(), + }) } for _, col := range columns { @@ -1122,7 +1129,7 @@ func (o *OracleDB) ApplyChanges(tableName string, changes connection.ChangeSet) if err != nil { return fmt.Errorf("删除失败:%v", err) } - if err := requireSingleRowAffected(res, "删除"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionDelete); err != nil { return err } } @@ -1155,7 +1162,7 @@ func (o *OracleDB) ApplyChanges(tableName string, changes connection.ChangeSet) if err != nil { return fmt.Errorf("更新失败:%v", err) } - if err := requireSingleRowAffected(res, "更新"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionUpdate); err != nil { return err } } diff --git a/internal/db/postgres_i18n_test.go b/internal/db/postgres_i18n_test.go new file mode 100644 index 0000000..72d69f3 --- /dev/null +++ b/internal/db/postgres_i18n_test.go @@ -0,0 +1,84 @@ +package db + +import ( + "os" + "strings" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +var rawPostgresTableNameRequiredText = string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) + +func TestPostgresMetadataErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + postgres := &PostgresDB{} + tests := []struct { + name string + call func() error + }{ + { + name: "columns table name required", + call: func() error { + _, err := postgres.GetColumns("", " ") + return err + }, + }, + { + name: "indexes table name required", + call: func() error { + _, err := postgres.GetIndexes("", " ") + return err + }, + }, + { + name: "foreign keys table name required", + call: func() error { + _, err := postgres.GetForeignKeys("", " ") + return err + }, + }, + { + name: "triggers table name required", + call: func() error { + _, err := postgres.GetTriggers("", " ") + return err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected PostgreSQL metadata call to fail") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawPostgresTableNameRequiredText) { + t.Fatalf("expected no raw Chinese PostgreSQL metadata text, got %q", err.Error()) + } + }) + } +} + +func TestPostgresMetadataErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("postgres_impl.go") + if err != nil { + t.Fatalf("read postgres_impl.go: %v", err) + } + source := string(sourceBytes) + rawMessage := `fmt.Errorf("` + rawPostgresTableNameRequiredText + `")` + + if strings.Contains(source, rawMessage) { + t.Fatalf("postgres_impl.go still contains raw PostgreSQL metadata text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.table_name_required") { + t.Fatal("postgres_impl.go does not reference db.backend.error.table_name_required") + } +} diff --git a/internal/db/postgres_impl.go b/internal/db/postgres_impl.go index 30f5fac..51aa5b5 100644 --- a/internal/db/postgres_impl.go +++ b/internal/db/postgres_impl.go @@ -82,7 +82,7 @@ func (p *PostgresDB) getDSN(config connection.ConnectionConfig) string { func (p *PostgresDB) Connect(config connection.ConnectionConfig) error { if supported, reason := DriverRuntimeSupportStatus("postgres"); !supported { if strings.TrimSpace(reason) == "" { - reason = "PostgreSQL 纯 Go 驱动未启用,请先在驱动管理中安装启用" + reason = localizedDriverRuntimeText("driver_manager.backend.status.optional_disabled", map[string]any{"name": "PostgreSQL"}) } return fmt.Errorf("%s", reason) } @@ -408,7 +408,7 @@ func (p *PostgresDB) GetCreateStatement(dbName, tableName string) (string, error func (p *PostgresDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { schema, table := normalizePGLikeMetadataTable(dbName, tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } data, _, err := p.Query(buildPGLikeColumnsMetadataQuery(schema, table)) @@ -422,7 +422,7 @@ func (p *PostgresDB) GetColumns(dbName, tableName string) ([]connection.ColumnDe func (p *PostgresDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { schema, table := normalizePGLikeMetadataTable(dbName, tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } data, _, err := p.Query(buildPGLikeIndexesMetadataQuery(schema, table)) @@ -440,7 +440,7 @@ func (p *PostgresDB) GetForeignKeys(dbName, tableName string) ([]connection.Fore } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -500,7 +500,7 @@ func (p *PostgresDB) GetTriggers(dbName, tableName string) ([]connection.Trigger } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -719,7 +719,7 @@ func (p *PostgresDB) ApplyChanges(tableName string, changes connection.ChangeSet if err != nil { return fmt.Errorf("删除失败:%v", err) } - if err := requireSingleRowAffected(res, "删除"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionDelete); err != nil { return err } } @@ -756,7 +756,7 @@ func (p *PostgresDB) ApplyChanges(tableName string, changes connection.ChangeSet if err != nil { return fmt.Errorf("更新失败:%v", err) } - if err := requireSingleRowAffected(res, "更新"); err != nil { + if err := requireSingleRowAffected(res, rowMutationActionUpdate); err != nil { return err } } diff --git a/internal/db/sqlite_i18n_test.go b/internal/db/sqlite_i18n_test.go new file mode 100644 index 0000000..a4363c0 --- /dev/null +++ b/internal/db/sqlite_i18n_test.go @@ -0,0 +1,274 @@ +//go:build gonavi_full_drivers || gonavi_sqlite_driver + +package db + +import ( + "database/sql" + "database/sql/driver" + "io" + "os" + "strings" + "sync" + "testing" + + "GoNavi-Wails/internal/connection" + "GoNavi-Wails/shared/i18n" +) + +type sqliteI18nEmptyRowsDriver struct{} + +type sqliteI18nEmptyRowsConn struct{} + +type sqliteI18nEmptyRowsStmt struct{} + +type sqliteI18nEmptyRowsRows struct{} + +var registerSQLiteI18nEmptyRowsDriverOnce sync.Once + +var ( + rawSQLiteCreateStatementNotFoundText = string([]rune{0x672a, 0x627e, 0x5230, 0x5efa, 0x8868, 0x8bed, 0x53e5}) + rawSQLiteTableNameRequiredText = string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) + rawSQLiteFilePathRequiredText = "SQLite " + string([]rune{0x9700, 0x8981, 0x672c, 0x5730, 0x6570, 0x636e, 0x5e93, 0x6587, 0x4ef6, 0x8def, 0x5f84}) + rawSQLiteHostAddressHintText = string([]rune{0x5f53, 0x524d, 0x8f93, 0x5165, 0x770b, 0x8d77, 0x6765, 0x662f, 0x4e3b, 0x673a, 0x5730, 0x5740}) +) + +func (sqliteI18nEmptyRowsDriver) Open(name string) (driver.Conn, error) { + return sqliteI18nEmptyRowsConn{}, nil +} + +func (sqliteI18nEmptyRowsConn) Prepare(query string) (driver.Stmt, error) { + return sqliteI18nEmptyRowsStmt{}, nil +} + +func (sqliteI18nEmptyRowsConn) Close() error { return nil } + +func (sqliteI18nEmptyRowsConn) Begin() (driver.Tx, error) { + return nil, localizedDatabaseRuntimeError("db.backend.error.transaction_not_open", nil) +} + +func (sqliteI18nEmptyRowsStmt) Close() error { return nil } + +func (sqliteI18nEmptyRowsStmt) NumInput() int { return -1 } + +func (sqliteI18nEmptyRowsStmt) Exec(args []driver.Value) (driver.Result, error) { + return driver.RowsAffected(0), nil +} + +func (sqliteI18nEmptyRowsStmt) Query(args []driver.Value) (driver.Rows, error) { + return sqliteI18nEmptyRowsRows{}, nil +} + +func (sqliteI18nEmptyRowsRows) Columns() []string { + return []string{"sql"} +} + +func (sqliteI18nEmptyRowsRows) Close() error { return nil } + +func (sqliteI18nEmptyRowsRows) Next(dest []driver.Value) error { + return io.EOF +} + +func openSQLiteI18nEmptyRowsDB(t *testing.T) *sql.DB { + t.Helper() + + registerSQLiteI18nEmptyRowsDriverOnce.Do(func() { + sql.Register("sqlite_i18n_empty_rows", sqliteI18nEmptyRowsDriver{}) + }) + + conn, err := sql.Open("sqlite_i18n_empty_rows", "") + if err != nil { + t.Fatalf("open sqlite_i18n_empty_rows test DB failed: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + return conn +} + +func TestSQLiteMetadataErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + sqlite := &SQLiteDB{} + tests := []struct { + name string + call func() error + want string + unexpected string + }{ + { + name: "create statement not found", + call: func() error { + _, err := (&SQLiteDB{conn: openSQLiteI18nEmptyRowsDB(t)}).GetCreateStatement("main", "orders") + return err + }, + want: "The CREATE TABLE statement was not found", + unexpected: rawSQLiteCreateStatementNotFoundText, + }, + { + name: "columns table name required", + call: func() error { + _, err := sqlite.GetColumns("", " ") + return err + }, + want: "Table name is required", + unexpected: rawSQLiteTableNameRequiredText, + }, + { + name: "indexes table name required", + call: func() error { + _, err := sqlite.GetIndexes("", " ") + return err + }, + want: "Table name is required", + unexpected: rawSQLiteTableNameRequiredText, + }, + { + name: "foreign keys table name required", + call: func() error { + _, err := sqlite.GetForeignKeys("", " ") + return err + }, + want: "Table name is required", + unexpected: rawSQLiteTableNameRequiredText, + }, + { + name: "triggers table name required", + call: func() error { + _, err := sqlite.GetTriggers("", " ") + return err + }, + want: "Table name is required", + unexpected: rawSQLiteTableNameRequiredText, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected SQLite metadata call to fail") + } + if err.Error() != tc.want { + t.Fatalf("expected %q, got %q", tc.want, err.Error()) + } + if strings.Contains(err.Error(), tc.unexpected) { + t.Fatalf("expected no raw Chinese SQLite metadata text, got %q", err.Error()) + } + }) + } +} + +func TestSQLiteDSNValidationErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + tests := []struct { + name string + config connection.ConnectionConfig + want string + unexpected string + }{ + { + name: "empty path", + config: connection.ConnectionConfig{Type: "sqlite"}, + want: "SQLite requires a local database file path (for example /path/to/demo.sqlite)", + unexpected: rawSQLiteFilePathRequiredText, + }, + { + name: "host port", + config: connection.ConnectionConfig{Type: "sqlite", Host: "localhost:3306"}, + want: "SQLite requires a local database file path; the current input looks like a host address: localhost:3306", + unexpected: rawSQLiteHostAddressHintText, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := (&SQLiteDB{}).Connect(tc.config) + if err == nil { + t.Fatal("expected SQLite DSN validation error") + } + if err.Error() != tc.want { + t.Fatalf("expected %q, got %q", tc.want, err.Error()) + } + if strings.Contains(err.Error(), tc.unexpected) { + t.Fatalf("expected no raw Chinese SQLite DSN validation text, got %q", err.Error()) + } + }) + } +} + +func TestSQLiteMetadataErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("sqlite_impl.go") + if err != nil { + t.Fatalf("read sqlite_impl.go: %v", err) + } + source := string(sourceBytes) + + for _, rawMessage := range []string{ + `fmt.Errorf("` + rawSQLiteCreateStatementNotFoundText + `")`, + `fmt.Errorf("` + rawSQLiteTableNameRequiredText + `")`, + } { + if strings.Contains(source, rawMessage) { + t.Fatalf("sqlite_impl.go still contains raw SQLite metadata text %q", rawMessage) + } + } + for _, key := range []string{ + "db.backend.error.create_table_statement_not_found", + "db.backend.error.table_name_required", + } { + if !strings.Contains(source, key) { + t.Fatalf("sqlite_impl.go does not reference i18n key %q", key) + } + } +} + +func TestSQLiteDSNValidationErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("sqlite_impl.go") + if err != nil { + t.Fatalf("read sqlite_impl.go: %v", err) + } + source := string(sourceBytes) + + for _, rawMessage := range []string{ + rawSQLiteFilePathRequiredText, + rawSQLiteHostAddressHintText, + } { + if strings.Contains(source, rawMessage) { + t.Fatalf("sqlite_impl.go still contains raw SQLite DSN validation text %q", rawMessage) + } + } + for _, key := range []string{ + "db.backend.error.sqlite_file_path_required", + "db.backend.error.sqlite_host_port_not_file_path", + } { + if !strings.Contains(source, key) { + t.Fatalf("sqlite_impl.go does not reference i18n key %q", key) + } + } +} + +func TestSQLiteDSNValidationErrorCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + keys := []string{ + "db.backend.error.sqlite_file_path_required", + "db.backend.error.sqlite_host_port_not_file_path", + } + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range keys { + if strings.TrimSpace(catalog[key]) == "" { + t.Fatalf("%s catalog missing SQLite DSN validation key %q", language, key) + } + } + } +} diff --git a/internal/db/sqlite_impl.go b/internal/db/sqlite_impl.go index acfb53d..3d83c43 100644 --- a/internal/db/sqlite_impl.go +++ b/internal/db/sqlite_impl.go @@ -34,7 +34,7 @@ func (s *SQLiteDB) Connect(config connection.ConnectionConfig) error { db, err := sql.Open("sqlite", dsn) if err != nil { - return fmt.Errorf("打开数据库连接失败:%w", err) + return wrapDatabaseConnectionOpenError(err) } s.conn = db s.pingTimeout = getConnectTimeout(config) @@ -43,7 +43,7 @@ func (s *SQLiteDB) Connect(config connection.ConnectionConfig) error { if err := s.Ping(); err != nil { _ = db.Close() s.conn = nil - return fmt.Errorf("连接建立后验证失败:%w", err) + return wrapDatabaseConnectionVerifyError(err) } return nil } @@ -55,13 +55,13 @@ func resolveSQLiteDSN(config connection.ConnectionConfig) (string, error) { } dsn = normalizeSQLitePath(dsn) if dsn == "" { - return "", fmt.Errorf("SQLite 需要本地数据库文件路径(例如 /path/to/demo.sqlite)") + return "", localizedDatabaseRuntimeError("db.backend.error.sqlite_file_path_required", nil) } if strings.EqualFold(dsn, ":memory:") { return dsn, nil } if looksLikeHostPort(dsn) { - return "", fmt.Errorf("SQLite 需要本地数据库文件路径,当前输入看起来是主机地址:%s", dsn) + return "", localizedDatabaseRuntimeError("db.backend.error.sqlite_host_port_not_file_path", map[string]any{"dsn": dsn}) } return dsn, nil } @@ -297,13 +297,13 @@ func (s *SQLiteDB) GetCreateStatement(dbName, tableName string) (string, error) return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("未找到建表语句") + return "", localizedDatabaseRuntimeError("db.backend.error.create_table_statement_not_found", nil) } func (s *SQLiteDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(v string) string { return strings.ReplaceAll(v, "'", "''") } @@ -394,7 +394,7 @@ func (s *SQLiteDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefi func (s *SQLiteDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(v string) string { return strings.ReplaceAll(v, "'", "''") } @@ -485,7 +485,7 @@ func (s *SQLiteDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefin func (s *SQLiteDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(v string) string { return strings.ReplaceAll(v, "'", "''") } @@ -559,7 +559,7 @@ func (s *SQLiteDB) GetForeignKeys(dbName, tableName string) ([]connection.Foreig func (s *SQLiteDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(v string) string { return strings.ReplaceAll(v, "'", "''") } diff --git a/internal/db/sqlserver_impl.go b/internal/db/sqlserver_impl.go index 34839d7..0ebce39 100644 --- a/internal/db/sqlserver_impl.go +++ b/internal/db/sqlserver_impl.go @@ -174,13 +174,13 @@ func (s *SqlServerDB) Connect(config connection.ConnectionConfig) error { db, err := sql.Open("sqlserver", dsn) if err != nil { - return fmt.Errorf("打开数据库连接失败:%w", err) + return wrapDatabaseConnectionOpenError(err) } s.conn = db s.pingTimeout = getConnectTimeout(config) if err := s.Ping(); err != nil { - return fmt.Errorf("连接建立后验证失败:%w", err) + return wrapDatabaseConnectionVerifyError(err) } return nil } @@ -500,7 +500,7 @@ func (s *SqlServerDB) GetColumns(dbName, tableName string) ([]connection.ColumnD } if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -612,7 +612,7 @@ func (s *SqlServerDB) GetIndexes(dbName, tableName string) ([]connection.IndexDe } if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -693,7 +693,7 @@ func (s *SqlServerDB) GetForeignKeys(dbName, tableName string) ([]connection.For } if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -751,7 +751,7 @@ func (s *SqlServerDB) GetTriggers(dbName, tableName string) ([]connection.Trigge } if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } diff --git a/internal/db/sqlserver_impl_test.go b/internal/db/sqlserver_impl_test.go index bebe085..3d62ce4 100644 --- a/internal/db/sqlserver_impl_test.go +++ b/internal/db/sqlserver_impl_test.go @@ -4,9 +4,15 @@ package db import ( "errors" + "os" + "strings" "testing" + + "GoNavi-Wails/shared/i18n" ) +var rawSQLServerTableNameRequiredText = string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) + type fakeSQLServerExecResult struct { affected int64 rowErr error @@ -65,3 +71,76 @@ func TestSQLServerRowsAffectedDoesNotHideDMLRowsAffectedErrors(t *testing.T) { t.Fatalf("expected rows affected error to propagate for DML, got %v", err) } } + +func TestSQLServerMetadataErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + sqlServer := &SqlServerDB{} + tests := []struct { + name string + call func() error + }{ + { + name: "columns table name required", + call: func() error { + _, err := sqlServer.GetColumns("", " ") + return err + }, + }, + { + name: "indexes table name required", + call: func() error { + _, err := sqlServer.GetIndexes("", " ") + return err + }, + }, + { + name: "foreign keys table name required", + call: func() error { + _, err := sqlServer.GetForeignKeys("", " ") + return err + }, + }, + { + name: "triggers table name required", + call: func() error { + _, err := sqlServer.GetTriggers("", " ") + return err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected SQL Server metadata call to fail") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawSQLServerTableNameRequiredText) { + t.Fatalf("expected no raw Chinese SQL Server metadata text, got %q", err.Error()) + } + }) + } +} + +func TestSQLServerMetadataErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("sqlserver_impl.go") + if err != nil { + t.Fatalf("read sqlserver_impl.go: %v", err) + } + source := string(sourceBytes) + rawMessage := `fmt.Errorf("` + rawSQLServerTableNameRequiredText + `")` + + if strings.Contains(source, rawMessage) { + t.Fatalf("sqlserver_impl.go still contains raw SQL Server metadata text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.table_name_required") { + t.Fatal("sqlserver_impl.go does not reference db.backend.error.table_name_required") + } +} diff --git a/internal/db/tdengine_applychanges_test.go b/internal/db/tdengine_applychanges_test.go index 8a61f21..4e77b7c 100644 --- a/internal/db/tdengine_applychanges_test.go +++ b/internal/db/tdengine_applychanges_test.go @@ -8,12 +8,14 @@ import ( "database/sql/driver" "fmt" "io" + "os" "reflect" "strings" "sync" "testing" "GoNavi-Wails/internal/connection" + "GoNavi-Wails/shared/i18n" ) const tdengineRecordingDriverName = "gonavi_tdengine_recording" @@ -220,6 +222,148 @@ func TestTDengineApplyChanges_RejectsMixedUpdatesWithoutPartialWrite(t *testing. } } +func rawTDengineConnectionNotOpenText() string { + return string([]rune{0x8fde, 0x63a5, 0x672a, 0x6253, 0x5f00}) +} + +func rawTDengineTableNameRequiredText() string { + return string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) +} + +func rawTDengineApplyChangesInsertOnlyText() string { + return string([]rune{ + 0x0054, 0x0044, 0x0065, 0x006e, 0x0067, 0x0069, 0x006e, 0x0065, 0x0020, + 0x76ee, 0x6807, 0x7aef, 0x5f53, 0x524d, 0x4ec5, 0x652f, 0x6301, + 0x0020, 0x0049, 0x004e, 0x0053, 0x0045, 0x0052, 0x0054, 0x0020, + 0x5199, 0x5165, 0xff0c, 0x6682, 0x4e0d, 0x652f, 0x6301, 0x0020, + 0x0055, 0x0050, 0x0044, 0x0041, 0x0054, 0x0045, 0x002f, 0x0044, + 0x0045, 0x004c, 0x0045, 0x0054, 0x0045, 0x0020, 0x5dee, 0x5f02, + 0x540c, 0x6b65, 0xff0c, 0x8bf7, 0x6539, 0x7528, 0x4ec5, 0x63d2, + 0x5165, 0x6216, 0x5168, 0x91cf, 0x8986, 0x76d6, 0x6a21, 0x5f0f, + }) +} + +func tdengineApplyChangesI18nKeys() []string { + return []string{ + "db.backend.error.connection_not_open", + "db.backend.error.table_name_required", + "db.backend.error.tdengine_apply_changes_insert_only", + } +} + +func TestTDengineApplyChangesErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + t.Run("connection not open", func(t *testing.T) { + td := &TDengineDB{} + err := td.ApplyChanges("metrics", connection.ChangeSet{}) + if err == nil { + t.Fatal("expected connection-not-open error") + } + if err.Error() != "Connection is not open" { + t.Fatalf("expected English connection-not-open error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawTDengineConnectionNotOpenText()) { + t.Fatalf("expected no raw Chinese connection-not-open text, got %q", err.Error()) + } + }) + + t.Run("table name required", func(t *testing.T) { + dbConn, _ := openTDengineRecordingDB(t) + td := &TDengineDB{conn: dbConn} + err := td.ApplyChanges(" ", connection.ChangeSet{}) + if err == nil { + t.Fatal("expected table-name-required error") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawTDengineTableNameRequiredText()) { + t.Fatalf("expected no raw Chinese table-name-required text, got %q", err.Error()) + } + }) + + t.Run("update delete unsupported", func(t *testing.T) { + dbConn, state := openTDengineRecordingDB(t) + td := &TDengineDB{conn: dbConn} + changes := connection.ChangeSet{ + Deletes: []map[string]interface{}{ + {"ts": "2026-03-09 10:00:00"}, + }, + } + + err := td.ApplyChanges("metrics", changes) + if err == nil { + t.Fatal("expected TDengine insert-only error") + } + want := "TDengine targets currently support only INSERT writes; UPDATE/DELETE differences are not supported by ApplyChanges" + if err.Error() != want { + t.Fatalf("expected %q, got %q", want, err.Error()) + } + if strings.Contains(err.Error(), rawTDengineApplyChangesInsertOnlyText()) { + t.Fatalf("expected no raw Chinese insert-only text, got %q", err.Error()) + } + if queries := state.snapshotQueries(); len(queries) != 0 { + t.Fatalf("expected no SQL execution after insert-only rejection, got %#v", queries) + } + }) +} + +func TestTDengineApplyChangesErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("tdengine_impl.go") + if err != nil { + t.Fatalf("read tdengine_impl.go: %v", err) + } + source := string(sourceBytes) + start := strings.Index(source, "func (t *TDengineDB) ApplyChanges") + if start < 0 { + t.Fatal("TDengine ApplyChanges function not found") + } + end := strings.Index(source[start:], "func execTDengineInsertBatches") + if end < 0 { + t.Fatal("TDengine ApplyChanges function end marker not found") + } + applyChangesSource := source[start : start+end] + + for _, rawMessage := range []string{ + `fmt.Errorf("` + rawTDengineConnectionNotOpenText() + `")`, + `fmt.Errorf("` + rawTDengineTableNameRequiredText() + `")`, + `fmt.Errorf("` + rawTDengineApplyChangesInsertOnlyText() + `")`, + } { + if strings.Contains(applyChangesSource, rawMessage) { + t.Fatalf("TDengine ApplyChanges still contains raw text %q", rawMessage) + } + } + for _, key := range tdengineApplyChangesI18nKeys() { + if !strings.Contains(applyChangesSource, key) { + t.Fatalf("TDengine ApplyChanges does not reference i18n key %q", key) + } + } +} + +func TestTDengineApplyChangesCatalogKeysExist(t *testing.T) { + catalogs, err := i18n.LoadCatalogs() + if err != nil { + t.Fatalf("LoadCatalogs() error = %v", err) + } + + for _, language := range i18n.SupportedLanguages() { + catalog := catalogs[language] + for _, key := range tdengineApplyChangesI18nKeys() { + value := strings.TrimSpace(catalog[key]) + if value == "" { + t.Fatalf("%s catalog missing TDengine ApplyChanges key %q", language, key) + } + if strings.Contains(value, "{{") || strings.Contains(value, "}}") { + t.Fatalf("%s catalog key %q should not use placeholders, got %q", language, key, value) + } + } + } +} + func TestTDengineGetTablesIncludesSuperTables(t *testing.T) { t.Parallel() @@ -373,3 +517,44 @@ func TestTDengineGetCreateStatementFallsBackToLegacySyntax(t *testing.T) { t.Fatalf("unexpected query sequence: got=%v want=%v", queries, wantQueries) } } + +func TestTDengineGetCreateStatementNotFoundUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + dbConn, _ := openTDengineRecordingDB(t) + td := &TDengineDB{conn: dbConn} + + _, err := td.GetCreateStatement("metrics", "meters") + if err == nil { + t.Fatal("expected CREATE TABLE not found error") + } + + want := "The CREATE TABLE statement was not found" + if err.Error() != want { + t.Fatalf("expected %q, got %q", want, err.Error()) + } + rawNotFoundText := "\u672a\u627e\u5230\u5efa\u8868\u8bed\u53e5" + if strings.Contains(err.Error(), rawNotFoundText) { + t.Fatalf("expected no raw Chinese CREATE TABLE not found text, got %q", err.Error()) + } +} + +func TestTDengineGetCreateStatementSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("tdengine_impl.go") + if err != nil { + t.Fatalf("read tdengine_impl.go: %v", err) + } + source := string(sourceBytes) + + rawNotFoundText := "\u672a\u627e\u5230\u5efa\u8868\u8bed\u53e5" + rawNotFoundSnippet := `fmt.Errorf("` + rawNotFoundText + `")` + if strings.Contains(source, rawNotFoundSnippet) { + t.Fatalf("TDengine GetCreateStatement still contains raw CREATE TABLE not found text") + } + if !strings.Contains(source, "db.backend.error.create_table_statement_not_found") { + t.Fatal("TDengine GetCreateStatement does not reference db.backend.error.create_table_statement_not_found") + } +} diff --git a/internal/db/tdengine_i18n_test.go b/internal/db/tdengine_i18n_test.go new file mode 100644 index 0000000..bfffb3d --- /dev/null +++ b/internal/db/tdengine_i18n_test.go @@ -0,0 +1,49 @@ +//go:build gonavi_full_drivers || gonavi_tdengine_driver + +package db + +import ( + "os" + "strings" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +var rawTDengineAllColumnsDatabaseRequiredText = string([]rune{0x83b7, 0x53d6, 0x5168, 0x90e8, 0x5217, 0x4fe1, 0x606f, 0x9700, 0x8981, 0x6307, 0x5b9a, 0x6570, 0x636e, 0x5e93, 0x540d, 0x79f0}) + +func TestTDengineGetAllColumnsDatabaseRequiredUsesCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + tdengineDB := &TDengineDB{} + + _, err := tdengineDB.GetAllColumns(" ") + if err == nil { + t.Fatal("expected TDengine GetAllColumns to fail") + } + if err.Error() != "Database name is required" { + t.Fatalf("expected English database-name error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawTDengineAllColumnsDatabaseRequiredText) { + t.Fatalf("expected no raw Chinese database-name text, got %q", err.Error()) + } +} + +func TestTDengineGetAllColumnsDatabaseRequiredSourceUsesI18nKey(t *testing.T) { + sourceBytes, err := os.ReadFile("tdengine_impl.go") + if err != nil { + t.Fatalf("read tdengine_impl.go: %v", err) + } + source := string(sourceBytes) + + rawMessage := `fmt.Errorf("` + rawTDengineAllColumnsDatabaseRequiredText + `")` + if strings.Contains(source, rawMessage) { + t.Fatalf("tdengine_impl.go still contains raw database-name text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.database_name_required") { + t.Fatal("tdengine_impl.go does not reference db.backend.error.database_name_required") + } +} diff --git a/internal/db/tdengine_impl.go b/internal/db/tdengine_impl.go index 69782e8..8f9ec94 100644 --- a/internal/db/tdengine_impl.go +++ b/internal/db/tdengine_impl.go @@ -295,7 +295,7 @@ func (t *TDengineDB) GetCreateStatement(dbName, tableName string) (string, error if lastErr != nil { return "", lastErr } - return "", fmt.Errorf("未找到建表语句") + return "", errors.New(localizedDriverRuntimeText("db.backend.error.create_table_statement_not_found", nil)) } func (t *TDengineDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -362,7 +362,7 @@ func (t *TDengineDB) GetColumns(dbName, tableName string) ([]connection.ColumnDe func (t *TDengineDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) { if strings.TrimSpace(dbName) == "" { - return nil, fmt.Errorf("获取全部列信息需要指定数据库名称") + return nil, localizedDatabaseRuntimeError("db.backend.error.database_name_required", nil) } tables, err := t.GetTables(dbName) @@ -403,13 +403,13 @@ func (t *TDengineDB) GetTriggers(dbName, tableName string) ([]connection.Trigger func (t *TDengineDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if t.conn == nil { - return fmt.Errorf("连接未打开") + return localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil) } if strings.TrimSpace(tableName) == "" { - return fmt.Errorf("表名不能为空") + return localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } if len(changes.Updates) > 0 || len(changes.Deletes) > 0 { - return fmt.Errorf("TDengine 目标端当前仅支持 INSERT 写入,暂不支持 UPDATE/DELETE 差异同步,请改用仅插入或全量覆盖模式") + return localizedDatabaseRuntimeError("db.backend.error.tdengine_apply_changes_insert_only", nil) } qualifiedTable := quoteTDengineTable("", tableName) diff --git a/internal/db/vastbase_i18n_test.go b/internal/db/vastbase_i18n_test.go new file mode 100644 index 0000000..e09eb8f --- /dev/null +++ b/internal/db/vastbase_i18n_test.go @@ -0,0 +1,86 @@ +//go:build gonavi_full_drivers || gonavi_vastbase_driver + +package db + +import ( + "os" + "strings" + "testing" + + "GoNavi-Wails/shared/i18n" +) + +var rawVastbaseTableNameRequiredText = string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a}) + +func TestVastbaseMetadataErrorsUseCurrentLanguage(t *testing.T) { + SetBackendLanguage(i18n.LanguageEnUS) + t.Cleanup(func() { + SetBackendLanguage(i18n.LanguageZhCN) + }) + + vastbase := &VastbaseDB{} + tests := []struct { + name string + call func() error + }{ + { + name: "columns table name required", + call: func() error { + _, err := vastbase.GetColumns("", " ") + return err + }, + }, + { + name: "indexes table name required", + call: func() error { + _, err := vastbase.GetIndexes("", " ") + return err + }, + }, + { + name: "foreign keys table name required", + call: func() error { + _, err := vastbase.GetForeignKeys("", " ") + return err + }, + }, + { + name: "triggers table name required", + call: func() error { + _, err := vastbase.GetTriggers("", " ") + return err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.call() + if err == nil { + t.Fatal("expected Vastbase metadata call to fail") + } + if err.Error() != "Table name is required" { + t.Fatalf("expected English table-name-required error, got %q", err.Error()) + } + if strings.Contains(err.Error(), rawVastbaseTableNameRequiredText) { + t.Fatalf("expected no raw Chinese Vastbase metadata text, got %q", err.Error()) + } + }) + } +} + +func TestVastbaseMetadataErrorSourcesUseI18nKeys(t *testing.T) { + sourceBytes, err := os.ReadFile("vastbase_impl.go") + if err != nil { + t.Fatalf("read vastbase_impl.go: %v", err) + } + source := string(sourceBytes) + rawMessage := `fmt.Errorf("` + rawVastbaseTableNameRequiredText + `")` + + if strings.Contains(source, rawMessage) { + t.Fatalf("vastbase_impl.go still contains raw Vastbase metadata text %q", rawMessage) + } + if !strings.Contains(source, "db.backend.error.table_name_required") { + t.Fatal("vastbase_impl.go does not reference db.backend.error.table_name_required") + } +} diff --git a/internal/db/vastbase_impl.go b/internal/db/vastbase_impl.go index a1ece0a..9611192 100644 --- a/internal/db/vastbase_impl.go +++ b/internal/db/vastbase_impl.go @@ -251,7 +251,7 @@ func (v *VastbaseDB) GetCreateStatement(dbName, tableName string) (string, error func (v *VastbaseDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { schema, table := normalizePGLikeMetadataTable(dbName, tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } data, _, err := v.Query(buildPGLikeColumnsMetadataQuery(schema, table)) @@ -265,7 +265,7 @@ func (v *VastbaseDB) GetColumns(dbName, tableName string) ([]connection.ColumnDe func (v *VastbaseDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { schema, table := normalizePGLikeMetadataTable(dbName, tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } data, _, err := v.Query(buildPGLikeIndexesMetadataQuery(schema, table)) @@ -283,7 +283,7 @@ func (v *VastbaseDB) GetForeignKeys(dbName, tableName string) ([]connection.Fore } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -343,7 +343,7 @@ func (v *VastbaseDB) GetTriggers(dbName, tableName string) ([]connection.Trigger } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("表名不能为空") + return nil, localizedDatabaseRuntimeError("db.backend.error.table_name_required", nil) } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } diff --git a/shared/i18n/de-DE.json b/shared/i18n/de-DE.json index 77a4ccc..c270eac 100644 --- a/shared/i18n/de-DE.json +++ b/shared/i18n/de-DE.json @@ -15,6 +15,19 @@ "common.success": "Erfolg", "common.unknown": "Unbekannt", "common.warning": "Warnung", + "dev.perf_data_grid.title": "DataGrid-Leistungsreproduktion", + "dev.perf_data_grid.ui_version.legacy": "Alte UI", + "dev.perf_data_grid.ui_version.v2": "Neue UI", + "dev.perf_data_grid.ui_version.legacy_short": "Alt", + "dev.perf_data_grid.ui_version.v2_short": "Neu", + "dev.perf_data_grid.rows": "Zeilen", + "dev.perf_data_grid.columns": "Spalten", + "dev.perf_data_grid.density.comfortable": "Standard", + "dev.perf_data_grid.density.standard": "Kompakt", + "dev.perf_data_grid.density.compact": "Sehr kompakt", + "dev.perf_data_grid.trigger_layout": "Layout neu berechnen", + "dev.perf_data_grid.notice.message": "Diese Seite dient nur der Entwicklungsmessung der Scroll-Leistung", + "dev.perf_data_grid.notice.description": "Aktuelle {{uiVersion}} UI, {{rows}} Zeilen / {{columns}} Spalten. Messe vertikales, horizontales und Shift+Mausrad-Scrollen direkt im Tabellenbereich.", "connection.sidebar.group.untitled": "Unbenannte Gruppe", "connection.sidebar.group.meta": "{{count}} Verbindungen · Verbindungsgruppe", "connection.sidebar.group.badge": "Gruppe", @@ -104,6 +117,9 @@ "table_overview.message.unpinned": "Anheften der Tabelle aufgehoben", "table_overview.message.copy_structure_success": "Tabellenstruktur in die Zwischenablage kopiert", "table_overview.message.copy_structure_failed": "Tabellenstruktur konnte nicht kopiert werden: {{detail}}", + "table_overview.message.copy_table_name_empty": "Der Tabellenname ist leer und kann nicht kopiert werden", + "table_overview.message.copy_table_name_success": "Tabellenname wurde in die Zwischenablage kopiert", + "table_overview.message.copy_table_name_failed": "Tabellenname konnte nicht kopiert werden: {{detail}}", "table_overview.message.exporting_table_format": "{{table}} wird als {{format}} exportiert...", "table_overview.message.export_success": "Export erfolgreich", "table_overview.message.export_failed": "Export fehlgeschlagen: {{detail}}", @@ -119,6 +135,8 @@ "table_overview.message.rename_table_success": "Tabelle umbenannt", "table_overview.message.rename_table_failed": "Tabelle konnte nicht umbenannt werden: {{detail}}", "table_overview.message.unknown_error": "Unbekannter Fehler", + "table_overview.tab.design_table_title": "Tabelle entwerfen ({{table}})", + "table_overview.tab.table_structure_title": "Tabellenstruktur ({{table}})", "table_overview.modal.delete_table.title": "Tabelle löschen", "table_overview.modal.delete_table.content": "Tabelle \"{{table}}\" löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "table_overview.modal.rename_table.title": "Tabelle umbenennen", @@ -150,6 +168,8 @@ "table_overview.action.show_more": "Mehr Tabellen anzeigen ({{count}} verbleibend)", "table_overview.menu.new_query": "Neue Abfrage", "table_overview.menu.design_table": "Tabelle entwerfen", + "table_overview.menu.table_structure": "Tabellenstruktur", + "table_overview.menu.copy_table_name": "Tabellennamen kopieren", "table_overview.menu.copy_structure": "Tabellenstruktur kopieren", "table_overview.menu.backup_table_sql": "Tabelle sichern (SQL)", "table_overview.menu.rename_table": "Tabelle umbenennen", @@ -213,6 +233,18 @@ "app.data_root.backend.error.open_directory_failed": "Datenverzeichnis konnte nicht geöffnet werden: {{detail}}", "app.data_root.backend.error.open_directory_unsupported": "Das Öffnen von Verzeichnissen wird auf dieser Plattform nicht unterstützt: {{platform}}", "app.data_root.backend.error.read_source_failed": "Quelldaten konnten nicht gelesen werden ({{entry}}): {{detail}}", + "app.data_root.backend.error.resolve_source_failed": "Quell-Datenverzeichnis konnte nicht aufgelöst werden: {{detail}}", + "app.data_root.backend.error.resolve_target_failed": "Ziel-Datenverzeichnis konnte nicht aufgelöst werden: {{detail}}", + "app.data_root.backend.error.target_inside_source": "Das Ziel-Datenverzeichnis darf nicht innerhalb des Quellverzeichnisses liegen", + "app.data_root.backend.error.read_source_root_failed": "Quell-Datenverzeichnis konnte nicht gelesen werden: {{detail}}", + "app.data_root.backend.error.read_migrated_security_update_state_failed": "Migrierter Status des Sicherheitsupdates konnte nicht gelesen werden: {{detail}}", + "app.data_root.backend.error.write_migrated_security_update_state_failed": "Migrierter Status des Sicherheitsupdates konnte nicht geschrieben werden: {{detail}}", + "app.data_root.backend.error.read_migrated_security_update_manifest_failed": "Migriertes Manifest der Sicherheitsupdate-Sicherung konnte nicht gelesen werden: {{detail}}", + "app.data_root.backend.error.parse_migrated_security_update_manifest_failed": "Migriertes Manifest der Sicherheitsupdate-Sicherung konnte nicht verarbeitet werden: {{detail}}", + "app.data_root.backend.error.write_migrated_security_update_manifest_failed": "Migriertes Manifest der Sicherheitsupdate-Sicherung konnte nicht geschrieben werden: {{detail}}", + "app.data_root.backend.error.read_migrated_security_update_result_failed": "Migriertes Ergebnis des Sicherheitsupdates konnte nicht gelesen werden: {{detail}}", + "app.data_root.backend.error.parse_migrated_security_update_result_failed": "Migriertes Ergebnis des Sicherheitsupdates konnte nicht verarbeitet werden: {{detail}}", + "app.data_root.backend.error.write_migrated_security_update_result_failed": "Migriertes Ergebnis des Sicherheitsupdates konnte nicht geschrieben werden: {{detail}}", "app.data_root.backend.message.migrated_restart": "Daten wurden migriert und auf das neue Verzeichnis umgestellt. Starten Sie die App neu, um alle Module vollständig umzustellen.", "app.data_root.backend.message.opened": "Datenverzeichnis wurde geöffnet", "app.data_root.backend.message.unchanged": "Datenverzeichnis ist unverändert", @@ -235,6 +267,7 @@ "app.proxy.enable": "Globalen Proxy aktivieren", "app.proxy.host": "Proxy-Host", "app.proxy.host_placeholder": "Beispiel: 127.0.0.1", + "app.proxy.message.config_applied": "Die globale Proxy-Konfiguration wurde angewendet", "app.proxy.message.invalid_enabled": "Der globale Proxy ist aktiviert, aber Host oder Port sind ungültig. Er wird derzeit als deaktiviert behandelt.", "app.proxy.message.save_failed": "Globale Proxy-Konfiguration fehlgeschlagen: {{error}}", "app.proxy.password_optional": "Passwort (optional)", @@ -371,6 +404,8 @@ "app.shortcuts.action.saveQuery.label": "Abfrage speichern", "app.shortcuts.action.selectCurrentStatement.description": "SQL-Anweisung an der Cursorposition im Abfrageeditor auswählen", "app.shortcuts.action.selectCurrentStatement.label": "Aktuelle Anweisung auswählen", + "app.shortcuts.action.toggleQueryResultsPanel.description": "Ergebnisbereich unter dem Abfrageeditor ein- oder ausblenden", + "app.shortcuts.action.toggleQueryResultsPanel.label": "Ergebnisbereich umschalten", "app.shortcuts.action.sendAIChatMessage.description": "Aktuelle Nachricht im AI-Eingabefeld senden; Shift+Enter fügt immer einen Zeilenumbruch ein", "app.shortcuts.action.sendAIChatMessage.label": "AI-Chat senden", "app.shortcuts.action.switchToNextTab.description": "In geöffneten Tabs nach rechts wechseln", @@ -570,8 +605,35 @@ "app.browser_mock.mcp_client.claude_code.not_detected": "Keine GoNavi MCP-Konfiguration auf Benutzerebene für Claude Code erkannt", "app.browser_mock.mcp_client.codex.installed": "Die MCP-Konfiguration auf Benutzerebene für Codex wurde geschrieben. Starten Sie Codex CLI oder die Desktop-App neu, um GoNavi zu sehen.", "app.browser_mock.mcp_client.codex.path_mismatch": "In Codex wurde ein GoNavi MCP-Eintrag erkannt, der nicht zum aktuellen GoNavi-Installationspfad passt. Eine Aktualisierung wird empfohlen.", + "ai.service.mcp_client.claude_code.install_success": "Die MCP-Konfiguration auf Benutzerebene für Claude Code wurde geschrieben. Starten Sie Claude CLI neu; GoNavi erscheint dann unter User MCPs in /mcp.", + "ai.service.mcp_client.codex.install_success": "Die MCP-Konfiguration auf Benutzerebene für Codex wurde geschrieben. Starten Sie Codex CLI oder die Desktop-App neu, um GoNavi zu sehen.", + "ai.service.mcp_client.claude_code.config_path_failed": "Claude Code-Konfiguration konnte nicht gefunden werden: {{detail}}", + "ai.service.mcp_client.codex.config_path_failed": "Codex-Konfiguration konnte nicht gefunden werden: {{detail}}", + "ai.service.mcp_client.executable_path_failed": "Die aktuelle GoNavi-Programmdatei konnte nicht gefunden werden: {{detail}}", + "ai.service.mcp_client.user_home_dir_unavailable": "Das aktuelle Benutzer-Home-Verzeichnis konnte nicht ermittelt werden", + "ai.service.mcp_client.executable_path_empty": "Der Pfad zur aktuellen GoNavi-Programmdatei ist leer", + "ai.service.mcp_client.claude_code.config_format_invalid": "Das Format der Claude Code-Konfiguration ist ungültig: {{path}} muss {{expected}} sein", + "ai.service.mcp_client.codex.config_format_invalid": "Das Format der Codex-Konfiguration ist ungültig: {{path}} konnte nicht als {{expected}} gelesen werden", + "ai.service.mcp_client.claude_code.config_read_failed": "Claude Code-Konfiguration konnte nicht gelesen werden: {{detail}}", + "ai.service.mcp_client.claude_code.config_parse_failed": "Claude Code-Konfiguration konnte nicht geparst werden: {{detail}}", + "ai.service.mcp_client.claude_code.config_serialize_failed": "Claude Code-Konfiguration konnte nicht serialisiert werden: {{detail}}", + "ai.service.mcp_client.claude_code.config_dir_create_failed": "Claude Code-Konfigurationsverzeichnis konnte nicht erstellt werden: {{detail}}", + "ai.service.mcp_client.claude_code.config_write_failed": "Claude Code-Konfiguration konnte nicht geschrieben werden: {{detail}}", + "ai.service.mcp_client.codex.config_read_failed": "Codex-Konfiguration konnte nicht gelesen werden: {{detail}}", + "ai.service.mcp_client.codex.config_dir_create_failed": "Codex-Konfigurationsverzeichnis konnte nicht erstellt werden: {{detail}}", + "ai.service.mcp_client.codex.config_write_failed": "Codex-Konfiguration konnte nicht geschrieben werden: {{detail}}", + "ai.service.mcp_client.claude_code.status.missing": "Keine GoNavi MCP-Konfiguration auf Benutzerebene für Claude Code erkannt", + "ai.service.mcp_client.codex.status.missing": "Keine GoNavi MCP-Konfiguration auf Benutzerebene für Codex erkannt", + "ai.service.mcp_client.claude_code.status.path_check_failed": "In Claude Code wurde ein GoNavi MCP-Eintrag erkannt, aber die Prüfung des aktuellen GoNavi-Installationspfads ist fehlgeschlagen: {{detail}}", + "ai.service.mcp_client.codex.status.path_check_failed": "In Codex wurde ein GoNavi MCP-Eintrag erkannt, aber die Prüfung des aktuellen GoNavi-Installationspfads ist fehlgeschlagen: {{detail}}", + "ai.service.mcp_client.claude_code.status.connected": "Die GoNavi MCP-Konfiguration auf Benutzerebene für Claude Code wurde erkannt und passt zum aktuellen GoNavi-Installationspfad", + "ai.service.mcp_client.codex.status.connected": "Die GoNavi MCP-Konfiguration auf Benutzerebene für Codex wurde erkannt und passt zum aktuellen GoNavi-Installationspfad", + "ai.service.mcp_client.claude_code.status.path_mismatch": "In Claude Code wurde ein GoNavi MCP-Eintrag erkannt, der nicht zum aktuellen GoNavi-Installationspfad passt. Eine Aktualisierung wird empfohlen.", + "ai.service.mcp_client.codex.status.path_mismatch": "In Codex wurde ein GoNavi MCP-Eintrag erkannt, der nicht zum aktuellen GoNavi-Installationspfad passt. Eine Aktualisierung wird empfohlen.", + "ai.service.mcp_client.remote.status.message": "{{label}} läuft normalerweise in der Cloud oder in einer Remote-Umgebung. Binden Sie es über eine Remote-MCP-Brücke an Windows GoNavi an; Datenbankpasswörter bleiben auf diesem GoNavi-Rechner.", "app.browser_mock.provider.test_failed_detail": "Verbindungstest fehlgeschlagen: {{detail}}", "app.browser_mock.provider.test_success": "Endpunkt-Verbindungstest erfolgreich", + "app.backend.error.reset_webview_zoom_failed": "Zurücksetzen des WebView2-Zooms fehlgeschlagen: {{detail}}", "app.update.action.hide_to_background": "In den Hintergrund ausblenden", "app.update.action.install_update": "Update installieren", "app.update.action.open_install_directory": "Installationsverzeichnis öffnen", @@ -642,6 +704,7 @@ "query.run": "Ausführen", "query.save": "Abfrage speichern", "saved_query.default_name": "Abfrage {{index}}", + "saved_query.error.missing_context": "Der gespeicherten Abfrage fehlt SQL-, Verbindungs- oder Datenbankkontext", "query.stop": "Stoppen", "message_publish_modal.title": "Nachricht testweise senden", "message_publish_modal.title_with_connection": "Nachricht testweise senden · {{connectionName}}", @@ -739,6 +802,7 @@ "connection_modal.field.oceanBaseProtocol.label": "OceanBase-Protokoll", "connection_modal.field.oceanBaseProtocol.help.primary": "Wählen Sie für MySQL-Mandanten MySQL und für Oracle-Mandanten Oracle. GoNavi wählt anhand des Ports automatisch: Für den OB MySQL wire-Port wird die OBClient-Capability-Injektion verwendet (derselbe Pfad wie in Navicat), für den OBProxy Oracle listener-Port Standard-TNS.", "connection_modal.field.oceanBaseProtocol.help.connectionAttributes": "Wenn bei einer Oracle-Mandantenverbindung \"Error 1235\" oder ein OBClient-Handshake-Fehler auftritt, können Sie im Feld \"Verbindungsparameter\" mit {{attributes}} die standardmäßig von GoNavi injizierte OBClient-Capability überschreiben.", + "connection.oceanbase.error.unsupported_protocol": "OceanBase unterstützt nur MySQL/Oracle-Mandantenprotokolle; \"{{value}}\" wird nicht unterstützt. Wechseln Sie zu MySQL oder Oracle.", "connection_modal.field.ssh_host": "SSH-Host", "connection_modal.field.ssh_password": "SSH-Passwort", "connection_modal.field.ssh_user": "SSH-Benutzer", @@ -980,6 +1044,7 @@ "connection_modal.secret.clear_saved_replica_password": "Gespeichertes Replica-Passwort löschen", "connection_modal.secret.clear_saved_ssh_password": "Gespeichertes SSH-Passwort löschen", "connection_modal.secret.clear_saved_tunnel_password": "Gespeichertes HTTP Tunnel-Passwort löschen", + "connection_modal.secret.error.saved_connection_deleted": "Die gespeicherte Verbindung wurde nicht gefunden. Sie wurde möglicherweise gelöscht. Aktualisieren Sie die Ansicht und versuchen Sie es erneut.", "connection_modal.secret.error.saved_connection_missing": "Das gespeicherte Secret für die aktuelle Verbindung wurde nicht gefunden. Geben Sie das Passwort erneut ein, speichern Sie und versuchen Sie es noch einmal.", "connection_modal.secret.error.store_unavailable": "Der sichere Secret-Speicher ist derzeit nicht verfügbar. Prüfen Sie den System-Schlüsselbund oder die Anmeldeinformationsverwaltung und versuchen Sie es erneut.", "connection_modal.secret.saved_mongo_replica_password": "Gespeichertes MongoDB-Replica-Passwort", @@ -1105,7 +1170,9 @@ "connection_modal.step1.group.nosql": "NoSQL-Datenbanken", "connection_modal.step1.group.relational": "Relationale Datenbanken", "connection_modal.step1.group.domestic": "Inländische Datenbanken", + "connection_modal.step1.group.vector": "Vektordatenbanken", "connection_modal.step1.group.timeseries": "Zeitreihendatenbanken", + "connection_modal.step1.group.message_queue": "Nachrichtenwarteschlangen", "connection_modal.step1.group.other": "Andere", "connection_modal.group.time_series": "Zeitreihendatenbanken", "connection_modal.group.other": "Andere", @@ -1119,6 +1186,18 @@ "connection_modal.layout.custom": "Benutzerdefinierte Treiberverbindung", "connection_modal.layout.jvm": "JVM-Laufzeitverbindung", "connection_modal.layout.generic_sql": "Generische SQL-Verbindung", + "connection_modal.layoutKind.mysqlCompatible": "MySQL-kompatibel", + "connection_modal.layoutKind.mongodb": "MongoDB", + "connection_modal.layoutKind.redis": "Redis", + "connection_modal.layoutKind.postgresCompatible": "PostgreSQL-kompatibel", + "connection_modal.layoutKind.oracle": "Oracle", + "connection_modal.layoutKind.file": "Dateibasierte Datenbanken", + "connection_modal.layoutKind.search": "Suchmaschinen", + "connection_modal.layoutKind.vector": "Vektordatenbanken", + "connection_modal.layoutKind.timeseries": "Zeitreihendatenbanken", + "connection_modal.layoutKind.custom": "Benutzerdefinierter Treiber", + "connection_modal.layoutKind.jvm": "JVM-Laufzeit", + "connection_modal.layoutKind.genericSql": "Standard-SQL", "connection_modal.db_type_hint.custom": "Mit einem benutzerdefinierten Treiber und DSN verbinden.", "connection_modal.db_type_hint.redis": "Mit Redis Standalone oder Redis Cluster verbinden.", "connection_modal.db_type_hint.mongodb": "Mit MongoDB Standalone, Replica Set oder SRV-Adressen verbinden.", @@ -1128,7 +1207,11 @@ "connection_modal.step1.hint.custom": "Benutzerdefinierter Treiber und DSN", "connection_modal.step1.hint.redis": "Einzelknoten / Cluster", "connection_modal.step1.hint.mongodb": "Einzelknoten / Replikatset", + "connection_modal.step1.hint.elasticsearch": "Index-Browsing, Mapping-Prüfung, JSON DSL und query_string-Abfragen", + "connection_modal.step1.hint.chroma": "Collection-Browsing, Vektorsuche und Metadatenfilter", + "connection_modal.step1.hint.qdrant": "Collection-Browsing, Vektorsuche und Payload-Filter", "connection_modal.step1.hint.oceanBase": "MySQL / Oracle-Mandant", + "connection_modal.step1.hint.goldendb": "MySQL-kompatibel / verteilte Transaktionen", "connection_modal.step1.hint.file": "Lokale Dateiverbindung", "connection_modal.step1.hint.standard": "Standard-Verbindungskonfiguration", "connection_modal.step.select_source": "Datenquelle auswählen", @@ -1248,12 +1331,19 @@ "sidebar.message.database_export_success": "{{database}} wurde exportiert.", "sidebar.message.database_export_failed": "{{database}} konnte nicht exportiert werden: {{error}}", "sidebar.message.connection_config_not_found": "Verbindungskonfiguration wurde nicht gefunden.", + "sidebar.message.ai_table_context_missing": "Für die aktuelle Tabelle fehlt der Verbindungskontext; sie kann nicht an AI gesendet werden.", "sidebar.sql_file_exec.title": "Externe SQL-Datei ausführen", "sidebar.message.read_file_failed": "Datei konnte nicht gelesen werden: {{error}}", "sidebar.message.select_connection_or_database_first": "Wählen Sie zuerst eine Verbindung oder Datenbank aus.", "sidebar.message.schema_create_unsupported": "Diese Datenbank unterstützt das Erstellen von schema nicht.", "sidebar.message.schema_target_missing": "Wählen Sie eine Datenbank für das Erstellen von schema aus.", "sidebar.message.schema_created": "schema wurde erstellt.", + "sidebar.message.schema_edit_unsupported": "Der aktuelle Knoten unterstützt das Bearbeiten von schema über diesen Einstieg nicht.", + "sidebar.message.schema_target_edit_missing": "Ziel-schema für die Bearbeitung nicht gefunden.", + "sidebar.message.schema_name_unchanged": "Der schema-Name ist unverändert.", + "sidebar.message.schema_renamed": "schema umbenannt.", + "sidebar.message.schema_target_delete_missing": "Ziel-schema zum Löschen nicht gefunden.", + "sidebar.message.schema_deleted": "schema gelöscht.", "sidebar.message.operation_create_failed": "Erstellen fehlgeschlagen: {{error}}", "sidebar.sql_file.default_name": "SQL-Datei", "sidebar.message.sql_file_context_incomplete": "SQL-Dateikontext unvollständig.", @@ -1294,6 +1384,8 @@ "sidebar.message.create_failed": "Erstellen fehlgeschlagen: {{error}}", "sidebar.modal.confirm_delete_database.title": "Datenbank löschen", "sidebar.modal.confirm_delete_database.content": "{{name}} löschen? Dies kann nicht rückgängig gemacht werden.", + "sidebar.modal.confirm_delete_schema.title": "schema löschen", + "sidebar.modal.confirm_delete_schema.content": "schema {{name}} löschen? Das schema und alle enthaltenen Objekte werden gelöscht. Dies kann nicht rückgängig gemacht werden.", "sidebar.modal.confirm_delete_sql_file.title": "SQL-Datei löschen", "sidebar.modal.confirm_delete_sql_file.content": "\"{{name}}\" löschen? Dadurch wird die lokale Datei vom Datenträger gelöscht und kann nicht wiederhergestellt werden.", "sidebar.modal.confirm_delete_sql_directory.title": "SQL-Verzeichnis löschen", @@ -1315,7 +1407,7 @@ "sidebar.search.multi_select_supported": "Mehrere Bereiche unterstützt", "sidebar.search.scope_hint": "Im intelligenten Modus werden je nach Kontext Namen, Hosts, Datenbanken und Objekte durchsucht.", "sidebar.modal.confirm_delete.title": "Löschen bestätigen", - "sidebar.modal.confirm_delete_tag.content": "{{name}} löschen?", + "sidebar.modal.confirm_delete_tag.content": "Gruppe \"{{name}}\" löschen? Die enthaltenen Verbindungen werden nicht gelöscht.", "sidebar.menu.edit_connection": "Verbindung bearbeiten", "sidebar.menu.delete_connection": "Verbindung löschen", "sidebar.modal.confirm_delete_connection.content": "{{name}} löschen?", @@ -1332,6 +1424,30 @@ "sidebar.command_search.reset_filter": "Seitenleistenfilter zurücksetzen", "sidebar.command_search.no_synced_filter": "Kein synchronisierter Seitenleistenfilter", "sidebar.command_search.no_filter_content": "Kein Filtertext", + "sidebar.command_search.recent_sql_fallback": "SQL-Eintrag", + "sidebar.command_search.action.ask_ai.title": "AI fragen", + "sidebar.command_search.action.new_query.meta": "Einen neuen SQL-Editor-Tab öffnen", + "sidebar.command_search.action.new_connection.title": "Neue Datenquelle", + "sidebar.command_search.action.new_connection.meta": "Eine Datenbank-, Runtime- oder andere Datenquellenverbindung erstellen", + "sidebar.command_search.action.open_ai.title": "AI-Datenanalyse öffnen", + "sidebar.command_search.action.open_ai.meta": "AI analysiert den aktuellen Datenbankkontext", + "sidebar.command_search.action.open_sql_log.title": "SQL-Ausführungslog anzeigen", + "sidebar.command_search.action.open_sql_log.meta": "Das Panel mit den letzten Ausführungen öffnen", + "sidebar.command_search.empty.ai": "Gib nach \"?\" eine Frage ein und drücke Enter, um sie an das AI-Panel zu senden.", + "sidebar.command_search.empty.object": "Keine passenden Tabellen, Sichten oder materialisierten Sichten.", + "sidebar.command_search.empty.default": "Keine Treffer. Gib @Tabelle ein, um nur Tabellenobjekte zu suchen, oder ?Frage, um AI zu fragen.", + "sidebar.command_search.section.goto": "Gehe zu", + "sidebar.command_search.section.ai": "AI · Fragen", + "sidebar.command_search.section.actions": "Aktionen", + "sidebar.command_search.section.recent": "Letzte Abfragen", + "sidebar.command_search.footer.navigate": "Navigieren", + "sidebar.command_search.footer.select": "Auswählen", + "sidebar.command_search.footer.object_only": "Nur Tabellenobjekte", + "sidebar.command_search.footer.ask_ai": "An AI senden", + "sidebar.ai_prompt.explain.intro": "Erkläre die Struktur und fachliche Bedeutung der Tabelle {{table}}.", + "sidebar.ai_prompt.explain.detail": "Gehe besonders auf Feldbedeutungen, Primärschlüssel/Indizes, mögliche Beziehungen, typische Abfrageszenarien und Risiken ein.", + "sidebar.ai_prompt.query.intro": "Erstelle 3 häufig genutzte SQL-Abfragen für die Tabelle {{table}}.", + "sidebar.ai_prompt.query.detail": "Enthalten sein sollen: eine Datenvorschau-Abfrage, eine Abfrage mit Filterung nach Schlüsselfeldern und eine Aggregations- oder Statistikabfrage.", "sidebar.command_search.object_kind.all": "Alle", "sidebar.command_search.object_kind.tables": "Tabellen", "sidebar.command_search.object_kind.views": "Sichten", @@ -1361,6 +1477,7 @@ "sidebar.action.batch_tables": "Tabellen stapelweise bearbeiten", "sidebar.action.batch_databases": "Datenbanken stapelweise bearbeiten", "sidebar.action.locate_current_table": "Aktuell geöffnete Tabelle lokalisieren", + "sidebar.action.locate_current_tab": "Aktuellen Tab lokalisieren", "sidebar.action.pin_table": "Tabelle anheften", "sidebar.action.unpin_table": "Anheften aufheben", "sidebar.status.pinned": "Angeheftet", @@ -1400,6 +1517,7 @@ "sidebar.v2_table_group_menu.sort_frequency": "Nutzungshäufigkeit", "sidebar.v2_table_group_menu.meta": "{{database}} · {{count}} Tabellen · nach {{sort}} sortiert", "sidebar.message.locate_current_table_unavailable": "Im aktuellen Tab gibt es keine lokalisierbare Tabelle", + "sidebar.message.locate_current_tab_unavailable": "Im aktuellen Tab gibt es keinen lokalisierbaren Inhalt", "sidebar.locate.object.table": "Tabelle", "sidebar.locate.object.view": "Ansicht", "sidebar.locate.object.materialized_view": "Materialisierte Ansicht", @@ -1421,6 +1539,7 @@ "sidebar.field.database_name": "Datenbankname", "sidebar.validation.name_required": "Geben Sie einen Namen ein.", "sidebar.modal.rename_database.title": "Datenbank umbenennen: {{name}}", + "sidebar.modal.rename_schema.title": "schema bearbeiten: {{name}}", "sidebar.field.new_database_name": "Neuer Datenbankname", "sidebar.validation.new_database_name_required": "Geben Sie den neuen Datenbanknamen ein.", "sidebar.field.schema_name": "schema-Name", @@ -1535,6 +1654,7 @@ "sidebar.tab.trigger": "Trigger: {{name}}", "sidebar.tab.event": "Ereignis: {{name}}", "sidebar.tab.edit_event": "Ereignis bearbeiten: {{name}}", + "sidebar.tab.new_event": "Neues Ereignis", "sidebar.tab.materialized_view_definition": "Materialisierte Ansicht: {{name}}", "sidebar.tab.view_definition": "Ansicht: {{name}}", "sidebar.tab.edit_view": "Ansicht bearbeiten: {{name}}", @@ -1545,12 +1665,17 @@ "sidebar.tab.create_procedure": "Neue Prozedur", "sidebar.tab.new_query": "Neue Abfrage", "sidebar.tab.new_query_database": "Neue Abfrage ({{database}})", + "store.fallback.connection_name": "Verbindung {{index}}", + "store.fallback.connection_tag_name": "Tag {{index}}", + "store.fallback.sql_snippet_name": "Snippet {{index}}", "sidebar.tab.redis_command": "Befehl - {{database}}", "sidebar.tab.redis_monitor": "Monitoring - {{database}}", + "sidebar.tab.recent_query": "Letzte Abfrage", "tab_manager.menu.close_all": "Alle Tabs schließen", "tab_manager.menu.close_left": "Tabs links schließen", "tab_manager.menu.close_other": "Andere Tabs schließen", "tab_manager.menu.close_right": "Tabs rechts schließen", + "tab_manager.menu.tab_display_settings": "Tab-Einstellungen", "tab_manager.close_aria": "{{title}} schließen", "tab_manager.kind_badge.query": "SQL", "tab_manager.kind_badge.table": "Tabelle", @@ -1603,6 +1728,22 @@ "tab_manager.hover.label.database": "Datenbank", "tab_manager.hover.label.object": "Objekt", "tab_manager.hover.label.type": "Typ", + "tab_manager.sql_file_close.read_failed_cancel_close": "SQL-Datei konnte nicht gelesen werden, das Schließen wurde abgebrochen: {{detail}}", + "tab_manager.sql_file_close.dirty_single_label": "\"{{title}}\"", + "tab_manager.sql_file_close.dirty_multiple_label": "{{count}} SQL-Dateien", + "tab_manager.sql_file_close.save_confirm_title": "Änderungen an SQL-Dateien speichern?", + "tab_manager.sql_file_close.save_confirm_content": "{{label}} enthält ungespeicherte Änderungen. Vor dem Schließen speichern?", + "tab_manager.sql_file_close.save_and_close": "Speichern und schließen", + "tab_manager.sql_file_close.discard": "Nicht speichern", + "tab_manager.sql_file_close.save_failed": "{{title}} konnte nicht gespeichert werden: {{detail}}", + "tab_manager.sql_file_close.unknown_error": "Unbekannter Fehler", + "tab_manager.sql_file_close.saved": "SQL-Datei gespeichert", + "tab_manager.sql_file_close.missing_single_label": "\"{{title}}\"", + "tab_manager.sql_file_close.missing_multiple_label": "{{count}} SQL-Datei-Tabs", + "tab_manager.sql_file_close.missing_confirm_title": "Fehlende SQL-Datei-Tabs schließen?", + "tab_manager.sql_file_close.missing_confirm_content": "Die externe SQL-Datei für {{label}} ist nicht mehr vorhanden oder wurde verschoben. Beim Schließen wird der lokale Entwurf im Tab verworfen.", + "tab_manager.sql_file_close.continue_close": "Schließen fortsetzen", + "tab_manager.sql_file_close.close_tabs": "Tabs schließen", "sidebar.message.no_visible_databases": "Es wurden keine sichtbaren Datenbanken oder Schemas zurückgegeben. Prüfen Sie die Berechtigungen oder aktualisieren Sie über das Kontextmenü.", "sidebar.message.visual_new_table_unsupported": "Diese Datenquelle unterstützt das visuelle Erstellen von Tabellen noch nicht.", "sidebar.message.jvm_resources_backend_unavailable": "JVM-Ressourcen können in diesem Build nicht durchsucht werden.", @@ -1634,8 +1775,26 @@ "sidebar.message.saved_query_deleted": "Abfrage gelöscht.", "sidebar.message.saved_query_name_unchanged": "Der Abfragename ist unverändert.", "sidebar.message.saved_query_renamed": "Abfrage umbenannt.", + "sidebar.message.saved_query_rename_failed": "Abfrage konnte nicht umbenannt werden: {{error}}", + "sidebar.message.saved_query_rebind_success": "Abfrage an {{name}} gebunden", + "sidebar.message.saved_query_rebind_failed": "Abfrage konnte nicht gebunden werden: {{error}}", + "sidebar.message.saved_query_delete_failed": "Abfrage konnte nicht gelöscht werden: {{error}}", + "sidebar.message.message_publish_unsupported": "Das aktuelle Objekt unterstützt kein testweises Senden von Nachrichten", + "sidebar.message.message_publish_success": "Testnachricht an {{destination}} gesendet", + "sidebar.message.message_publish_success_with_count": "Testnachricht an {{destination}} gesendet ({{count}} übermittelt)", + "sidebar.message.message_publish_target_fallback": "Ziel", + "sidebar.message.connection_release_failed": "Verbindung konnte nicht freigegeben werden", + "sidebar.message.connection_release_failed_from_sidebar": "Die Verbindung wurde in der Seitenleiste getrennt, aber die Backend-Verbindung konnte nicht freigegeben werden", "sidebar.menu.sort_by_name": "Sortieren nach Name", "sidebar.menu.sort_by_frequency": "Sortieren nach Häufigkeit", + "sidebar.menu.new_table": "Neue Tabelle", + "sidebar.menu.create_event": "Neues Ereignis", + "sidebar.menu.bind_to_connection": "An Verbindung binden", + "sidebar.aria.switch_connection": "Zu Verbindung {{name}} wechseln", + "sidebar.menu.edit_schema": "schema bearbeiten", + "sidebar.menu.export_current_schema_sql": "Tabellenstruktur des aktuellen schema exportieren (SQL)", + "sidebar.menu.backup_current_schema_sql": "Alle Tabellen im aktuellen schema sichern (Struktur und Daten SQL)", + "sidebar.menu.delete_schema": "schema löschen", "sidebar.menu.create_view": "Neue Ansicht", "sidebar.menu.create_function": "Neue Funktion", "sidebar.menu.create_procedure": "Neue Prozedur", @@ -1668,7 +1827,10 @@ "sidebar.menu.view_routine_definition": "Definition anzeigen", "sidebar.menu.edit_definition": "Definition bearbeiten", "sidebar.menu.delete_routine": "{{type}} löschen", + "sidebar.menu.copy_object_name": "Namen kopieren", + "sidebar.menu.table_structure": "Tabellenstruktur", "sidebar.menu.design_table": "Tabelle entwerfen", + "sidebar.menu.copy_table_name": "Tabellennamen kopieren", "sidebar.menu.copy_table_structure": "Tabellenstruktur kopieren", "sidebar.menu.backup_table_sql": "Tabelle sichern (SQL)", "sidebar.menu.rename_table": "Tabelle umbenennen", @@ -2038,6 +2200,7 @@ "jvm_diagnostic.session.default_reason": "Sitzung aus der Konsole gestartet", "jvm_diagnostic.ai_plan.error.transport_mismatch": "Der Diagnose-transport des AI-Plans ist {{planTransport}} und stimmt nicht mit der aktuellen Konsole {{currentTransport}} überein. Erstellen Sie den Plan vor dem Anwenden erneut.", "jvm_diagnostic.ai_plan.message.filled": "AI-Diagnoseplan wurde in die Konsole übernommen", + "jvm_diagnostic.ai_plan.default_reason": "AI-Diagnoseplan: {{intent}}", "jvm_diagnostic.session_capability.title": "Sitzung und Funktionen", "jvm_diagnostic.session_capability.description": "Aktueller Kanal, Berechtigungen und Schnellwartung", "jvm_diagnostic.session_capability.status.session_established": "Sitzung hergestellt", @@ -2090,11 +2253,23 @@ "jvm_diagnostic.presentation.phase.running": "Läuft", "jvm_diagnostic.presentation.phase.completed": "Abgeschlossen", "jvm_diagnostic.presentation.phase.failed": "Fehlgeschlagen", + "jvm_diagnostic.presentation.phase.canceled": "Abgebrochen", "jvm_diagnostic.presentation.phase.canceling": "Wird abgebrochen", "jvm_diagnostic.presentation.phase.diagnostic": "Diagnoseereignis", "jvm_diagnostic.presentation.event.diagnostic": "Diagnoseausgabe", "jvm_diagnostic.presentation.event.chunk": "Ausgabeabschnitt", "jvm_diagnostic.presentation.event.done": "Ausführung beendet", + "jvm_diagnostic.presentation.transport.agent_bridge": "Agent Bridge", + "jvm_diagnostic.presentation.transport.arthas_tunnel": "Arthas Tunnel", + "jvm_diagnostic.presentation.risk.low": "Niedriges Risiko", + "jvm_diagnostic.presentation.risk.medium": "Mittleres Risiko", + "jvm_diagnostic.presentation.risk.high": "Hohes Risiko", + "jvm_diagnostic.presentation.command_type.observe": "Beobachtung", + "jvm_diagnostic.presentation.command_type.trace": "Trace", + "jvm_diagnostic.presentation.command_type.mutating": "Hochrisiko", + "jvm_diagnostic.presentation.source.manual": "Manuelle Eingabe", + "jvm_diagnostic.presentation.source.ai_plan": "AI-Plan", + "jvm_diagnostic.presentation.fallback.unknown": "Unbekannt", "jvm_diagnostic.presentation.chunk.empty_event": "Leeres Ereignis", "jvm_diagnostic.history.title": "Audit-Verlauf", "jvm_diagnostic.history.description": "Letzte Befehle und Ausführungsstatus", @@ -2187,6 +2362,80 @@ "query_editor.format.restore_last_format": "Letzte Formatierung zurücknehmen", "query_editor.format.snippet_settings": "Snippet-Einstellungen...", "query_editor.format.shortcut_settings": "Tastenkürzel-Einstellungen...", + "snippet_settings.list.title": "Snippet-Liste", + "snippet_settings.action.new": "Neues Snippet", + "snippet_settings.action.reset": "Auf Standard zurücksetzen", + "snippet_settings.action.delete": "Löschen", + "snippet_settings.action.save": "Speichern", + "snippet_settings.action.close": "Schließen", + "snippet_settings.tag.builtin": "Integriert", + "snippet_settings.field.prefix.label": "Präfix", + "snippet_settings.field.prefix.placeholder": "z. B. sel, ins", + "snippet_settings.field.name.label": "Name", + "snippet_settings.field.name.placeholder": "Anzeigename des Snippets", + "snippet_settings.field.description.label": "Beschreibung (optional)", + "snippet_settings.field.description.placeholder": "Beschreibung für die Vervollständigungsdetails", + "snippet_settings.field.body.label": "Snippet-Inhalt", + "snippet_settings.empty_state": "Wählen Sie links ein Snippet zum Bearbeiten oder klicken Sie auf „{{action}}“", + "snippet_settings.syntax_help.label": "Snippet-Syntaxhinweise (bearbeitbar)", + "snippet_settings.syntax_help.placeholder": "Hinweise, die in den Vervollständigungsdetails angezeigt werden, z. B. Platzhalterbedeutungen, Parameterkonventionen oder Hinweise", + "snippet_settings.syntax_reference.label": "Referenz für Platzhalter-Syntax", + "snippet_settings.syntax_reference.first_tabstop": "${1:Platzhalter} Erste Tab-Position; der Platzhalter ist der Hinweistext", + "snippet_settings.syntax_reference.second_tabstop": "${2:Standardwert} Zweite Tab-Position; der Standardwert kann direkt übernommen werden", + "snippet_settings.syntax_reference.final_cursor": "$0 Endgültige Cursor-Position", + "snippet_settings.syntax_reference.linked_tabstop": "${1:tabellenname} Bei gleicher Nummer werden Bearbeitungen synchronisiert", + "snippet_settings.syntax_reference.builtin_variables": "Eingebaute Variablen (werden beim Expandieren automatisch ersetzt):", + "snippet_settings.syntax_reference.current_date": "${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} Aktuelles Datum", + "snippet_settings.syntax_reference.current_time": "${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND} Aktuelle Uhrzeit", + "snippet_settings.syntax_reference.unix_seconds": "${CURRENT_SECONDS_UNIX} Unix-Zeitstempel", + "snippet_settings.syntax_reference.uuid": "${UUID} Zufällige UUID", + "snippet_settings.syntax_reference.random": "${RANDOM} 6-stellige Zufallszahl", + "snippet_settings.syntax_reference.example": "Beispiel: SELECT ${1:spaltenname} FROM ${2:tabellenname} WHERE date >= '${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}';$0", + "snippet_settings.confirm.reset.title": "Auf Standard zurücksetzen", + "snippet_settings.confirm.reset.description": "Den ursprünglichen Inhalt dieses integrierten Snippets wiederherstellen", + "snippet_settings.confirm.delete.title": "Snippet löschen", + "snippet_settings.confirm.delete.description": "Möchten Sie dieses Snippet wirklich löschen?", + "snippet_settings.message.prefix_required": "Präfix ist erforderlich", + "snippet_settings.message.name_required": "Name ist erforderlich", + "snippet_settings.message.body_required": "Snippet-Inhalt ist erforderlich", + "snippet_settings.message.prefix_duplicate": "Das Präfix \"{{prefix}}\" wird bereits von einem anderen Snippet verwendet", + "snippet_settings.message.saved": "Snippet gespeichert", + "snippet_settings.message.deleted": "Snippet gelöscht", + "snippet_settings.message.reset_default": "Auf Standard zurückgesetzt", + "sql_snippets.builtin.sel.name": "SELECT-Basisabfrage", + "sql_snippets.builtin.sel.description": "Einfache SELECT-Abfragevorlage", + "sql_snippets.builtin.selw.name": "SELECT WHERE", + "sql_snippets.builtin.selw.description": "SELECT-Abfrage mit WHERE-Bedingung", + "sql_snippets.builtin.selj.name": "SELECT JOIN", + "sql_snippets.builtin.selj.description": "SELECT-Abfrage mit INNER JOIN", + "sql_snippets.builtin.ins.name": "INSERT", + "sql_snippets.builtin.ins.description": "Vorlage zum Einfügen von Daten mit INSERT", + "sql_snippets.builtin.upd.name": "UPDATE", + "sql_snippets.builtin.upd.description": "Vorlage zum Aktualisieren von Daten mit UPDATE", + "sql_snippets.builtin.del.name": "DELETE", + "sql_snippets.builtin.del.description": "Vorlage zum Löschen von Daten mit DELETE", + "sql_snippets.builtin.ct.name": "CREATE TABLE", + "sql_snippets.builtin.ct.description": "CREATE TABLE-Vorlage", + "sql_snippets.builtin.alt.name": "ALTER TABLE", + "sql_snippets.builtin.alt.description": "ALTER TABLE-Vorlage zum Hinzufügen einer Spalte", + "sql_snippets.builtin.dro.name": "DROP TABLE", + "sql_snippets.builtin.dro.description": "DROP TABLE-Vorlage", + "sql_snippets.builtin.grp.name": "GROUP BY", + "sql_snippets.builtin.grp.description": "Aggregationsabfragevorlage mit GROUP BY", + "sql_snippets.builtin.ljo.name": "LEFT JOIN", + "sql_snippets.builtin.ljo.description": "LEFT JOIN-Vorlage", + "sql_snippets.builtin.sub.name": "Unterabfrage", + "sql_snippets.builtin.sub.description": "IN-Unterabfragevorlage", + "sql_snippets.builtin.lim.name": "LIMIT-Abfrage", + "sql_snippets.builtin.lim.description": "Paging-Abfragevorlage mit LIMIT", + "sql_snippets.builtin.ord.name": "ORDER BY", + "sql_snippets.builtin.ord.description": "Abfragevorlage mit Sortierung", + "sql_snippets.builtin.seld.name": "SELECT nach Datum", + "sql_snippets.builtin.seld.description": "SELECT-Abfrage mit Datumsfilter, automatisch mit dem heutigen Datum vorbelegt", + "sql_snippets.builtin.ctt.name": "CREATE TABLE (mit Zeitspalten)", + "sql_snippets.builtin.ctt.description": "Tabellenerstellungsvorlage mit created_at / updated_at-Zeitspalten", + "sql_snippets.builtin.inst.name": "INSERT (mit Zeitstempel)", + "sql_snippets.builtin.inst.description": "INSERT-Vorlage, automatisch mit dem aktuellen Zeitstempel vorbelegt", "query_editor.message.format_failed": "Formatierung fehlgeschlagen: Die SQL-Syntax ist möglicherweise ungültig.", "query_editor.message.no_format_restore_snapshot": "Es ist kein SQL-Stand vor der Formatierung zum Wiederherstellen verfügbar.", "query_editor.message.format_restore_success": "Der SQL-Stand vor der Formatierung wurde wiederhergestellt.", @@ -2203,6 +2452,40 @@ "query_editor.message.execution_multi_success": "{{statements}} Anweisungen ausgeführt und {{results}} Ergebnismengen erzeugt.", "query_editor.message.execution_result_sets_success": "Ausführung abgeschlossen und {{results}} Ergebnismengen erzeugt.", "query_editor.message.execution_failed_with_error": "Abfrageausführung fehlgeschlagen: {{error}}", + "query_editor.sql_error.unknown": "Unbekannter Fehler", + "query_editor.sql_error.wrapper.semantic_line": "Bedeutung: {{label}}. {{explanation}}", + "query_editor.sql_error.wrapper.suggestion_line": "Vorschlag: {{suggestion}}", + "query_editor.sql_error.wrapper.raw_line": "Ursprünglicher Fehler: {{error}}", + "query_editor.sql_error.rule.syntax.label": "SQL-Syntaxfehler", + "query_editor.sql_error.rule.syntax.explanation": "Ursache sind meist Schlüsselwörter, Kommas, Klammern, Anführungszeichen, die Reihenfolge der Anweisungen oder ein nicht passender SQL-Dialekt.", + "query_editor.sql_error.rule.syntax.suggestion": "Prüfe das SQL-Fragment nahe der gemeldeten Position und bestätige, dass Datenquellentyp und SQL-Dialekt zusammenpassen.", + "query_editor.sql_error.rule.object_missing.label": "Tabelle oder Objekt existiert nicht", + "query_editor.sql_error.rule.object_missing.explanation": "Das SQL verweist auf eine Tabelle, View, Sequenz oder ein anderes Datenbankobjekt, das in der aktuellen Datenbank oder im schema nicht gefunden wurde.", + "query_editor.sql_error.rule.object_missing.suggestion": "Prüfe Objektname, Groß-/Kleinschreibung, schema/database-Präfix und ob die für diese Abfrage ausgewählte Datenbank korrekt ist.", + "query_editor.sql_error.rule.column_missing.label": "Spalte existiert nicht", + "query_editor.sql_error.rule.column_missing.explanation": "Das SQL verweist auf eine Spalte, die nicht im Ergebnissatz enthalten ist, anders geschrieben wurde oder in der aktuellen Tabelle nicht existiert.", + "query_editor.sql_error.rule.column_missing.suggestion": "Prüfe Spaltennamen, Aliasse, Groß-/Kleinschreibung, Tabellenaliase und ob die Spalte zum aktuellen FROM/JOIN-Objekt gehört.", + "query_editor.sql_error.rule.unique_conflict.label": "Eindeutige Einschränkung oder Primärschlüsselkonflikt", + "query_editor.sql_error.rule.unique_conflict.explanation": "Die eingefügten oder aktualisierten Daten duplizieren einen bestehenden Wert in einem eindeutigen Index, Primärschlüssel oder einer eindeutigen Einschränkung.", + "query_editor.sql_error.rule.unique_conflict.suggestion": "Prüfe den doppelten Schlüsselwert und nutze bei Bedarf UPDATE oder UPSERT, oder passe den eindeutigen Schlüsselwert an.", + "query_editor.sql_error.rule.permission_denied.label": "Unzureichende Berechtigungen", + "query_editor.sql_error.rule.permission_denied.explanation": "Das aktuelle Datenbankkonto darf dieses SQL nicht ausführen oder nicht auf die betroffenen Objekte zugreifen.", + "query_editor.sql_error.rule.permission_denied.suggestion": "Prüfe Kontoberechtigungen, schema-Berechtigungen, Nur-Lese-Einschränkungen und ob ein Administrator Zugriff gewähren muss.", + "query_editor.sql_error.rule.type_mismatch.label": "Datentyp oder Format passt nicht", + "query_editor.sql_error.rule.type_mismatch.explanation": "Der geschriebene, verglichene oder konvertierte Wert passt nicht zum Zielfeld oder Ausdrucksformat.", + "query_editor.sql_error.rule.type_mismatch.suggestion": "Prüfe Datum, Zahlen, Boolesche Werte, Enum-Werte, implizite Konvertierungen und Spaltentypen; nutze bei Bedarf ein explizites CAST.", + "query_editor.sql_error.rule.constraint_failed.label": "Einschränkungsprüfung fehlgeschlagen", + "query_editor.sql_error.rule.constraint_failed.explanation": "Die Daten verletzen einen Fremdschlüssel, eine NOT NULL-Regel, CHECK-Einschränkung oder referenzielle Integrität.", + "query_editor.sql_error.rule.constraint_failed.suggestion": "Prüfe zugehörige Parent-Tabelleneinträge, Pflichtfelder, CHECK-Bedingungen und ob die Schreibreihenfolge korrekt ist.", + "query_editor.sql_error.rule.timeout_or_canceled.label": "Abfrage mit Timeout oder abgebrochen", + "query_editor.sql_error.rule.timeout_or_canceled.explanation": "Die SQL-Ausführung hat das Zeitlimit überschritten oder wurde während der Ausführung manuell abgebrochen.", + "query_editor.sql_error.rule.timeout_or_canceled.suggestion": "Prüfe SQL-Ausführungsplan, Filterbedingungen und Indizes; begrenze bei Bedarf den Abfragebereich oder passe das Timeout an.", + "query_editor.sql_error.rule.connection_or_auth.label": "Datenbankverbindung oder Authentifizierung fehlgeschlagen", + "query_editor.sql_error.rule.connection_or_auth.explanation": "Der Client konnte keine Verbindung zur Datenbank herstellen, oder Zugangsdaten, Netzwerk oder Instanzstatus sind fehlerhaft.", + "query_editor.sql_error.rule.connection_or_auth.suggestion": "Prüfe Host, Port, Benutzername, Passwort, Netzwerkverbindung, Proxy/SSH-Tunnel und Status des Datenbankdienstes.", + "query_editor.sql_error.rule.generic.label": "Datenbankausführungsfehler", + "query_editor.sql_error.rule.generic.explanation": "Die Datenbank hat einen Ausführungsfehler zurückgegeben, ohne dass ein spezifischerer Fehlertyp erkannt wurde.", + "query_editor.sql_error.rule.generic.suggestion": "Untersuche weiter mit dem ursprünglichen Fehler, dem SQL-Fragment und dem aktuellen Datenbankdialekt.", "query_editor.message.cancel_no_running": "Keine laufende Abfrage zum Abbrechen.", "query_editor.message.cancel_success": "Abfrage abgebrochen.", "query_editor.message.cancel_failed": "Abfrage konnte nicht abgebrochen werden: {{error}}", @@ -2471,6 +2754,7 @@ "data_grid.column_settings.show_all": "Alle anzeigen", "data_grid.column_settings.show_comments": "Spaltenkommentare in der Kopfzeile anzeigen", "data_grid.column_settings.show_types": "Spaltentypen in der Kopfzeile anzeigen", + "data_grid.aria.row_number": "Zeilennummer", "data_grid.context_menu.auto_fit_column": "Spaltenbreite an Inhalt anpassen", "data_grid.context_menu.clear_column_sort": "Sortierung für dieses Feld aufheben", "data_grid.context_menu.column_display_section": "Feldanzeige", @@ -2606,6 +2890,7 @@ "data_grid.message.auto_commit_failed": "Automatische Übernahme fehlgeschlagen: {{detail}}", "data_grid.message.auto_commit_success": "Automatisch übernommen", "data_grid.message.commit_failed": "Übernahme fehlgeschlagen: {{detail}}", + "data_grid.message.rollback_failed": "Rollback fehlgeschlagen: {{detail}}", "data_grid.message.undo_added_row_hint": "Neue Zeilen lassen sich über „Auswahl löschen“ oder einen Rollback der ganzen Tabelle zurücknehmen.", "data_grid.message.undo_cell_original_missing": "Die ursprünglichen Daten dieser Zelle wurden nicht gefunden, daher kann die Änderung nicht zurückgenommen werden.", "data_grid.message.undo_cell_success": "Zelländerung zurückgenommen", @@ -2680,6 +2965,7 @@ "data_grid.message.target_rows_cannot_only_source": "Die Zielzeilen dürfen nicht nur aus der Quellzeile bestehen. Wähle eine andere Zeile aus.", "data_grid.message.target_rows_no_update": "Zielzeilen müssen nicht aktualisiert werden", "data_grid.message.transaction_committed": "Transaktion übernommen", + "data_grid.message.transaction_rolled_back": "Transaktion zurückgesetzt", "data_viewer.message.result_not_ready": "Die aktuelle Ergebnismenge ist noch nicht bereit. Laden Sie zuerst einmal Daten.", "data_viewer.message.query_failed": "Abfrage fehlgeschlagen", "data_viewer.message.query_timeout": "Die Abfrage hat das Verbindungstimeout überschritten und wurde unterbrochen. Erhöhen Sie das Verbindungstimeout oder verkleinern Sie den Abfragebereich und versuchen Sie es erneut.", @@ -2697,6 +2983,7 @@ "data_viewer.read_only.reason.index_metadata_unavailable": "Metadaten zum eindeutigen Index konnten nicht geladen werden, deshalb können Änderungen nicht sicher gesendet werden.", "data_viewer.read_only.reason.no_safe_locator": "Es wurde kein Primärschlüssel oder nutzbarer eindeutiger Index gefunden, deshalb können Änderungen nicht sicher gesendet werden.", "data_viewer.read_only.reason.oracle_rowid_missing": "Es wurde kein Primärschlüssel oder nutzbarer eindeutiger Index gefunden, und Oracle ROWID fehlt im Ergebnis, deshalb können Änderungen nicht sicher gesendet werden.", + "data_viewer.read_only.reason.duckdb_rowid_missing": "Es wurde kein Primärschlüssel, nutzbarer eindeutiger Index oder DuckDB rowid gefunden, deshalb können Änderungen nicht sicher gesendet werden.", "data_viewer.read_only.reason.primary_key_column_missing": "Im Ergebnis fehlt die Primärschlüsselspalte {{columns}}, deshalb können Änderungen nicht sicher gesendet werden.", "data_viewer.read_only.warning.table": "Tabelle {{target}} bleibt schreibgeschützt: {{reason}}", "data_viewer.read_only.warning.collection": "Collection {{target}} bleibt schreibgeschützt: {{reason}}", @@ -2719,6 +3006,12 @@ "definition_viewer.error.query_failed_detail": "Definitionsabfrage fehlgeschlagen: {{detail}}", "definition_viewer.field.database": "Datenbank", "definition_viewer.field.type": "Typ", + "definition_viewer.action.edit_object": "Objekt bearbeiten", + "definition_viewer.warning.refresh_latest_failed": "Neueste Definition konnte nicht aktualisiert werden", + "definition_viewer.edit.tab_title": "{{object}} bearbeiten: {{name}}", + "definition_viewer.edit.comment_title": "{{object}} bearbeiten: {{name}}", + "definition_viewer.edit.comment_compatibility": "Bestätigen Sie vor der Ausführung, dass die Syntax mit der aktuellen Datenbank kompatibel ist", + "definition_viewer.edit.comment_empty_definition": "Die aktuelle Objektdefinition ist leer. Vervollständigen Sie vor der Ausführung das DDL für {{name}}", "definition_viewer.editor.unsupported_view_definition": "Dieser Datenbanktyp unterstützt das Anzeigen von Ansichtsdefinitionen nicht", "definition_viewer.editor.unsupported_sqlite_routine_definition": "SQLite unterstützt keine Verwaltung von Funktions-/Prozedurdefinitionen", "definition_viewer.editor.unsupported_routine_definition": "Dieser Datenbanktyp unterstützt das Anzeigen von Funktions-/Prozedurdefinitionen nicht", @@ -2757,6 +3050,14 @@ "trigger_viewer.editor.sphinx.unsupported_query": "Die aktuelle Sphinx-Instanz{{version}} unterstützt das Abfragen von Triggerdefinitionen nicht.", "trigger_viewer.editor.sphinx.failed_message_label": "Zurückgegebene Fehlermeldung", "trigger_viewer.editor.sphinx.failed_message_unknown": "Zurückgegebene Fehlermeldung: Unbekannter Fehler", + "trigger_viewer.action.edit_object": "Objekt bearbeiten", + "trigger_viewer.warning.refresh_latest_failed": "Neueste Definition konnte nicht aktualisiert werden", + "trigger_viewer.tab.edit_trigger_title": "Trigger bearbeiten: {{name}}", + "trigger_viewer.edit_sql.header": "Trigger bearbeiten: {{name}}", + "trigger_viewer.edit_sql.replace_hint": "Die Tabellenentwurfsänderung löscht zuerst den ursprünglichen Trigger und erstellt danach einen neuen. Vor dem Ausführen prüfen.", + "trigger_viewer.edit_sql.compatibility_hint": "Prüfe vor dem Ausführen die Kompatibilität mit der aktuellen Datenbank.", + "trigger_viewer.edit_sql.empty_definition": "Die Triggerdefinition ist leer. Vervollständige die CREATE TRIGGER-Anweisung vor dem Ausführen.", + "trigger_viewer.edit_sql.fragment_definition": "Die aktuelle Datenquelle hat nur ein Fragment der Triggerdefinition zurückgegeben. Vervollständige die CREATE TRIGGER-Anweisung vor dem Ausführen.", "data_grid.modal.export_options.all_data": "Alle Daten exportieren", "data_grid.modal.export_options.current_page": "Aktuelle Seite exportieren ({{count}} Zeilen)", "data_grid.modal.export_options.filtered_results": "Gefilterte Ergebnisse", @@ -2950,6 +3251,7 @@ "table_designer.message.table_comment_updated": "Tabellenkommentar aktualisiert", "table_designer.message.table_name_required": "Geben Sie einen Tabellennamen ein", "table_designer.message.target_table_required": "Geben Sie den Namen der Zieltabelle ein", + "table_designer.message.duckdb_primary_key_change_unsupported": "DuckDB unterstützt derzeit nur das Hinzufügen eines Primärschlüssels zu Tabellen ohne Primärschlüssel. Das Ändern oder Entfernen eines vorhandenen Primärschlüssels erfordert den Neuaufbau der Tabelle.", "table_designer.message.trigger_created": "Trigger erstellt", "table_designer.message.trigger_deleted": "Trigger gelöscht", "table_designer.message.trigger_updated": "Trigger aktualisiert", @@ -2995,7 +3297,9 @@ "table_designer.placeholder.table_comment": "Tabellenkommentar eingeben", "table_designer.placeholder.table_name": "Tabellennamen eingeben", "table_designer.placeholder.target_table_name": "Namen der Zieltabelle eingeben", + "table_designer.schema_sql.doris.primary_key_hint": "-- Doris-Primärschlüssel-/Key-Modelländerungen müssen passend zum Tabellenmodell manuell migriert werden. MySQL-spezifische DROP/ADD PRIMARY KEY-Klauseln wurden ausgelassen.", "table_designer.schema_sql.duckdb.comment_hint": "-- DuckDB kann Spaltenkommentare nicht über COMMENT ON COLUMN dauerhaft speichern. Der Kommentar für Spalte {{column}} bleibt nur in der Designer-Vorschau erhalten.", + "table_designer.schema_sql.duckdb.primary_key_hint": "-- DuckDB unterstützt derzeit nur das Hinzufügen von PRIMARY KEY zu Tabellen ohne bestehenden Primärschlüssel. Änderungen oder Löschungen bestehender Primärschlüssel erfordern einen Tabellenneuaufbau.", "table_designer.schema_sql.limited_column_hint": "-- Die Syntax für Spalten-Constraints, Standardwerte und Kommentare in {{dialect}} unterscheidet sich von MySQL. MySQL-spezifische Klauseln wurden ausgelassen; ergänzen Sie vor der Ausführung dialektspezifisches SQL.", "table_designer.schema_sql.sqlite.modify_column_hint": "-- SQLite kann Spalteneigenschaften nicht direkt ändern. Erstellen Sie für Spalte {{column}} eine neue Tabelle, migrieren Sie die Daten und ersetzen Sie die alte Tabelle.", "table_designer.schema_sql.sqlserver.drop_primary_key_hint": "-- SQL Server benötigt zum Löschen des alten Primärschlüssels den ursprünglichen Constraint-Namen. Prüfen Sie ihn vor dem Löschen im Indextab.", @@ -3023,6 +3327,7 @@ "table_designer.sql_preview.change.comment": "Kommentaränderung", "table_designer.sql_preview.change.constraint": "Constraint-Änderung", "table_designer.sql_preview.change.create": "Neue Tabellenstruktur", + "table_designer.sql_preview.change.create_index": "Index erstellen", "table_designer.sql_preview.change.drop": "Entfernung", "table_designer.sql_preview.change.modify": "Spalteneigenschaft geändert", "table_designer.sql_preview.change.rename": "Umbenennung", @@ -3033,6 +3338,7 @@ "table_designer.title.default_database": "Standarddatenbank", "table_designer.title.schema_designer": "Schema-Designer", "table_designer.tab.columns": "Spalten", + "table_designer.tab.edit_trigger_title": "Trigger bearbeiten: {{name}}", "table_designer.tab.foreign_keys": "Fremdschlüssel", "table_designer.tab.indexes": "Indizes", "table_designer.tab.triggers": "Trigger", @@ -3055,6 +3361,122 @@ "redis_command.output.title": "Ausgabe", "redis_command.state.connection_not_found": "Verbindung nicht gefunden", "redis_command.title.console": "Redis-Konsole", + "db.backend.error.data_source_type_required": "Wählen Sie zuerst einen Datenquellentyp aus", + "db.backend.error.unsupported_database_type": "Nicht unterstützter Datenbanktyp: {{dbType}}", + "db.backend.error.clickhouse_address_required": "Geben Sie eine ClickHouse-Hostadresse oder Verbindungs-URI ein", + "db.backend.error.clickhouse_http_client_protocol_version_unsupported": "Der aktuelle ClickHouse-HTTP-Port unterstützt client_protocol_version nicht (häufig bei ClickHouse 22.8). Es wird im HTTP-Kompatibilitätsmodus erneut versucht. Wenn es weiterhin fehlschlägt, prüfen Sie Verbindungsprotokoll und Port", + "db.backend.error.clickhouse_native_protocol_mismatch": "Die Serverantwort sieht nicht wie ein Native-Handshake aus; der aktuelle Port wirkt eher wie ein HTTP/HTTPS-Port. Wählen Sie das HTTP-Protokoll oder prüfen Sie den ClickHouse-Native-Port", + "db.backend.error.clickhouse_http_protocol_mismatch": "Die Serverantwort sieht nicht wie eine HTTP-Antwort aus; der aktuelle Port wirkt eher wie ein Native-Port. Wählen Sie das Native-Protokoll oder prüfen Sie den ClickHouse-HTTP-Port", + "db.backend.error.clickhouse_unknown_error": "Unbekannter Fehler", + "db.backend.error.clickhouse_driver_detail_missing": "Der Treiber hat keine Fehlerdetails zurückgegeben", + "db.backend.error.clickhouse_attempt_tls_config_failed": "TLS-Konfiguration bei Versuch {{attempt}} fehlgeschlagen (protocol={{protocol}}): {{detail}}", + "db.backend.error.clickhouse_attempt_validation_failed": "Verbindungsvalidierung bei Versuch {{attempt}} fehlgeschlagen (protocol={{protocol}}): {{detail}}", + "db.backend.error.clickhouse_validation_failed_manual": "ClickHouse-Verbindungsvalidierung fehlgeschlagen: Benutzergewähltes Protokoll {{protocol}} wurde für {{host}}:{{port}} verwendet. {{detail}}", + "db.backend.error.clickhouse_validation_failed_auto": "ClickHouse-Verbindungsvalidierung fehlgeschlagen: Automatische Protokollerkennung fehlgeschlagen (übliche Native-Ports 9000/9440, übliche HTTP-Ports {{httpPorts}}; geben Sie bei nicht standardmäßigen Ports das Verbindungsprotokoll manuell an). {{detail}}", + "db.backend.error.duckdb_driver_unavailable": "DuckDB-Treiber ist nicht verfügbar: {{detail}}", + "db.backend.error.duckdb_build_unavailable": "Der aktuelle Build enthält den DuckDB-Treiber nicht (platform={{platform}}). Aktiviere CGO und verwende eine unterstützte Plattform (darwin/linux amd64|arm64, windows/amd64) oder stelle über -tags duckdb_use_lib / duckdb_use_static_lib eine benutzerdefinierte Bibliothek bereit", + "db.backend.error.connection_open_failed_prefix": "Datenbankverbindung konnte nicht geöffnet werden: ", + "db.backend.error.custom_driver_system_odbc_unsupported_prefix": "Datenbankverbindung konnte nicht geöffnet werden: Benutzerdefinierte Verbindungen unterstützen es nicht, den System-ODBC/JDBC-Treibernamen \"{{driver}}\" direkt einzugeben. Geben Sie stattdessen einen bereits in GoNavi registrierten Go database/sql-Treibernamen ein. Der aktuelle Build registriert keinen generischen ODBC-Treiber, daher wird eine Verbindung zu InterSystems IRIS über \"{{driver}}\" derzeit noch nicht unterstützt: ", + "db.backend.error.custom_driver_unregistered_prefix": "Datenbankverbindung konnte nicht geöffnet werden: Der benutzerdefinierte Verbindungs-Treiber \"{{driver}}\" ist in GoNavi nicht registriert. Geben Sie statt eines System-ODBC/JDBC-Treibernamens einen registrierten Go database/sql-Treibernamen ein: ", + "db.backend.error.connection_verify_failed_prefix": "Verbindung konnte nach dem Aufbau nicht verifiziert werden: ", + "db.backend.error.sqlite_file_path_required": "SQLite erfordert einen lokalen Datenbankdateipfad (z. B. /path/to/demo.sqlite)", + "db.backend.error.sqlite_host_port_not_file_path": "SQLite erfordert einen lokalen Datenbankdateipfad; die aktuelle Eingabe sieht wie eine Hostadresse aus: {{dsn}}", + "db.backend.error.connection_not_open": "Verbindung ist nicht geöffnet", + "db.backend.error.transaction_not_open": "Die Transaktion ist nicht geöffnet", + "db.backend.error.transaction_already_finished": "Die Transaktion wurde bereits beendet", + "db.backend.error.mqtt_connect_timeout": "MQTT-Verbindung zeitlich überschritten", + "db.backend.error.mqtt_subscribe_timeout": "MQTT-Abonnement zeitlich überschritten", + "db.backend.error.mqtt_publish_timeout": "MQTT-Publish zeitlich überschritten", + "db.backend.action.delete": "Löschen", + "db.backend.action.update": "Aktualisieren", + "db.backend.error.row_delete_failed": "Löschen fehlgeschlagen: {{detail}}", + "db.backend.error.row_action_not_effective_rows_affected_unknown": "{{action}} wurde nicht wirksam: Die Anzahl betroffener Zeilen konnte nicht bestimmt werden: {{detail}}", + "db.backend.error.row_action_not_effective_no_rows_matched": "{{action}} wurde nicht wirksam: Keine Zeilen wurden gefunden", + "db.backend.error.row_action_not_effective_multiple_rows": "{{action}} wurde nicht wirksam: {{count}} Zeilen betroffen; genau 1 erwartet", + "db.backend.error.row_update_key_conditions_required": "Für den Aktualisierungsvorgang sind Schlüsselkriterien erforderlich", + "db.backend.error.row_update_failed": "Aktualisierung fehlgeschlagen: {{detail}}", + "db.backend.error.batch_insert_quote_column_required": "Die Spaltenquotierungsfunktion ist erforderlich", + "db.backend.error.batch_insert_placeholder_required": "Die Platzhalterfunktion ist erforderlich", + "db.backend.error.batch_insert_exec_required": "Die Ausführungsfunktion ist erforderlich", + "db.backend.error.batch_insert_literal_required": "Die Literal-Funktion ist erforderlich", + "db.backend.error.batch_insert_failed": "Einfügen fehlgeschlagen: {{detail}}", + "db.backend.error.batch_insert_failed_with_sql": "Einfügen fehlgeschlagen: {{detail}}; SQL={{sql}}", + "db.backend.error.batch_insert_no_rows_affected": "Einfügen wurde nicht wirksam: keine Zeilen betroffen", + "db.backend.error.mongo_member_discovery_unsupported": "Der aktuelle MongoDB-Treiber unterstützt keine Mitgliedererkennung", + "db.backend.error.http_tunnel_proxy_conflict": "HTTP Tunnel kann nicht zusammen mit einem normalen Proxy aktiviert werden", + "db.backend.error.http_tunnel_host_required": "HTTP-Tunnel-Host ist erforderlich", + "db.backend.error.http_tunnel_port_invalid": "HTTP-Tunnel-Port ist ungültig: {{port}}", + "db.backend.error.proxy_ssh_gateway_connect_failed": "Verbindung zum SSH-Gateway über den Proxy fehlgeschlagen: {{detail}}", + "db.backend.error.proxy_target_port_invalid": "Zielport ist ungültig: {{port}}", + "db.backend.error.proxy_local_forward_addr_parse_failed": "Lokale Proxy-Weiterleitungsadresse konnte nicht geparst werden: {{address}}", + "db.backend.message.connect_timeout_detail": "Zeitüberschreitung bei der Datenbankverbindung: {{dbType}} {{host}}:{{port}}/{{database}}: {{detail}}", + "db.backend.message.connect_failure_cooldown": "Die Verbindung ist vor Kurzem fehlgeschlagen und befindet sich in einer Abkühlphase. Versuchen Sie es in {{remaining}} erneut; letzter Fehler: {{detail}}", + "db.backend.message.connect_success": "Verbindung erfolgreich", + "db.backend.message.mongo_primary_credentials_label": "Primär-Anmeldedaten", + "db.backend.message.mongo_replica_credentials_label": "Replikat-Anmeldedaten", + "db.backend.message.release_success": "Verbindung freigegeben", + "db.backend.message.mongo_members_discovered": "{{count}} Mitglieder gefunden", + "db.backend.error.schema_name_required": "Schemaname ist erforderlich", + "db.backend.error.schema_create_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Erstellen von Schemas über diesen Einstiegspunkt nicht", + "db.backend.error.schema_same_name": "Alter und neuer Schemaname müssen unterschiedlich sein", + "db.backend.error.schema_rename_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Umbenennen von Schemas über diesen Einstiegspunkt nicht", + "db.backend.error.schema_drop_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Löschen von Schemas über diesen Einstiegspunkt nicht", + "db.backend.error.target_database_required": "Zieldatenbank ist erforderlich", + "db.backend.message.schema_created": "Schema erstellt", + "db.backend.message.schema_renamed": "Schema umbenannt", + "db.backend.message.schema_dropped": "Schema gelöscht", + "db.backend.error.database_name_required": "Datenbankname ist erforderlich", + "db.backend.error.database_create_sphinx_unsupported": "Sphinx unterstützt das Erstellen von Datenbanken nicht", + "db.backend.error.database_create_user_schema_unsupported": "Die aktuelle Datenquelle ({{dbType}}) behandelt Datenbanken als Benutzer/Schemas und unterstützt das Erstellen über diesen Einstiegspunkt nicht. Verwenden Sie den SQL-Editor, um eine CREATE USER-Anweisung auszuführen", + "db.backend.error.database_same_name": "Alter und neuer Datenbankname müssen unterschiedlich sein", + "db.backend.error.database_rename_direct_unsupported": "MySQL/MariaDB/OceanBase/StarRocks/Sphinx unterstützt kein direktes Umbenennen von Datenbanken. Erstellen Sie stattdessen eine neue Datenbank und migrieren Sie die Daten", + "db.backend.error.database_rename_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Umbenennen von Datenbanken nicht", + "db.backend.error.database_drop_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Löschen von Datenbanken nicht", + "db.backend.message.database_created": "Datenbank erstellt", + "db.backend.message.database_renamed": "Datenbank umbenannt", + "db.backend.message.database_dropped": "Datenbank gelöscht", + "db.backend.error.multi_statement_execution_failed": "Anweisung {{index}} konnte nicht ausgeführt werden: {{detail}}", + "db.backend.error.multi_statement_previous_success": " ({{count}} vorherige Anweisungen wurden erfolgreich ausgeführt)", + "db.backend.message.multi_statement_sequential_fallback": "Die aktuelle Datenquelle ({{dbType}}) unterstützt keine native Ausführung mehrerer Anweisungen. Sie wurde automatisch in {{count}} Anweisungen aufgeteilt und nacheinander ausgeführt.", + "db.backend.error.managed_transaction_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt keine vom SQL-Editor verwalteten Transaktionen", + "db.backend.error.transaction_query_unsupported": "Die aktuelle Transaktionssitzung unterstützt keine Abfrageanweisungen", + "db.backend.error.transaction_id_required": "Transaktions-ID ist erforderlich", + "db.backend.error.transaction_not_found": "Transaktion nicht gefunden oder bereits beendet", + "db.backend.error.transaction_commit_failed": "Commit der Transaktion fehlgeschlagen: {{detail}}", + "db.backend.error.transaction_rollback_failed": "Rollback der Transaktion fehlgeschlagen: {{detail}}", + "db.backend.error.transaction_commit_close_failed": "Transaktion wurde übernommen, aber das Schließen der Sitzung ist fehlgeschlagen: {{detail}}", + "db.backend.error.transaction_rollback_close_failed": "Transaktion wurde zurückgesetzt, aber das Schließen der Sitzung ist fehlgeschlagen: {{detail}}", + "db.backend.message.transaction_committed": "Transaktion übernommen", + "db.backend.message.transaction_rolled_back": "Transaktion zurückgesetzt", + "db.backend.error.table_name_required": "Tabellenname ist erforderlich", + "db.backend.error.clickhouse_delete_failed_with_sql": "ClickHouse-Löschung fehlgeschlagen: {{detail}}; SQL={{sql}}", + "db.backend.error.clickhouse_update_failed_with_sql": "ClickHouse-Aktualisierung fehlgeschlagen: {{detail}}; SQL={{sql}}", + "db.backend.error.tdengine_apply_changes_insert_only": "TDengine-Ziele unterstützen derzeit nur INSERT-Schreibvorgänge; ApplyChanges unterstützt keine UPDATE/DELETE-Differenzen", + "db.backend.error.oracle_column_metadata_load_failed": "Spaltenmetadaten konnten nicht geladen werden (Tabelle={{table}}): {{detail}}. Prüfen Sie die ALL_TAB_COLUMNS-Abfrageberechtigung und ob die Tabelle existiert", + "db.backend.error.create_table_statement_not_found": "Die CREATE TABLE-Anweisung wurde nicht gefunden", + "db.backend.error.oceanbase_oracle_show_create_table_fallback_failed": "{{metadataDetail}}; OceanBase Oracle SHOW CREATE TABLE-Fallback fehlgeschlagen: {{showDetail}}", + "db.backend.error.table_columns_missing_for_ddl": "Es konnten keine Spaltendefinitionen abgerufen werden, daher konnte die CREATE TABLE-Anweisung nicht erzeugt werden", + "db.backend.error.table_columns_empty_for_ddl": "Die abgerufenen Spaltendefinitionen waren leer, daher konnte die CREATE TABLE-Anweisung nicht erzeugt werden", + "db.backend.error.table_same_name": "Der alte und der neue Tabellenname müssen unterschiedlich sein", + "db.backend.error.table_new_name_no_qualifier": "Der neue Tabellenname darf kein Schema- oder Datenbankpräfix enthalten", + "db.backend.error.table_rename_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Umbenennen von Tabellen nicht", + "db.backend.error.old_table_name_required": "Der alte Tabellenname ist erforderlich", + "db.backend.error.table_drop_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Löschen von Tabellen nicht", + "db.backend.message.table_renamed": "Tabelle umbenannt", + "db.backend.message.table_dropped": "Tabelle gelöscht", + "db.backend.error.view_name_required": "View-Name ist erforderlich", + "db.backend.error.view_same_name": "Der alte und der neue View-Name müssen unterschiedlich sein", + "db.backend.error.view_new_name_no_qualifier": "Der neue View-Name darf kein Schema- oder Datenbankpräfix enthalten", + "db.backend.error.view_rename_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Umbenennen von Views nicht", + "db.backend.error.old_view_name_required": "Der alte View-Name ist erforderlich", + "db.backend.error.view_drop_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Löschen von Views nicht", + "db.backend.message.view_renamed": "View umbenannt", + "db.backend.message.view_dropped": "View gelöscht", + "db.backend.error.routine_name_required": "Der Name der Funktion oder gespeicherten Prozedur ist erforderlich", + "db.backend.error.routine_drop_unsupported": "Die aktuelle Datenquelle ({{dbType}}) unterstützt das Löschen von Funktionen oder gespeicherten Prozeduren nicht", + "db.backend.error.duckdb_procedure_drop_unsupported": "DuckDB unterstützt gespeicherte Prozeduren noch nicht", + "db.backend.message.function_dropped": "Funktion gelöscht", + "db.backend.message.procedure_dropped": "Gespeicherte Prozedur gelöscht", "redis.backend.message.connect_success": "Verbindung erfolgreich", "redis.backend.message.set_success": "Setzen erfolgreich", "redis.backend.message.select_db_success": "Datenbank gewechselt", @@ -3069,6 +3491,20 @@ "redis.backend.error.node_address_required": "Redis-Knotenadresse darf nicht leer sein", "redis.backend.error.invalid_node_address": "Ungültige Redis-Knotenadresse: {{address}}", "redis.backend.error.invalid_port": "Ungültiger Redis-Port: {{address}}", + "redis.backend.label.topology_sentinel": "Sentinel", + "redis.backend.label.topology_cluster": "Cluster", + "redis.backend.label.topology_multi_node": "Multi-Node", + "redis.backend.error.topology_ssh_tunnel_unsupported": "Der Redis-{{topology}}-Modus unterstützt SSH-Tunnel noch nicht. Deaktivieren Sie SSH und versuchen Sie es erneut.", + "redis.backend.error.sentinel_master_required": "Der Redis-Sentinel-Modus erfordert einen Master-Namen", + "redis.backend.error.connect_tls_setup_failed": "TLS-Einrichtung bei Versuch {{attempt}} fehlgeschlagen: {{detail}}", + "redis.backend.error.connect_attempt_failed": "Verbindungsversuch {{attempt}} fehlgeschlagen: {{detail}}", + "redis.backend.error.sentinel_connect_failed": "Redis-Sentinel-Verbindung fehlgeschlagen: {{detail}}", + "redis.backend.error.cluster_connect_failed": "Redis-Cluster-Verbindung fehlgeschlagen: {{detail}}", + "redis.backend.error.connect_failed": "Redis-Verbindung fehlgeschlagen: {{detail}}", + "redis.backend.error.ssh_tunnel_create_failed": "SSH-Tunnel konnte nicht erstellt werden: {{detail}}", + "redis.backend.error.select_db_index_required": "Für den SELECT-Befehl ist ein Datenbankindex erforderlich", + "redis.backend.error.select_db_index_invalid": "Ungültiger Datenbankindex: {{value}}", + "redis.backend.error.select_db_index_out_of_range": "Der Datenbankindex muss zwischen {{min}} und {{max}} liegen", "redis_monitor.action.pause_refresh": "Aktualisierung pausieren", "redis_monitor.action.refresh_now": "Jetzt aktualisieren", "redis_monitor.action.resume_refresh": "Aktualisierung fortsetzen", @@ -3243,6 +3679,7 @@ "ai_chat.header.action.export": "Exportieren", "ai_chat.history.action.new_chat": "Neuen Chat starten", "ai_chat.history.default_session_title": "Neuer Chat", + "ai_chat.history.empty.no_history": "Noch kein Verlauf", "ai_chat.history.empty.no_matches": "Keine passenden Chats", "ai_chat.history.search.placeholder": "Verlauf suchen...", "ai_chat.history.title": "Chatverlauf", @@ -3287,9 +3724,21 @@ "ai_chat.welcome.suggestion.low_rows.with_context": "Warum hat {{table}} nur wenige Zeilen?", "ai_chat.input.action.send": "Senden", "ai_chat.input.action.stop": "Generierung stoppen", + "ai_chat.input.context.add": "Hinzufügen", + "ai_chat.input.context.current_count": "Aktueller Kontext · {{count}}", "ai_chat.input.context.connection_tooltip": "Aktueller Datenabfragekontext", + "ai_chat.input.context.label": "Verknüpfter Kontext", "ai_chat.input.context.memory_tooltip": "Aktuelle Speichernutzung der Sitzung. Bei Erreichen der Grenze von {{limit}} startet die automatische Komprimierung.", "ai_chat.input.context.tag_label": "Verknüpfter Kontext ({{count}})", + "ai_chat.input.context.selector.cancel": "Abbrechen", + "ai_chat.input.context.selector.confirm": "Ausgewählte Tabellen in den Kontext übernehmen", + "ai_chat.input.context.selector.database_placeholder": "Datenbank wechseln", + "ai_chat.input.context.selector.empty_no_match": "Keine Tabellen gefunden, die zu '{{searchText}}' passen", + "ai_chat.input.context.selector.empty_no_tables": "In der aktuellen Datenbank sind keine verknüpfbaren Tabellen verfügbar", + "ai_chat.input.context.selector.invert_selection": "Treffer-Auswahl umkehren", + "ai_chat.input.context.selector.search_placeholder": "Tabellennamen in der aktuellen Datenbank suchen...", + "ai_chat.input.context.selector.select_all": "Alle passenden Tabellen auswählen ({{count}})", + "ai_chat.input.context.selector.title": "Tabellenstrukturen als Kontext verknüpfen", "ai_chat.input.message.context_added": "{{count}} Tabellenstrukturen zum Kontext hinzugefügt", "ai_chat.input.message.context_load_failed": "Tabellenkontext konnte nicht geladen werden: {{detail}}", "ai_chat.input.message.context_removed": "{{count}} Tabellenstrukturen aus dem Kontext entfernt", @@ -3298,6 +3747,7 @@ "ai_chat.input.message.fetch_tables_failed": "Tabellen konnten nicht geladen werden: {{detail}}", "ai_chat.input.message.select_database_context_first": "Wählen Sie links eine Datenbank aus, bevor Sie Chatkontext anhängen", "ai_chat.input.message.selection_unchanged": "Die ausgewählten Tabellen wurden nicht geändert", + "ai_chat.input.message.context_sync_failed": "Der AI-Kontext konnte nicht synchronisiert werden: {{detail}}", "ai_chat.input.modal.empty_tables": "Keine Tabellen passen zu \"{{query}}\"", "ai_chat.input.modal.invert_matching": "Passende Ergebnisse umkehren", "ai_chat.input.modal.ok": "Ausgewählte Tabellen mit Kontext synchronisieren", @@ -3306,7 +3756,90 @@ "ai_chat.input.modal.switch_database.placeholder": "Datenbank wechseln", "ai_chat.input.modal.title": "Datenbanktabellen-Schemakontext anhängen", "ai_chat.input.model.placeholder": "Modell auswählen", - "ai_chat.input.placeholder": "Nachricht eingeben... (Enter zum Senden, Shift+Enter für Zeilenumbruch, / für Befehle)", + "ai_chat.input.placeholder": "Nachricht eingeben... ({{shortcut}}, Shift+Enter für Zeilenumbruch, / für Befehle)", + "ai_chat.input.placeholder_compact": "Nachricht eingeben... {{shortcut}} · / Befehle", + "ai_chat.input.shortcut.disabled": "Senden per Tastenkürzel deaktiviert", + "ai_chat.input.shortcut.send_with_combo": "{{shortcut}} zum Senden", + "ai_chat.mcp_client.install.status_tone.connected": "Verbunden", + "ai_chat.mcp_client.install.status_tone.update_required": "Update nötig", + "ai_chat.mcp_client.install.status_tone.remote_bridge": "Remote-Bridge", + "ai_chat.mcp_client.install.status_tone.status_error": "Statusfehler", + "ai_chat.mcp_client.install.status_tone.not_connected": "Nicht verbunden", + "ai_chat.mcp_client.install.state.connected": "Verbindungsstatus des externen Tools: mit diesem GoNavi verbunden", + "ai_chat.mcp_client.install.state.stale": "Verbindungsstatus des externen Tools: alte Konfiguration gefunden, Update nötig", + "ai_chat.mcp_client.install.state.error": "Verbindungsstatus des externen Tools: Lesen fehlgeschlagen", + "ai_chat.mcp_client.install.state.remote": "Verbindungsstatus des externen Tools: Remote-MCP-Bridge erforderlich", + "ai_chat.mcp_client.install.state.missing": "Verbindungsstatus des externen Tools: nicht verbunden", + "ai_chat.mcp_client.install.summary.connected": "{{label}} ist mit diesem GoNavi MCP verbunden und kann es direkt aufrufen.", + "ai_chat.mcp_client.install.summary.stale": "{{label}} hat bereits einen alten GoNavi-Eintrag. Nach dem Update zeigt er auf dieses GoNavi.", + "ai_chat.mcp_client.install.summary.error": "Der Verbindungsstatus für {{label}} konnte nicht gelesen werden. Aktualisiere zuerst die Erkennung.", + "ai_chat.mcp_client.install.summary.remote": "{{label}} läuft normalerweise in der Cloud oder auf einer anderen Maschine und benötigt eine Remote-MCP-Bridge, um dieses GoNavi aufzurufen.", + "ai_chat.mcp_client.install.summary.missing": "Dieses GoNavi MCP ist noch nicht mit {{label}} verbunden.", + "ai_chat.mcp_client.install.option.connected": "Dieses GoNavi MCP ist bereits mit diesem Client verbunden.", + "ai_chat.mcp_client.install.option.stale": "Ein alter GoNavi-Eintrag wurde erkannt. Aktualisiere ihn auf den aktuellen Installationspfad.", + "ai_chat.mcp_client.install.option.error": "Der Verbindungsstatus wirkt ungewöhnlich. Vor Änderungen zuerst aktualisieren.", + "ai_chat.mcp_client.install.option.remote": "Für Cloud-Agents: schema-only liest standardmäßig die GoNavi-Struktur, ohne Datenbankpasswörter zu kopieren oder execute_sql freizugeben.", + "ai_chat.mcp_client.install.option.missing": "Das aktuelle GoNavi MCP ist hier noch nicht verbunden.", + "ai_chat.mcp_client.install.detection.remote": "{{label}} läuft normalerweise nicht auf dieser Windows-Maschine. Eine lokale Erkennung des Befehls {{command}} ist nicht nötig; konfiguriere in der Cloud eine Remote-MCP-Bridge-URL.", + "ai_chat.mcp_client.install.detection.detected": "Lokaler Befehl {{command}} erkannt. Starte {{label}} nach Verbindung oder Update neu, um zu prüfen.", + "ai_chat.mcp_client.install.detection.not_detected": "Lokaler Befehl {{command}} wurde nicht erkannt. Wenn die CLI noch nicht in PATH ist, kannst du die Konfiguration für {{label}} trotzdem zuerst schreiben und später neu starten.", + "ai_chat.mcp_client.install.selected.connected": "Mit aktuellem GoNavi verbunden; keine erneute Aktion nötig", + "ai_chat.mcp_client.install.selected.stale": "Alter Verbindungseintrag vorhanden; auf den aktuellen GoNavi-Pfad aktualisieren", + "ai_chat.mcp_client.install.selected.error": "Statuslesen ist ungewöhnlich; Erkennung zuerst aktualisieren", + "ai_chat.mcp_client.install.selected.remote": "Remote-MCP-Bridge konfigurieren; Datenbankpasswörter bleiben auf der GoNavi-Maschine", + "ai_chat.mcp_client.install.selected.missing": "GoNavi MCP ist noch nicht verbunden", + "ai_chat.mcp_client.install.action.connected": "{{label}} ist verbunden; keine Neuinstallation nötig", + "ai_chat.mcp_client.install.action.update": "{{label}}-Verbindungskonfiguration aktualisieren", + "ai_chat.mcp_client.install.action.copy_remote": "Remote-Verbindungsanleitung für {{label}} kopieren", + "ai_chat.mcp_client.install.action.install": "In {{label}} installieren (externes Tool)", + "ai_chat.mcp_client.install.intro.title": "Dies verbindet GoNavi MCP mit Claude Code / Codex / OpenClaw / Hermans für externe Toolaufrufe. Es installiert kein Plugin in GoNavi selbst.", + "ai_chat.mcp_client.install.intro.description": "Claude Code und Codex schreiben lokale MCP-Konfiguration auf Benutzerebene. Cloud-Agents wie OpenClaw und Hermans verwenden eine Remote-Verbindungsanleitung, damit Datenbankpasswörter nicht in die Cloud kopiert werden.", + "ai_chat.mcp_client.install.repeat_avoidance": "Wenn dieses GoNavi bereits verbunden ist, wird die Hauptschaltfläche deaktiviert, um doppelte Schreibvorgänge zu vermeiden.", + "ai_chat.mcp_client.install.selector.title": "Externen Client verbinden", + "ai_chat.mcp_client.install.selector.description": "Wähle zuerst einen Zielclient. Lokale CLIs können die Konfiguration automatisch schreiben oder aktualisieren; Remote-Agents müssen über MCP-Bridge oder Tunnel auf das aktuelle GoNavi zugreifen und sollten keine Datenbankpasswörter speichern.", + "ai_chat.mcp_client.install.selector.aria_label": "Externen Client für GoNavi MCP auswählen", + "ai_chat.mcp_client.install.selector.choice_title": "Externen Client auswählen", + "ai_chat.mcp_client.install.selector.step.target.title": "Zielclient auswählen", + "ai_chat.mcp_client.install.selector.step.target.detail": "Lokale Claude/Codex-Clients können automatisch installiert werden. OpenClaw/Hermans verwenden eine Remote-Verbindungsanleitung.", + "ai_chat.mcp_client.install.selector.step.write.title": "Konfiguration schreiben oder kopieren", + "ai_chat.mcp_client.install.selector.step.write.detail": "Die automatische Installation ändert nur MCP-Konfiguration auf Benutzerebene. Remote-Agents kopieren die Bridge-Anleitung.", + "ai_chat.mcp_client.install.selector.step.restart.title": "Ziel neu starten oder konfigurieren", + "ai_chat.mcp_client.install.selector.step.restart.detail": "Lokale CLI nach dem Neustart prüfen. Cloud-Agents nach Konfiguration der Remote-MCP-URL prüfen.", + "ai_chat.mcp_client.install.selector.hint.active_remote": "Ausgewählt. Die Remote-Verbindungsanleitung wird kopiert.", + "ai_chat.mcp_client.install.selector.hint.active_local": "Ausgewählt. Nur dieser Client wird geschrieben oder aktualisiert.", + "ai_chat.mcp_client.install.selector.hint.inactive_remote": "Klicken, um die Remote-Verbindungsmethode anzuzeigen.", + "ai_chat.mcp_client.install.selector.hint.inactive_local": "Klicken, um zu diesem Client zu wechseln.", + "ai_chat.mcp_client.install.status.title": "Status des ausgewählten Clients", + "ai_chat.mcp_client.install.status.current_target": "Aktueller Zielclient: {{label}}", + "ai_chat.mcp_client.install.status.no_client": "Kein Client ausgewählt", + "ai_chat.mcp_client.install.status.current_state": "Aktueller Status: {{status}}", + "ai_chat.mcp_client.install.status.remote_boundary": "Remote-Verbindungsgrenze: Datenbankverbindungsdaten und Passwörter bleiben in Windows GoNavi. Cloud-Agents lesen Verbindungszusammenfassungen, Tabellen und DDL standardmäßig über schema-only MCP-Tools; execute_sql wird nicht registriert. Für maschinenübergreifenden Zugriff GoNavi Streamable HTTP mit token, Tunnel oder Reverse Proxy verwenden.", + "ai_chat.mcp_client.install.status.cli_prefix": "CLI-Erkennung: {{status}}", + "ai_chat.mcp_client.install.status.cli.remote": "Remote-Agent benötigt keine lokale Erkennung des Befehls {{command}}", + "ai_chat.mcp_client.install.status.cli.detected": "{{command}} erkannt", + "ai_chat.mcp_client.install.status.cli.not_detected": "{{command}} wurde nicht erkannt; Konfiguration kann trotzdem zuerst geschrieben werden", + "ai_chat.mcp_client.install.status.command_path": "Befehlspfad: {{path}}", + "ai_chat.mcp_client.install.status.detection_result": "Erkennungsergebnis: {{message}}", + "ai_chat.mcp_client.install.status.detection_missing": "Kein Verbindungsstatus erkannt", + "ai_chat.mcp_client.install.status.config_file": "Konfigurationsdatei: {{path}}", + "ai_chat.mcp_client.install.status.launch_command": "Startbefehl: {{command}}", + "ai_chat.mcp_client.install.status.refresh": "Status aktualisieren", + "ai_chat.mcp_client.install.status.copy_config": "Konfigurationspfad kopieren", + "ai_chat.mcp_client.install.status.copy_command": "Startbefehl kopieren", + "ai_chat.mcp_client.install.message.refresh_failed": "Client-Installationsstatus konnte nicht aktualisiert werden", + "ai_chat.mcp_client.install.message.remote_guide_copied": "Remote-Verbindungsanleitung für {{label}} kopiert", + "ai_chat.mcp_client.install.message.remote_guide_copy_failed": "Remote-Verbindungsanleitung für {{label}} konnte nicht kopiert werden", + "ai_chat.mcp_client.install.message.already_connected": "{{label}} ist bereits mit dem aktuellen GoNavi MCP verbunden. Kein erneutes Schreiben nötig.", + "ai_chat.mcp_client.install.message.codex_not_supported": "Diese Version unterstützt die automatische Codex MCP-Installation noch nicht", + "ai_chat.mcp_client.install.message.claude_not_supported": "Diese Version unterstützt die automatische Claude Code MCP-Installation noch nicht", + "ai_chat.mcp_client.install.message.install_success": "MCP-Konfiguration auf Benutzerebene für {{label}} geschrieben", + "ai_chat.mcp_client.install.message.install_failed": "Installation von {{label}} MCP fehlgeschlagen", + "ai_chat.mcp_client.install.message.config_path_missing": "Kein Konfigurationsdateipfad zum Kopieren verfügbar", + "ai_chat.mcp_client.install.message.config_path_copied": "Konfigurationsdateipfad kopiert", + "ai_chat.mcp_client.install.message.config_path_copy_failed": "Konfigurationsdateipfad konnte nicht kopiert werden", + "ai_chat.mcp_client.install.message.launch_command_missing": "Kein Startbefehl zum Kopieren verfügbar", + "ai_chat.mcp_client.install.message.launch_command_copied": "Startbefehl kopiert", + "ai_chat.mcp_client.install.message.launch_command_copy_failed": "Startbefehl konnte nicht kopiert werden", "ai_chat.input.slash.diff.desc": "Zwei Tabellen vergleichen und Änderungen erzeugen", "ai_chat.input.slash.diff.label": "🔄 Tabellenvergleich", "ai_chat.input.slash.diff.prompt": "Vergleiche die Strukturunterschiede zwischen diesen zwei Tabellen und erzeuge ALTER-Anweisungen für die Migration von der alten zur neuen Version:", @@ -3331,8 +3864,149 @@ "ai_chat.input.slash.sql.desc": "Anforderungen beschreiben und Anweisungen erzeugen", "ai_chat.input.slash.sql.label": "📝 SQL erzeugen", "ai_chat.input.slash.sql.prompt": "Erzeuge SQL aus den folgenden Anforderungen:", + "ai_chat.input.slash.category.generate.title": "SQL-Generierung", + "ai_chat.input.slash.category.generate.description": "SQL, Testdaten oder Migrationsentwürfe direkt erzeugen.", + "ai_chat.input.slash.category.review.title": "Strukturprüfung", + "ai_chat.input.slash.category.review.description": "SQL erklären sowie Tabellendesign und Indexstrategien prüfen.", + "ai_chat.input.slash.category.diagnose.title": "Diagnose-Sonden", + "ai_chat.input.slash.category.diagnose.description": "Zuerst integrierte Sonden ausführen, um den tatsächlichen Status von AI, MCP und der letzten SQL-Aktivitäten zu prüfen.", + "ai_chat.input.slash.empty.title": "Keine passenden Slash-Befehle", + "ai_chat.input.slash.empty.description": "Probiere zuerst diese häufigen Einstiege aus, um schnell zu SQL-Generierung, AI-Gesundheitscheck oder MCP-Diagnose zu springen.", + "ai_chat.input.slash.empty.summary": "Derzeit sind {{count}} Slash-Befehle verfügbar. Suche nach Befehlsname, Beschreibung oder Schlüsselwort.", + "ai_chat.input.slash.health.label": "🩺 AI-Gesundheitscheck", + "ai_chat.input.slash.health.desc": "Gesundheitsprüfungen für die aktuelle AI-Konfiguration ausführen", + "ai_chat.input.slash.health.prompt": "Führe zuerst inspect_ai_setup_health aus. Prüfe die aktuelle GoNavi AI-Konfiguration vollständig und fasse anschließend blockers, warnings und nextActions zusammen.", + "ai_chat.input.slash.tools.label": "🧰 Tool-Katalog", + "ai_chat.input.slash.tools.desc": "Per Schlüsselwort die passende integrierte Sonde wählen", + "ai_chat.input.slash.tools.prompt": "Führe zuerst inspect_ai_tool_catalog aus. Filtere anhand meiner Frage-Schlüsselwörter empfohlene Abläufe, integrierte Tool-Parameterhinweise und die aktuelle MCP-Tool-Zusammenfassung und sage mir dann, welches Tool ich als Nächstes aufrufen sollte. Schlüsselwörter:", + "ai_chat.input.slash.budget.label": "🧠 Kontextbudget", + "ai_chat.input.slash.budget.desc": "Größe von Nachrichten, DDL, MCP schema und Skills prüfen", + "ai_chat.input.slash.budget.prompt": "Führe zuerst inspect_ai_context_budget aus. Prüfe die Größenrisiken der aktuellen Gesprächsnachrichten, Tool-Ergebnisse, DDL, MCP schema, Prompts und Skills und sage mir danach, welche Kontexte ich eingrenzen sollte.", + "ai_chat.input.slash.hotspots.label": "🧱 Code-Hotspots", + "ai_chat.input.slash.hotspots.desc": "Kandidaten für die Aufteilung großer Dateien und Testumfang prüfen", + "ai_chat.input.slash.hotspots.prompt": "Führe zuerst inspect_codebase_hotspots aus. Lies die aktuellen Hotspots großer Frontend-Dateien in GoNavi, empfohlene Aufteilungsscheiben und Testziele und sage mir dann, welche Datei ich als Nächstes am besten aufteilen sollte, welche Grenze sinnvoll ist und welche Prüfungen ich ausführen muss. Schlüsselwörter:", + "ai_chat.input.slash.mcp.label": "🪛 MCP-Setup prüfen", + "ai_chat.input.slash.mcp.desc": "MCP-Dienste und Status externer Clients prüfen", + "ai_chat.input.slash.mcp.prompt": "Führe zuerst inspect_mcp_setup aus. Prüfe die aktuellen MCP-Dienste, die Ergebnisse der Tool-Erkennung sowie den Verbindungsstatus der lokalen Claude Code / Codex-Clients und der entfernten OpenClaw / Hermans-Agents.", + "ai_chat.input.slash.mcpfail.label": "🧯 MCP-Laufzeitfehler", + "ai_chat.input.slash.mcpfail.desc": "Aktuelle Fehlerprotokolle zu MCP-Start, Erkennung und Aufrufen lesen", + "ai_chat.input.slash.mcpfail.prompt": "Führe zuerst inspect_mcp_runtime_failures aus. Lies die letzten Fehlerprotokolle zu MCP-Start, Tool-Erkennung, Tool-Aufrufen, stdio, Docker oder HTTP MCP und bewerte die Ursache samt nextActions zusammen mit der aktuellen MCP-Dienstkonfiguration. Schlüsselwort oder Dienstname:", + "ai_chat.input.slash.mcpadd.label": "🧭 Anleitung zum Hinzufügen von MCP", + "ai_chat.input.slash.mcpadd.desc": "Sehen, wie command, args, env und Vorlagen ausgefüllt werden", + "ai_chat.input.slash.mcpadd.prompt": "Führe zuerst inspect_mcp_authoring_guide aus. Wenn ich einen vollständigen Startbefehl oder Entwurf eingefügt habe, rufe zusätzlich inspect_mcp_draft auf, um Felder und Validierungsprobleme zu simulieren. Nutze anschließend auch inspect_mcp_setup und sage mir, wie command, args, env und timeout beim Hinzufügen eines GoNavi MCP-Dienstes ausgefüllt werden sollten und welche Vorlage am besten passt.", + "ai_chat.input.slash.mcpdraft.label": "🧪 MCP-Entwurfsprüfung", + "ai_chat.input.slash.mcpdraft.desc": "Prüfen, wie ein MCP-Startbefehl aufgeteilt werden sollte", + "ai_chat.input.slash.mcpdraft.prompt": "Führe zuerst inspect_mcp_draft aus, um den von mir gelieferten MCP fullCommand oder command/args/env/timeout-Entwurf zu prüfen. Gib die automatische Aufteilung, die Startvorschau, suggestedServerSeed, Fehler, Warnungen und nextActions zurück; falls noch Feldhinweise fehlen, rufe zusätzlich inspect_mcp_authoring_guide auf.", + "ai_chat.input.slash.mcptool.label": "🧩 MCP-Tool-Argumente", + "ai_chat.input.slash.mcptool.desc": "MCP-Tool-schema und arguments-Format prüfen", + "ai_chat.input.slash.mcptool.prompt": "Führe zuerst inspect_mcp_setup aus, um den aktuell entdeckten MCP-Tool-Alias zu finden. Wenn ich bereits einen Tool-Namen oder ein Schlüsselwort genannt habe, rufe zusätzlich inspect_mcp_tool_schema auf, um das passende inputSchema zu lesen, und erkläre mir Pflichtparameter, Feldtypen, Enum-Werte, verschachtelte Pfade und wie das arguments-JSON geschrieben werden sollte.", + "ai_chat.input.slash.connfail.label": "🧯 Sonde für Verbindungsfehler", + "ai_chat.input.slash.connfail.desc": "Letzte Verbindungsfehler, Abkühlphasen und Validierungsfehler zusammenfassen", + "ai_chat.input.slash.connfail.prompt": "Führe zuerst inspect_recent_connection_failures aus. Fasse die echten Log-Erkenntnisse zu den letzten Datenbankverbindungsfehlern, Verbindungsabkühlungen, Validierungsfehlern und SSH-Tunnel-Ausnahmen zusammen. Wenn bereits eine klare Adresse oder ein Typ bekannt ist, nutze zusätzlich inspect_current_connection oder inspect_saved_connections, um den Bereich weiter einzugrenzen.", + "ai_chat.input.slash.shortcuts.label": "⌨️ Shortcut-Sonde", + "ai_chat.input.slash.shortcuts.desc": "Aktuelle Win/Mac-Tastenkonfiguration lesen", + "ai_chat.input.slash.shortcuts.prompt": "Führe zuerst inspect_shortcuts aus. Erkläre mir die aktuelle GoNavi-Shortcut-Konfiguration, insbesondere wie SQL ausführen, Ergebnisbereich wechseln, AI-Panel öffnen und AI-Nachrichten senden auf der aktuellen Plattform und der anderen Plattform belegt sind und ob Standardwerte geändert wurden.", + "ai_chat.input.slash.applog.label": "🪵 App-Protokolle", + "ai_chat.input.slash.applog.desc": "Letzte GoNavi-Anwendungsprotokolle prüfen", + "ai_chat.input.slash.applog.prompt": "Führe zuerst inspect_app_logs aus. Prüfe die letzten Fehler und Warnungen in den GoNavi-Anwendungsprotokollen. Wenn ich Verbindungsfehler, MCP-Startfehler, Startausnahmen oder gonavi.log erwähne, filtere bevorzugt mit diesen Schlüsselwörtern weiter.", + "ai_chat.input.slash.airender.label": "🧯 AI-Renderfehler", + "ai_chat.input.slash.airender.desc": "Den letzten Fehler beim Rendern von AI-Nachrichten lesen", + "ai_chat.input.slash.airender.prompt": "Führe zuerst inspect_ai_last_render_error aus. Sage mir, welche Nachricht im letzten AI-Renderfehlerprotokoll fehlgeschlagen ist, was die Fehlerzusammenfassung sagt und was ich als Nächstes prüfen sollte.", + "ai_chat.input.slash.safety.label": "🛡️ Schreibsicherheit", + "ai_chat.input.slash.safety.desc": "Schreibgrenzen und allowMutating bestätigen", + "ai_chat.input.slash.safety.prompt": "Führe zuerst inspect_ai_safety aus. Sage mir die aktuellen Schreibgrenzen für AI und GoNavi MCP, ob sie schreibgeschützt sind und ob execute_sql allowMutating benötigt.", + "ai_chat.input.slash.activity.label": "🕘 Letzte SQL-Aktivität", + "ai_chat.input.slash.activity.desc": "Letzte Ausführungen, Fehler und Hotspots zusammenfassen", + "ai_chat.input.slash.activity.prompt": "Führe zuerst inspect_recent_sql_activity aus. Fasse die letzten SQL-Aktivitäten, Fehler-Hotspots und die wichtigsten Lese-/Schreibmuster zusammen.", + "ai_chat.input.slash.tx.label": "🔁 SQL-Transaktionsstatus", + "ai_chat.input.slash.tx.desc": "Commit-Modus des SQL-Editors und offene Transaktionen prüfen", + "ai_chat.input.slash.tx.prompt": "Führe zuerst inspect_sql_editor_transaction aus. Sage mir die aktuelle Semantik der DML-gesteuerten Transaktionen im SQL-Editor, die Einstellungen für manuelles/automatisches Commit, ob die aktiven SQL-Tabs in eine Transaktion gehen, ob offene Transaktionen vorhanden sind und ob ich als Nächstes committen, zurückrollen oder weiter ausführen sollte.", + "ai_chat.input.slash.query.keywords": "abfrage|natürliche sprache|daten suchen", + "ai_chat.input.slash.sql.keywords": "sql|generieren|abfragesatz", + "ai_chat.input.slash.mock.keywords": "mock|testdaten|insert", + "ai_chat.input.slash.diff.keywords": "diff|migration|alter", + "ai_chat.input.slash.explain.keywords": "erklären|sql|logik", + "ai_chat.input.slash.optimize.keywords": "optimieren|index|leistung", + "ai_chat.input.slash.schema.keywords": "schema|tabellenstruktur|design", + "ai_chat.input.slash.index.keywords": "index|langsame abfrage|leistung", + "ai_chat.input.slash.health.keywords": "health|zustandsprüfung|ai-konfiguration|probe", + "ai_chat.input.slash.tools.keywords": "toolkatalog|integrierte tools|toolcatalog|parameterhinweise|arguments|probe-route", + "ai_chat.input.slash.budget.keywords": "kontext|context|umfang|budget|langsam|falsche antworten|schema zu groß|toolergebnisse", + "ai_chat.input.slash.hotspots.keywords": "große datei|aufgebläht|aufteilen|refactoring|hotspots|code-hotspots|tausende zeilen", + "ai_chat.input.slash.mcp.keywords": "mcp|codex|claude|openclaw|hermans|externer client", + "ai_chat.input.slash.mcpfail.keywords": "mcpfail|mcp-fehler|laufzeitfehler|null tools gefunden|stdio|docker mcp|http mcp|startfehler|aufruffehler", + "ai_chat.input.slash.mcpadd.keywords": "mcp hinzufügen|command|args|env|vorlage", + "ai_chat.input.slash.mcpdraft.keywords": "mcp-entwurf|mcp-validierung|fullcommand|startbefehl|argumentaufteilung|command|args|env", + "ai_chat.input.slash.mcptool.keywords": "mcp-tool|mcp-tool-argumente|schema|arguments|parameter|toolaufruf|inputschema", + "ai_chat.input.slash.connfail.keywords": "verbindungsfehler|cooldown|validierungsfehler|ssh|mysql", + "ai_chat.input.slash.shortcuts.keywords": "tastenkürzel|shortcuts|ergebnisbereich|mac|windows", + "ai_chat.input.slash.applog.keywords": "logs|gonavi.log|mcp-fehler|verbindungsfehler|startausnahme", + "ai_chat.input.slash.airender.keywords": "renderfehler|leere blase|ai-nachricht|render|weißer block", + "ai_chat.input.slash.safety.keywords": "sicherheit|nur lesen|allowmutating|ddl|dml", + "ai_chat.input.slash.activity.keywords": "activity|sql-logs|letzte ausführungen|fehler", + "ai_chat.input.slash.tx.keywords": "transaktion|transaction|commit|automatischer commit|manueller commit|offene transaktion|dml", "ai_chat.input.tooltip.attach_table_context": "Datenbanktabellenkontext anhängen", + "ai_chat.input.tooltip.slash_command": "Slash-Befehle", + "ai_chat.input.tooltip.upload_attachment": "Anhang hochladen (Bilder, Markdown, Word, Excel, PDF, Text)", "ai_chat.input.tooltip.upload_image": "Bild oder Screenshot hochladen", + "ai_chat.input.attachment.remove_file": "Anhang entfernen", + "ai_chat.input.attachment.remove_image": "Bild entfernen", + "ai_chat.input.attachment.kind.text": "Text", + "ai_chat.input.attachment.kind.image": "Bild", + "ai_chat.input.attachment.kind.file": "Datei", + "ai_chat.input.attachment.warning.pdf_partial_text": "Für die PDF wurde eine leichte Textextraktion verwendet; Inhalte aus Scans oder mit komprimierten Schriftarten lassen sich möglicherweise nicht vollständig lesen.", + "ai_chat.input.attachment.warning.pdf_no_text": "Aus der PDF konnte kein lesbarer Text extrahiert werden. Wenn es sich um einen Scan oder eine PDF mit komplexer Kodierung handelt, kopieren Sie den Inhalt bitte vor dem Senden.", + "ai_chat.input.attachment.warning.legacy_office_partial_text": "Für ältere Office-Binärformate wird nur eine leichte Textfragment-Extraktion verwendet; konvertieren Sie die Datei vor dem Hochladen in docx/xlsx, um vollständigere Inhalte zu erhalten.", + "ai_chat.input.attachment.warning.too_large": "Die Datei überschreitet {{size}}; Dateiinformationen wurden angehängt, der Inhalt wurde jedoch nicht gelesen.", + "ai_chat.input.attachment.warning.unsupported_type": "Dieser Dateityp wurde angehängt, aber der Textinhalt wurde noch nicht extrahiert. Wenn das Modell den Inhalt analysieren soll, verwenden Sie markdown, txt, docx, xlsx oder pdf.", + "ai_chat.input.attachment.warning.extract_failed": "Das Extrahieren des Anhanginhalts ist fehlgeschlagen: {{detail}}", + "ai_chat.input.attachment.excel.worksheet_header": "[Arbeitsblatt: {{sheetName}}]", + "ai_chat.input.attachment.kind.markdown": "Markdown", + "ai_chat.input.attachment.kind.pdf": "PDF", + "ai_chat.input.attachment.kind.word": "Word", + "ai_chat.input.attachment.kind.excel": "Excel", + "ai_chat.input.attachment.kind.document": "Datei", + "ai_chat.input.attachment.prompt.content_truncated": "[Anhangtext gekürzt]", + "ai_chat.input.attachment.prompt.heading": "### Anhang {{index}}: {{name}}", + "ai_chat.input.attachment.prompt.kind": "- Typ: {{kind}}", + "ai_chat.input.attachment.prompt.mime": "- MIME: {{mimeType}}", + "ai_chat.input.attachment.prompt.size": "- Größe: {{size}}", + "ai_chat.input.attachment.prompt.extract_warning": "- Extraktionshinweis: {{message}}", + "ai_chat.input.attachment.prompt.text_truncated": "- Extraktionshinweis: Der Textinhalt war zu lang und wurde vor dem Senden gekürzt.", + "ai_chat.input.attachment.prompt.no_text": "Es wurde kein sendbarer Anhangtext extrahiert.", + "ai_chat.input.attachment.prompt.default_user_content": "Bitte bearbeiten Sie das Anliegen auf Basis der folgenden Anhanginhalte weiter.", + "ai_chat.input.attachment.prompt.wrapper_start": "", + "ai_chat.input.attachment.prompt.wrapper_end": "", + "ai_chat.input.attachment.message.warning": "{{name}}: {{message}}", + "ai_chat.input.attachment.message.read_failed": "Der Anhang {{name}} konnte nicht gelesen werden: {{detail}}", + "ai_chat.input.status.label.not_ready": "Nicht bereit", + "ai_chat.input.status.label.needs_fix": "Muss behoben werden", + "ai_chat.input.status.label.loading": "Wird geladen", + "ai_chat.input.status.label.model_required": "Modell erforderlich", + "ai_chat.input.status.label.ready": "Bereit", + "ai_chat.input.status.action.open_settings": "AI-Einstellungen öffnen", + "ai_chat.input.status.action.fix_provider": "Anbieterkonfiguration korrigieren", + "ai_chat.input.status.action.reload_models": "Modelle neu laden", + "ai_chat.input.status.dismiss_aria_label": "AI-Statushinweis schließen", + "ai_chat.input.status.provider_fallback_name": "Aktueller Anbieter", + "ai_chat.input.status.issue.missing_secret": "API-Schlüssel", + "ai_chat.input.status.issue.missing_base_url": "Endpunkt-URL", + "ai_chat.input.status.issue.missing_selected_model": "Modell", + "ai_chat.input.status.issue.separator": ", ", + "ai_chat.input.status.missing_provider.title.none": "Kein Anbieter verfügbar", + "ai_chat.input.status.missing_provider.description.none": "Fügen Sie zuerst in den AI-Einstellungen einen Modellanbieter hinzu und aktivieren Sie ihn.", + "ai_chat.input.status.missing_provider.title.unselected": "Anbieter sind konfiguriert, aber aktuell ist keiner aktiv", + "ai_chat.input.status.missing_provider.description.unselected": "Wählen Sie vor dem Senden in den AI-Einstellungen einen aktiven Anbieter aus.", + "ai_chat.input.status.provider_incomplete.title": "{{provider}} fehlt noch {{issues}}", + "ai_chat.input.status.provider_incomplete.description": "Ergänzen Sie vor dem Senden die Anbieterkonfiguration, damit die Anfrage nicht sofort fehlschlägt.", + "ai_chat.input.status.missing_model.title.loading": "Modellliste für {{provider}} wird geladen", + "ai_chat.input.status.missing_model.title.select": "Wählen Sie zuerst ein Modell für {{provider}} aus", + "ai_chat.input.status.missing_model.description.available": "Derzeit sind {{count}} auswählbare Modelle verfügbar. Nach der Auswahl können Sie senden.", + "ai_chat.input.status.missing_model.description.empty": "Wenn die Liste leer bleibt, prüfen Sie den Anbieter-Endpunkt, den API-Schlüssel und die Modellberechtigungen.", + "ai_chat.input.status.ready.title": "AI ist bereit: {{provider}} / {{model}}", + "ai_chat.input.status.ready.description.with_context": "Aktuell sind {{count}} Tabellenstruktur-Kontexte verknüpft. Sie können jetzt senden.", + "ai_chat.input.status.ready.description.with_connection": "Die aktuelle Verbindung ist ausgewählt. Verknüpfen Sie zusätzlich Tabellenstruktur-Kontexte, um präzisere Datenbankhinweise zu erhalten.", + "ai_chat.input.status.ready.description.no_context": "Sie können jetzt senden. Wählen Sie eine Verbindung aus oder verknüpfen Sie Tabellenstruktur-Kontexte, um präzisere Datenbankhinweise zu erhalten.", "ai_chat.tools.mcp_fallback_description": "MCP-Tool {{toolName}} von {{serverName}}", "ai_chat.composer_notice.missing_model.description": "Öffnen Sie unten die Modellauswahl und wählen Sie ein Modell. Wenn die Liste leer ist, prüfen Sie Anbieter-Endpunkt und API Key.", "ai_chat.composer_notice.missing_model.title": "Zuerst ein Modell auswählen", @@ -3348,6 +4022,11 @@ "ai_chat.message.action.retry": "Aus der vorherigen Benutzernachricht neu generieren", "ai_chat.message.action.copy_error_raw": "Originalfehler kopieren", "ai_chat.message.action.copied_error_raw": "Originalfehler kopiert", + "ai_chat.message.render_error.title": "Diese AI-Nachricht konnte nicht gerendert werden und wurde isoliert", + "ai_chat.message.render_error.body": "Der restliche Verlauf bleibt nutzbar. Sie können diese fehlerhafte Nachricht löschen, bevor Sie fortfahren.", + "ai_chat.message.render_error.unknown": "Unbekannter Renderfehler", + "ai_chat.message.render_error.retry": "Rendern erneut versuchen", + "ai_chat.message.render_error.delete": "Diese Nachricht löschen", "ai_chat.message.role.user": "Sie", "ai_chat.message.image_alt": "Angehängtes Bild {{index}}", "ai_chat.message.code.copy": "Code kopieren", @@ -3376,12 +4055,553 @@ "ai_chat.message.tool_result.title": "Prüfergebnis ({{name}})", "ai_chat.message.tool_result.char_count": "{{count}} Zeichen", "ai_chat.message.tool_result.no_data": "Keine Daten", + "ai_chat.message.tool_call.inspect_ai_runtime": "Aktuellen AI-Laufzeitstatus lesen", + "ai_chat.message.tool_call.inspect_ai_safety": "Aktuelle AI-Sicherheitsgrenze lesen", + "ai_chat.message.tool_call.inspect_ai_providers": "Aktuelle AI-Anbieter- und Modellkonfiguration lesen", + "ai_chat.message.tool_call.inspect_ai_chat_readiness": "Aktuellen AI-Chat-Vorbereitungsstatus lesen", + "ai_chat.message.tool_call.inspect_ai_tool_catalog": "AI-Toolkatalog und Parameterhinweise lesen", + "ai_chat.message.tool_call.inspect_ai_support_bundle": "AI-Supportpaket zur Fehlerdiagnose erzeugen", + "ai_chat.message.tool_call.inspect_mcp_setup": "Aktuellen MCP-Konfigurationsstatus lesen", + "ai_chat.message.tool_call.inspect_mcp_runtime_failures": "MCP-Laufzeitfehler diagnostizieren", + "ai_chat.message.tool_call.inspect_mcp_authoring_guide": "MCP-Erstellungsleitfaden lesen", + "ai_chat.message.tool_call.inspect_mcp_draft": "MCP-Entwurf validieren", + "ai_chat.message.tool_call.inspect_mcp_tool_schema": "MCP-Toolparameter-schema lesen", + "ai_chat.message.tool_call.inspect_ai_guidance": "Aktuelle AI-Prompt- und Skill-Konfiguration lesen", "ai_chat.message.tool_call.get_connections": "Verfügbare Verbindungsinformationen laden", "ai_chat.message.tool_call.get_databases": "Datenbankliste scannen", "ai_chat.message.tool_call.get_tables": "Tabellenstrukturinformationen analysieren", - "ai_chat.message.tool_call.get_columns": "Spaltenliste lesen", - "ai_chat.message.tool_call.get_table_ddl": "CREATE TABLE-Anweisung lesen", - "ai_chat.message.tool_call.execute_sql": "SQL-Abfrage ausführen", + "ai_chat.message.tool_call.get_all_columns": "Spalten über Tabellen hinweg zusammenfassen", + "ai_chat.message.tool_call.get_columns": "Echte Spaltendefinitionen prüfen", + "ai_chat.message.tool_call.get_indexes": "Indexdefinitionen prüfen", + "ai_chat.message.tool_call.get_foreign_keys": "Fremdschlüsselbeziehungen zuordnen", + "ai_chat.message.tool_call.get_triggers": "Triggerlogik prüfen", + "ai_chat.message.tool_call.get_table_ddl": "CREATE TABLE-Anweisung extrahieren", + "ai_chat.message.tool_call.inspect_table_bundle": "Vollständigen Tabellenstruktur-Snapshot abrufen", + "ai_chat.message.tool_call.inspect_database_bundle": "Datenbankstrukturübersicht abrufen", + "ai_chat.inspection.app_health.last_render_error.empty_summary": "Es wurden noch keine Rendering-Fehler für AI-Nachrichten aufgezeichnet.", + "ai_chat.inspection.last_render_error.empty_summary": "Es wurden noch keine Rendering-Fehler für AI-Nachrichten aufgezeichnet.", + "ai_chat.inspection.last_render_error.empty_next_action.reproduce": "Wenn der Nutzer eine leere AI-Nachricht, einen weißen Block oder einen lokalen Rendering-Fehler meldet, reproduziere das Problem und lies diesen Snapshot erneut.", + "ai_chat.inspection.last_render_error.empty_next_action.inspect_health": "Wenn das gesamte AI-Panel fehlschlägt, kombiniere dies mit inspect_ai_setup_health und inspect_app_logs.", + "ai_chat.inspection.last_render_error.recorded_summary": "Ein aktueller Rendering-Fehler einer AI-Nachricht wurde aufgezeichnet, inklusive Nachricht, Rendering-Pfad und Stack-Zusammenfassung für die Diagnose.", + "ai_chat.inspection.last_render_error.next_action.match_message": "Gleiche messageId und contentPreview mit der aktuellen Unterhaltung ab, um die betroffene Antwortblase zu identifizieren.", + "ai_chat.inspection.last_render_error.next_action.narrow_scope": "Wenn die Eingrenzung weitergehen muss, vergleiche die letzte Nutzereingabe, Tool-Ergebnisse und den zugehörigen Komponentencode.", + "ai_chat.inspection.app_health.log_reading_unavailable": "Die aktuelle Laufzeitumgebung stellt keine Log-Lesefunktion bereit", + "ai_chat.inspection.app_health.app_log.unread": "GoNavi-Anwendungslogs sind nicht lesbar: {{detail}}", + "ai_chat.inspection.app_health.connection_failures.unread": "Logs zu Verbindungsfehlern sind nicht lesbar: {{detail}}", + "ai_chat.inspection.app_health.warning.app_log_unread": "GoNavi-Anwendungslogs können aktuell nicht gelesen werden; für Startausnahmen und MCP-/Verbindungsfehler fehlen Log-Belege", + "ai_chat.inspection.app_health.next_action.enable_app_log_reading": "Prüfe, ob die aktuelle Laufzeit gonavi.log lesen kann, und rufe danach inspect_app_logs für Log-Details auf", + "ai_chat.inspection.app_health.warning.app_log_errors": "Die letzten Anwendungslogs enthalten {{count}} ERROR-Einträge; inspect_app_logs sollte zuerst geprüft werden", + "ai_chat.inspection.app_health.next_action.inspect_app_log_errors": "Rufe inspect_app_logs auf, um die letzten rohen ERROR-/WARN-Zeilen zu prüfen und zu klären, ob AI, MCP oder Datenbankverbindungen betroffen sind", + "ai_chat.inspection.app_health.warning.app_log_warnings": "Die letzten Anwendungslogs enthalten {{count}} WARN-Einträge; prüfe, ob es bekannte ignorierbare Warnungen sind", + "ai_chat.inspection.app_health.next_action.inspect_app_log_warnings": "Wenn der Nutzer Instabilität meldet, rufe zuerst inspect_app_logs auf und prüfe, ob WARN-Zeilen bei AI/MCP/Verbindungen gehäuft auftreten", + "ai_chat.inspection.app_health.warning.connection_failures_unread": "Logs zu Verbindungsfehlern können aktuell nicht gelesen werden; für Datenbank-Verbindungscooldowns und Validierungsfehler fehlen strukturierte Belege", + "ai_chat.inspection.app_health.warning.connection_failures_recent": "Kürzlich wurden {{count}} Verbindungsfehler-/Cooldown-Einträge erkannt", + "ai_chat.inspection.app_health.next_action.inspect_recent_connection_failures": "Rufe inspect_recent_connection_failures auf, um die neueste Ursache des Verbindungsfehlers zu prüfen, und entscheide danach, ob die aktuelle Verbindung oder gespeicherte Verbindungskonfiguration geprüft werden muss", + "ai_chat.inspection.app_health.warning.no_workspace_tabs": "Im aktuellen Arbeitsbereich sind keine Tabs geöffnet; AI hat keinen direkt lesbaren aktiven Editor-Kontext", + "ai_chat.inspection.app_health.next_action.open_sql_tab": "Um das aktuelle SQL zu analysieren, öffne oder wähle zuerst den Ziel-SQL-Tab und rufe danach inspect_active_tab auf", + "ai_chat.inspection.app_health.warning.last_render_error": "Kürzlich wurde ein Rendering-Fehler einer AI-Nachricht aufgezeichnet, der Antwortblasen oder Markdown-Rendering beeinflussen kann", + "ai_chat.inspection.app_health.next_action.inspect_last_render_error": "Rufe inspect_ai_last_render_error auf, um messageId, Inhaltsvorschau und Komponentenstack des letzten Rendering-Fehlers zu prüfen", + "ai_chat.inspection.app_health.message.ready": "Die AI-Anwendungszustandsübersicht ist bestanden; AI-Konfiguration, Logs, Verbindungsfehler und Arbeitsbereichskontext zeigen keine offensichtlichen Probleme", + "ai_chat.inspection.app_health.message.blocked": "Der AI-Anwendungszustand hat {{count}} Blocker; behebe zuerst Anbieter- und Sendevoraussetzungen", + "ai_chat.inspection.app_health.message.degraded": "Der AI-Anwendungszustand enthält Laufzeit-Anomaliesignale; prüfe zuerst Logs oder Verbindungsfehleraufzeichnungen", + "ai_chat.inspection.app_health.message.needs_attention": "Der AI-Anwendungszustand ist insgesamt nutzbar, hat aber noch {{count}} Empfehlungen", + "ai_chat.inspection.connection_failures.category.cooldown": "Verbindungs-Cooldown", + "ai_chat.inspection.connection_failures.category.parameter_compatibility": "Verbindungsparameter-/Kompatibilitätsproblem", + "ai_chat.inspection.connection_failures.category.validation": "Verbindungsvalidierung fehlgeschlagen", + "ai_chat.inspection.connection_failures.category.authentication": "Authentifizierung fehlgeschlagen", + "ai_chat.inspection.connection_failures.category.timeout": "Verbindungs-Timeout", + "ai_chat.inspection.connection_failures.category.network": "Netzwerk nicht erreichbar", + "ai_chat.inspection.connection_failures.category.ssh": "SSH-Tunnel fehlgeschlagen", + "ai_chat.inspection.connection_failures.category.startup": "Treiber-/Prozessstart fehlgeschlagen", + "ai_chat.inspection.connection_failures.category.other": "Andere Verbindungsanomalie", + "ai_chat.inspection.connection_failures.next_action.cooldown": "Behebe zuerst den letzten echten Verbindungsfehler und versuche es dann erneut; reines Aktualisieren trifft weiter den Verbindungs-Cooldown.", + "ai_chat.inspection.connection_failures.next_action.parameter_compatibility": "Prüfe zuerst Verbindungsparameter, DSN und Protokollkompatibilität, insbesondere multiStatements, charset, zusätzliche query-Parameter und URL-Codierung.", + "ai_chat.inspection.connection_failures.next_action.validation": "Prüfe die vom Server gemeldeten Validierungsdetails und bestätige, dass Datenbanktyp, Treiberprotokoll, Datenbankname oder Service Name zum Zieldienst passen.", + "ai_chat.inspection.connection_failures.next_action.authentication": "Prüfe Benutzername, Passwort, Authentifizierungsdatenbank, Tenant oder Service Name und bestätige, dass der Server dieses Konto anmelden lässt.", + "ai_chat.inspection.connection_failures.next_action.ssh": "Prüfe SSH-Jump-Host-Adresse, Port, Konto und Tunnel-Zieladresse; validiere bei Bedarf zuerst die Verbindung vom Jump Host zur Datenbank.", + "ai_chat.inspection.connection_failures.next_action.network": "Prüfe, ob Zieladresse, Port, Firewall, Proxy und Tunnelpfad erreichbar sind, und bestätige, dass der Server tatsächlich lauscht.", + "ai_chat.inspection.connection_failures.next_action.startup": "Prüfe, ob der Treiberprozess oder die externe Abhängigkeit normal starten kann; validiere bei Bedarf den Startbefehl lokal oder auf dem Zielhost separat.", + "ai_chat.inspection.connection_failures.next_action.check_current_connection": "Wenn du bestätigen musst, ob die aktuell angezeigte Verbindung noch dasselbe Ziel meint, rufe inspect_current_connection auf und prüfe, ob sie weiterhin auf {{address}} zeigt.", + "ai_chat.inspection.connection_failures.next_action.inspect_config": "Prüfe zuerst mit inspect_current_connection und inspect_saved_connections die aktuelle Verbindungskonfiguration und erweitere danach bei Bedarf das Logfenster.", + "ai_chat.inspection.connection_failures.message.detected": "In den letzten Logs wurden {{count}} verbindungsbezogene Anomalien erkannt; die neueste Kategorie ist {{categoryLabel}}", + "ai_chat.inspection.connection_failures.message.no_keyword_match": "In den letzten Logs wurden keine Verbindungsfehler zum Keyword \"{{keyword}}\" gefunden", + "ai_chat.inspection.connection_failures.message.none": "In den letzten Logs wurden keine Verbindungsfehler, Validierungsfehler oder Verbindungs-Cooldown-Einträge erkannt", + "ai_chat.inspection.context_budget.warning.missing_session": "Die Ziel-AI-Sitzung wurde nicht gefunden; die Nachrichtenvolumenstatistik deckt daher nur ein leeres Fenster ab", + "ai_chat.inspection.context_budget.next_action.open_session": "Öffne oder wähle zuerst die Ziel-AI-Sitzung aus und rufe danach inspect_ai_context_budget erneut auf", + "ai_chat.inspection.context_budget.warning.critical_risk": "Der aktuelle AI-Eingabekontext hat ein critical Volumen erreicht und kann langsame Antworten, Kürzungen oder ignorierte Kernvorgaben verursachen", + "ai_chat.inspection.context_budget.warning.high_risk": "Der aktuelle AI-Eingabekontext ist groß; grenze den Kontext vor komplexen Fragen ein", + "ai_chat.inspection.context_budget.warning.large_schema_context": "Viele Tabellenschemas oder lange DDL sind eingebunden und können Benutzerfrage und Tool-Ergebnisse verdrängen", + "ai_chat.inspection.context_budget.next_action.narrow_tables": "Behalte nur die für diese Runde relevanten Tabellen; nutze bei Bedarf inspect_table_bundle, um Zieltabellen gezielt zu lesen", + "ai_chat.inspection.context_budget.warning.large_messages": "Die letzten Nachrichten in dieser Sitzung sind lang und können die Stabilität späterer Antworten beeinträchtigen", + "ai_chat.inspection.context_budget.next_action.summarize_or_new_session": "Starte eine neue Sitzung oder lasse AI zuerst die aktuelle Schlussfolgerung zusammenfassen, bevor du die nächste komplexe Aufgabe fortsetzt", + "ai_chat.inspection.context_budget.warning.large_tool_results": "Die letzten Tool-Ergebnisse sind lang und können spätere Antworten durch Logs oder große Ergebnismengen verwässern", + "ai_chat.inspection.context_budget.next_action.reduce_tool_results": "Reduziere die Rückgabemenge von inspect_app_logs / inspect_recent_sql_logs / includeDDL / includeLogLines", + "ai_chat.inspection.context_budget.warning.large_mcp_catalog": "Viele MCP-Tools oder schemas sind sichtbar, wodurch das Modell leichter ein falsches Tool wählen kann", + "ai_chat.inspection.context_budget.next_action.narrow_tools": "Deaktiviere vorübergehend irrelevante MCP-Dienste oder rufe zuerst inspect_ai_tool_catalog mit Schlüsselwort auf, um die Tool-Route einzugrenzen", + "ai_chat.inspection.context_budget.warning.large_skills": "Viele Skills sind aktiviert oder Prompts sind lang, wodurch sich widersprüchliche Vorgaben überlagern können", + "ai_chat.inspection.context_budget.next_action.reduce_skills": "Behalte nur Skills, die für diese Runde relevant sind, und stelle die übrigen Skills nach Abschluss wieder her", + "ai_chat.inspection.context_budget.warning.unresolved_tool_calls": "Im letzten Nachrichtenfenster gibt es {{count}} nicht abgeschlossene Tool-Aufrufe", + "ai_chat.inspection.context_budget.next_action.inspect_message_flow": "Rufe zuerst inspect_ai_message_flow auf, um zu prüfen, ob Tool-Aufrufen tool-Ergebnisnachrichten fehlen", + "ai_chat.inspection.context_budget.next_action.continue_narrow_probe": "Das aktuelle Kontextvolumen ist beherrschbar; rufe für die konkrete Frage weiter engere Struktur-, Log- oder SQL-Risikoprobes auf", + "ai_chat.inspection.tool_catalog.next_action.filter_by_keyword": "Filtere zuerst nach Schlüsselwörtern der Benutzerfrage, etwa mcp, Verbindungsfehler, Transaktion, Tastenkürzel, schema oder Logs", + "ai_chat.inspection.tool_catalog.warning.no_mcp_tools": "Es wurden keine externen MCP-Tools gefunden; wenn der Benutzer externe Funktionen benötigt, prüfe zuerst MCP-Servicekonfiguration und Tool-Erkennungsstatus", + "ai_chat.inspection.tool_catalog.next_action.inspect_mcp_setup": "Rufe inspect_mcp_setup auf, um MCP-Dienste und den Zugriffsstatus externer Clients zu prüfen", + "ai_chat.inspection.tool_catalog.warning.no_matches": "Es wurden keine passenden Tools oder empfohlenen Abläufe gefunden", + "ai_chat.inspection.tool_catalog.next_action.broaden_keyword": "Verwende allgemeinere Schlüsselwörter oder rufe zuerst inspect_ai_runtime auf, um die vollständige aktuelle Tool-Liste zu sehen", + "ai_chat.inspection.tool_catalog.next_action.use_parameter_descriptions": "Baue vor dem Aufruf parameterisierter Tools die arguments bevorzugt anhand von parameters.description; frage den Benutzer, wenn Kontext fehlt", + "ai_chat.inspection.tool_catalog.message.by_tool_name": "Kataloginformationen für Tool {{toolName}} zurückgegeben", + "ai_chat.inspection.tool_catalog.message.by_keyword": "Toolkatalog-Vorschläge für Schlüsselwort {{keyword}} zurückgegeben", + "ai_chat.inspection.tool_catalog.message.summary": "GoNavi AI-Toolkatalog-Zusammenfassung zurückgegeben", + "ai_chat.inspection.app_health.error.support_bundle_failed": "AI-Supportpaket konnte nicht erzeugt werden", + "ai_chat.inspection.support_bundle.message.ready": "GoNavi AI-Supportpaket-Snapshot zur Diagnose von AI-, MCP-, Log-, Verbindungs- und Kontextgrößenproblemen erzeugt", + "ai_chat.inspection.support_bundle.privacy.note": "Standardmäßig werden nur Zusammenfassungen und strukturierte Zählwerte zurückgegeben; Logzeilen oder Nachrichtenvorschauen werden nur einbezogen, wenn includeLogLines/includeMessageContent ausdrücklich aktiviert ist.", + "ai_chat.inspection.codebase_hotspots.evidence.note": "Based on the current repository frontend file-line hotspot snapshot; before extraction, prefer slices that are readyToExtract and already covered by tests.", + "ai_chat.inspection.codebase_hotspots.next_action.pick_ready_slice": "Prefer a slice with readiness=readyToExtract and existing test coverage for small-step extraction; avoid rewriting an entire large component directly.", + "ai_chat.inspection.codebase_hotspots.next_action.confirm_safe_seam": "Confirm the safeSeam before every extraction, and do not cross SQL execution, transaction, connection secret, or database dialect boundaries.", + "ai_chat.inspection.codebase_hotspots.next_action.run_targeted_tests": "After extraction, run the corresponding component tests, related utils tests, and npm --prefix frontend run build at minimum.", + "ai_chat.inspection.codebase_hotspots.next_action.browser_smoke": "For visible UI extraction, open the real page in a browser for one smoke verification.", + "ai_chat.inspection.codebase_hotspots.sidebar.why": "The left tree, command palette, context menus, and connection actions are concentrated in one file, so change entry points are numerous and regression risk is high.", + "ai_chat.inspection.codebase_hotspots.sidebar.preferred_next_slice": "External SQL directory dialog", + "ai_chat.inspection.codebase_hotspots.sidebar.safe_seam": "Extract stateless dialog and menu configuration first, then handle action dispatch that depends on connection tree state.", + "ai_chat.inspection.codebase_hotspots.sidebar.suggested_slice.v2_command_palette": "V2 command palette", + "ai_chat.inspection.codebase_hotspots.sidebar.suggested_slice.external_sql_directory_dialog": "External SQL directory dialog", + "ai_chat.inspection.codebase_hotspots.sidebar.suggested_slice.connection_tree_actions": "Connection tree actions", + "ai_chat.inspection.codebase_hotspots.sidebar.suggested_slice.batch_operation_dialogs": "Batch operation dialogs", + "ai_chat.inspection.codebase_hotspots.sidebar.verification.browser_smoke": "Open the sidebar in a browser and verify the connection tree, context menus, and external SQL directory entry.", + "ai_chat.inspection.codebase_hotspots.data_grid.why": "Result display, editing, DDL, export, and column operations are coupled, so a focused fix can affect the query result area.", + "ai_chat.inspection.codebase_hotspots.data_grid.preferred_next_slice": "Result export toolbar", + "ai_chat.inspection.codebase_hotspots.data_grid.safe_seam": "Extract the pure display toolbar and menu item generation first; do not move data-editing transaction state yet.", + "ai_chat.inspection.codebase_hotspots.data_grid.suggested_slice.result_export_toolbar": "Result export toolbar", + "ai_chat.inspection.codebase_hotspots.data_grid.suggested_slice.column_header_menu": "Column header menu", + "ai_chat.inspection.codebase_hotspots.data_grid.suggested_slice.ddl_view": "DDL view", + "ai_chat.inspection.codebase_hotspots.data_grid.suggested_slice.cell_edit_transaction_hint": "Cell edit transaction hint", + "ai_chat.inspection.codebase_hotspots.data_grid.verification.browser_smoke": "Run a query in the browser and verify the result table, export, column menu, and table editing entry.", + "ai_chat.inspection.codebase_hotspots.connection_modal.why": "Multi-source connection forms are still concentrated in one component, so adding data sources or secret rules can affect each other.", + "ai_chat.inspection.codebase_hotspots.connection_modal.preferred_next_slice": "TLS configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.safe_seam": "The connection form already has presentation utilities; first extract configuration sections shown per data source.", + "ai_chat.inspection.codebase_hotspots.connection_modal.suggested_slice.ssh_proxy_section": "SSH/proxy configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.suggested_slice.tls_section": "TLS configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.suggested_slice.mongodb_section": "MongoDB configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.suggested_slice.jvm_section": "JVM configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.verification.browser_smoke": "Open the add/edit connection dialog in a browser and switch MySQL, Oracle, MongoDB, and Redis forms.", + "ai_chat.inspection.codebase_hotspots.query_editor.why": "SQL editing, execution, transactions, result layout, and shortcut state are concentrated, so transaction and result-panel regressions are likely.", + "ai_chat.inspection.codebase_hotspots.query_editor.preferred_next_slice": "Editor toolbar", + "ai_chat.inspection.codebase_hotspots.query_editor.safe_seam": "The toolbar JSX can pass state and callbacks through props, avoiding SQL execution, transaction, and result pagination logic.", + "ai_chat.inspection.codebase_hotspots.query_editor.suggested_slice.result_toolbar": "Result area toolbar", + "ai_chat.inspection.codebase_hotspots.query_editor.suggested_slice.transaction_status_bar": "Transaction status bar", + "ai_chat.inspection.codebase_hotspots.query_editor.suggested_slice.execution_log_hint": "Execution log hint", + "ai_chat.inspection.codebase_hotspots.query_editor.suggested_slice.editor_shortcut_binding": "Editor shortcut binding", + "ai_chat.inspection.codebase_hotspots.query_editor.verification.browser_smoke": "Open the SQL editor in a browser and verify connection/database selection, run, save, format, result visibility, and the AI menu.", + "ai_chat.inspection.codebase_hotspots.table_designer.why": "Field editing, indexes, foreign keys, partitions, and DDL generation are concentrated, so database dialect differences can spread easily.", + "ai_chat.inspection.codebase_hotspots.table_designer.preferred_next_slice": "Field editing table", + "ai_chat.inspection.codebase_hotspots.table_designer.safe_seam": "Add field type, length, NULL, and default value snapshot tests first, then extract the field editing table.", + "ai_chat.inspection.codebase_hotspots.table_designer.suggested_slice.field_editing_table": "Field editing table", + "ai_chat.inspection.codebase_hotspots.table_designer.suggested_slice.index_panel": "Index configuration panel", + "ai_chat.inspection.codebase_hotspots.table_designer.suggested_slice.foreign_key_panel": "Foreign key configuration panel", + "ai_chat.inspection.codebase_hotspots.table_designer.suggested_slice.dialect_ddl_preview": "Dialect DDL preview", + "ai_chat.inspection.codebase_hotspots.table_designer.verification.browser_smoke": "Open object design in a browser and verify fields, indexes, foreign keys, and DDL preview.", + "ai_chat.inspection.codebase_hotspots.redis_viewer.why": "Key browsing, data-structure editing, TTL, encoding display, and the add dialog are concentrated, so Redis Cluster/Sentinel follow-up validation is broad.", + "ai_chat.inspection.codebase_hotspots.redis_viewer.preferred_next_slice": "Key search bar", + "ai_chat.inspection.codebase_hotspots.redis_viewer.safe_seam": "Extract the search bar and topology hint first, avoiding early changes to each data-structure editor.", + "ai_chat.inspection.codebase_hotspots.redis_viewer.suggested_slice.key_search_bar": "Key search bar", + "ai_chat.inspection.codebase_hotspots.redis_viewer.suggested_slice.structure_editors": "String/List/Set/ZSet/Hash/Stream editors", + "ai_chat.inspection.codebase_hotspots.redis_viewer.suggested_slice.add_key_dialog": "Add key dialog", + "ai_chat.inspection.codebase_hotspots.redis_viewer.verification.browser_smoke": "Open a Redis connection in a browser and verify key search, refresh, TTL, and the add entry.", + "ai_chat.inspection.codebase_hotspots.driver_manager.why": "Driver installation, status display, downloads, and optional proxy logic are substantial, so status cards and action areas are good next extraction targets.", + "ai_chat.inspection.codebase_hotspots.driver_manager.preferred_next_slice": "Driver status list", + "ai_chat.inspection.codebase_hotspots.driver_manager.safe_seam": "The status list is presentational; extract it first while keeping install and download actions in the parent component.", + "ai_chat.inspection.codebase_hotspots.driver_manager.suggested_slice.driver_status_list": "Driver status list", + "ai_chat.inspection.codebase_hotspots.driver_manager.suggested_slice.install_actions": "Install action area", + "ai_chat.inspection.codebase_hotspots.driver_manager.suggested_slice.download_logs": "Download log area", + "ai_chat.inspection.codebase_hotspots.driver_manager.verification.browser_smoke": "Open driver management in a browser and verify status display, install buttons, and the log area.", + "ai_chat.inspection.codebase_hotspots.data_sync.why": "Data sync connections, table mapping, prechecks, and execution results are concentrated, so database dialect issues can be hidden.", + "ai_chat.inspection.codebase_hotspots.data_sync.preferred_next_slice": "Sync precheck results", + "ai_chat.inspection.codebase_hotspots.data_sync.safe_seam": "Make precheck results a pure display component first, keeping connection selection and execution actions in the parent component.", + "ai_chat.inspection.codebase_hotspots.data_sync.suggested_slice.connection_selection": "Connection selection area", + "ai_chat.inspection.codebase_hotspots.data_sync.suggested_slice.table_mapping": "Table mapping area", + "ai_chat.inspection.codebase_hotspots.data_sync.suggested_slice.precheck_results": "Sync precheck results", + "ai_chat.inspection.codebase_hotspots.data_sync.suggested_slice.execution_logs": "Execution log area", + "ai_chat.inspection.codebase_hotspots.data_sync.verification.browser_smoke": "Open data sync in a browser and verify connection selection, table mapping, precheck, and execution logs.", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.why": "Diagnostic commands, output blocks, permission hints, and session state can continue to be split to lower JVM diagnostics regression risk.", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.preferred_next_slice": "Diagnostic output area", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.safe_seam": "The output area mainly depends on the command result array, so it can be extracted as a display component first.", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.suggested_slice.command_input": "Command input area", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.suggested_slice.diagnostic_output": "Diagnostic output area", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.suggested_slice.permission_hint": "Permission hint area", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.verification.browser_smoke": "Open the JVM diagnostics panel in a browser and verify command input, output, and permission hints.", + "ai_chat.inspection.app_health.error.app_health_failed": "AI-Anwendungszustandsübersicht konnte nicht gelesen werden", + "ai_chat.inspection.guidance.scope.global": "Global", + "ai_chat.inspection.guidance.scope.database": "Datenbanksitzung", + "ai_chat.inspection.guidance.scope.jvm": "JVM-Ressourcenanalyse", + "ai_chat.inspection.guidance.scope.jvmDiagnostic": "JVM-Diagnose", + "ai_chat.inspection.guidance.message.configured": "{{promptCount}} benutzerdefinierte Prompts und {{skillCount}} Skills sind aktiviert", + "ai_chat.inspection.guidance.message.empty": "Es sind keine benutzerdefinierten Prompts oder Skills aktiviert", + "ai_chat.inspection.runtime.safety.readonly": "Nur lesen", + "ai_chat.inspection.runtime.safety.readwrite": "Lesen/Schreiben", + "ai_chat.inspection.runtime.safety.full": "Voller Zugriff", + "ai_chat.inspection.runtime.context.schema_only": "Nur schema", + "ai_chat.inspection.runtime.context.with_samples": "schema + Beispiele", + "ai_chat.inspection.runtime.context.with_results": "schema + Ergebnisse", + "ai_chat.inspection.runtime.message.active": "AI verwendet {{provider}}; {{toolCount}} Tools sind verfügbar", + "ai_chat.inspection.runtime.message.no_provider": "Aktuell ist kein AI-Anbieter aktiv", + "ai_chat.inspection.safety.rule.readonly": "Im Nur-Lesen-Modus sind nur Abfrageanweisungen erlaubt.", + "ai_chat.inspection.safety.rule.readwrite": "Im Lesen/Schreiben-Modus sind Abfragen und DML erlaubt; DDL bleibt blockiert.", + "ai_chat.inspection.safety.rule.full": "Im Modus mit vollem Zugriff sind alle SQL-Operationen erlaubt; risikoreiche oder nicht erkannte Anweisungen erfordern weiterhin eine Bestätigung.", + "ai_chat.inspection.safety.restriction.readonly_blocks_mutating": "Auf der aktuellen Sicherheitsstufe werden alle DML/DDL direkt blockiert.", + "ai_chat.inspection.safety.restriction.non_query_confirmation": "Jede erlaubte Nicht-Abfrage-Anweisung erfordert weiterhin eine menschliche Bestätigung.", + "ai_chat.inspection.safety.restriction.mcp_allow_mutating": "Beim Ausführen von Nicht-Abfrage-Anweisungen über GoNavi MCP execute_sql muss allowMutating=true zusätzlich explizit übergeben werden.", + "ai_chat.inspection.safety.restriction.active_result_readonly": "Das Resultset des aktuellen aktiven Tabs ist schreibgeschützt und kann nicht als direkt beschreibbares Datengrid behandelt werden.", + "ai_chat.inspection.safety.restriction.jvm_readonly": "Die aktuelle JVM-Verbindung ist schreibgeschützt, daher sollten Diagnosepläne standardmäßig auf Beobachtung und Fehlerbehebung ausgerichtet sein.", + "ai_chat.inspection.safety.restriction.jvm_mutating_disabled": "Die aktuelle JVM-Diagnose verbietet mutating-Befehle ausdrücklich, selbst wenn die AI-Sicherheitsstufe Schreibzugriffe erlaubt.", + "ai_chat.inspection.safety.recommendation.enable_readwrite_for_dml": "Wechsle vor INSERT/UPDATE/DELETE zur AI-Sicherheitsstufe Lesen/Schreiben.", + "ai_chat.inspection.safety.recommendation.enable_full_for_ddl": "Wechsle vor CREATE/ALTER/DROP/TRUNCATE schema-Änderungen zum Modus mit vollem Zugriff.", + "ai_chat.inspection.safety.recommendation.full_required_for_schema": "DML ist bereits erlaubt; schema-Änderungen erfordern weiterhin den Modus mit vollem Zugriff.", + "ai_chat.inspection.safety.recommendation.open_editable_grid": "Wenn ein Resultgrid bearbeitet werden soll, öffne statt des aktuellen schreibgeschützten Tabs eine bearbeitbare Tabelle oder ein bearbeitbares Abfrageergebnis erneut.", + "ai_chat.inspection.safety.recommendation.confirm_jvm_policy": "Die aktuelle JVM-Verbindung sollte als schreibgeschützt behandelt werden; prüfe vor mutating-Diagnosen, ob die Richtlinie geändert werden soll.", + "ai_chat.inspection.safety.recommendation.enable_jvm_mutating": "JVM-Diagnosen verbieten derzeit mutating-Befehle; passe vor risikoreichen Befehlen die Diagnoseberechtigungen an.", + "ai_chat.inspection.safety.message.active": "AI-Sicherheitsstufe ist {{safety}}; aktive Verbindung ist {{connection}}", + "ai_chat.inspection.safety.message.no_connection": "AI-Sicherheitsstufe ist {{safety}}; keine aktive Verbindung ausgewählt", + "ai_chat.inspection.provider.message.empty": "Es sind keine AI-Anbieter konfiguriert", + "ai_chat.inspection.provider.message.active_needs_attention": "{{provider}} wird verwendet, aber {{issueCount}} Punkte müssen noch geprüft werden", + "ai_chat.inspection.provider.message.active_ready": "{{count}} Anbieter sind konfiguriert; verwendet wird {{provider}}", + "ai_chat.inspection.provider.message.unselected": "{{count}} Anbieter sind konfiguriert, aber kein aktiver Anbieter ist ausgewählt", + "ai_chat.inspection.table_schema.warning.ddl_fallback": "DDL für Tabelle {{tableName}} konnte nicht abgerufen werden; es wurde auf eine Spaltenmetadaten-Zusammenfassung zurückgegriffen.", + "ai_chat.inspection.table_schema.warning.ddl_error": "DDL-Fehler: {{detail}}", + "ai_chat.inspection.table_schema.warning.fallback_limitation": "Dieses Ergebnis enthält keine vollständigen Index-, Constraint-, Trigger- oder sonstigen DDL-Informationen; analysiere auf Basis der Spaltenliste weiter und stoppe nicht nur wegen fehlender DDL-Berechtigungen.", + "ai_chat.inspection.table_schema.warning.columns_contract": "⚠️ Dies ist die echte field-Liste der Tabelle {{tableName}}. Beim Erzeugen von SQL dürfen nur diese field-Werte exakt als Spaltennamen verwendet werden; nicht ändern, abkürzen oder selbst zusammensetzen.", + "ai_chat.inspection.table_schema.warning.available_fields": "Verfügbare Felder: {{fields}}", + "ai_chat.inspection.table_schema.warning.detail": "Details: {{detail}}", + "ai_chat.inspection.table_schema.value.none": "keine", + "ai_chat.inspection.table_schema.error.unknown": "Unbekannter Fehler", + "ai_chat.inspection.table_schema.error.ddl_failed": "CREATE TABLE-DDL konnte nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.table_schema.error.ddl_and_columns_failed": "CREATE TABLE-DDL konnte nicht abgerufen werden: {{ddlDetail}}; auch der Fallback zum Abrufen der Spaltenliste ist fehlgeschlagen: {{columnDetail}}", + "ai_chat.inspection.mcp.warning.config_errors": "{{count}} MCP-Server hat Fehler in der Startkonfiguration; Tests und Tool-Erkennung können fehlschlagen", + "ai_chat.inspection.mcp.warning.config_warnings": "{{count}} MCP-Server hat Warnungen in der Startkonfiguration; vor der Diagnose fehlgeschlagener Tool-Erkennung prüfen", + "ai_chat.inspection.mcp.next_action.fix_config_errors": "Behebe zuerst die MCP-Server-Konfigurationsfehler und teste den Server danach erneut", + "ai_chat.inspection.mcp.next_action.fix_config_warnings": "Öffne den betroffenen MCP-Server und trenne Startbefehl, Argumente und Timeout gemäß den Konfigurationshinweisen", + "ai_chat.inspection.mcp.message.with_issues": "{{serverCount}} MCP-Server ist konfiguriert; {{enabledCount}} aktiviert; {{issueCount}} Konfigurationsprüfungen benötigen Aufmerksamkeit", + "ai_chat.inspection.mcp.message.configured": "{{serverCount}} MCP-Server ist konfiguriert; {{enabledCount}} aktiviert", + "ai_chat.inspection.mcp.message.empty": "Es sind noch keine MCP-Server konfiguriert", + "ai_chat.inspection.mcp_draft.default_name": "MCP-Entwurf", + "ai_chat.inspection.mcp_draft.redacted_parse_failed": "[Analyse fehlgeschlagen, ursprünglicher Befehl ausgeblendet]", + "ai_chat.inspection.mcp_draft.parse.no_full_command": "Es wurde kein fullCommand angegeben; der Entwurf aus Einzelfeldern wurde geprüft.", + "ai_chat.inspection.mcp_draft.next_action.command_missing": "Füge zuerst den vollständigen Startbefehl aus der README ein, oder trage mindestens node, npx, uvx, python oder exe als command ein.", + "ai_chat.inspection.mcp_draft.next_action.command_whole_line": "Lege den ganzen Befehl zur automatischen Aufteilung in das fullCommand-Feld; in command bleibt nur die ausführbare Datei, Skriptname, Paketname und --stdio gehören in args.", + "ai_chat.inspection.mcp_draft.next_action.args_missing_for_launcher": "Vervollständige die Launcher-Argumente: npx benötigt meist -y und einen Paketnamen, node braucht server.js, python braucht -m und einen Modulnamen, uvx braucht einen Paketnamen, docker braucht run, -i und einen Image-Namen.", + "ai_chat.inspection.mcp_draft.next_action.docker_run": "Für Docker MCP command auf docker setzen und run separat in args ergänzen.", + "ai_chat.inspection.mcp_draft.next_action.docker_interactive": "Ergänze -i oder --interactive in Docker-MCP-args, damit die stdio-Verbindung nicht sofort schließt.", + "ai_chat.inspection.mcp_draft.next_action.docker_image": "Ergänze den Image-Namen aus der README in Docker-MCP-args, zum Beispiel mcp/server-fetch:latest.", + "ai_chat.inspection.mcp_draft.next_action.env_lines": "Schreibe Umgebungsvariablen zeilenweise als KEY=VALUE; export, set, env, && oder $env:KEY=VALUE; gehören nicht in args.", + "ai_chat.inspection.mcp_draft.next_action.timeout": "Setze timeout auf 20 Sekunden; langsam startende Dienste können 45 oder 60 Sekunden verwenden.", + "ai_chat.inspection.mcp_draft.next_action.ready_to_save": "Der aktuelle Entwurf kann gespeichert und für Tool-Erkennung getestet werden; wenn 0 Tools gefunden werden, prüfe, ob der Dienst stdio unterstützt.", + "ai_chat.inspection.mcp_draft.next_action.can_test_with_warnings": "Der aktuelle Entwurf kann getestet werden, aber behebe zuerst die warnings, um Timeouts bei der Tool-Erkennung oder 0 gefundene Tools zu vermeiden.", + "ai_chat.inspection.mcp_draft.next_action.send_full_command": "Wenn du noch unsicher bist, wie die Aufteilung aussehen soll, übergib den ursprünglichen vollständigen Befehl als fullCommand und lasse GoNavi ihn berechnen.", + "ai_chat.inspection.mcp_docker.next_action.add_run": "Ergänze run in args, zum Beispiel docker run --rm -i ", + "ai_chat.inspection.mcp_docker.next_action.add_interactive": "Ergänze -i oder --interactive in args, damit MCP stdio nicht sofort schließt", + "ai_chat.inspection.mcp_docker.next_action.add_image": "Ergänze den Image-Namen aus der README nach den docker run-Optionen", + "ai_chat.inspection.mcp_docker.next_action.timeout": "Docker kann beim ersten Start langsam sein; verwende timeoutSeconds 45 oder 60", + "ai_chat.inspection.mcp_docker.next_action.no_tools": "Die Konfiguration wirkt vollständig, aber es wurden keine Tools erkannt; klicke auf \"Tool-Erkennung testen\", um Docker, das Image und Abhängigkeiten im Container zu prüfen", + "ai_chat.inspection.mcp_docker.next_action.disabled": "Dieses Docker MCP ist derzeit deaktiviert; aktiviere es nach der Konfigurationsprüfung und teste die Tool-Erkennung", + "ai_chat.inspection.mcp_docker.next_action.create_from_readme": "Wenn die README docker run -i --rm vorgibt, erstelle in den MCP-Einstellungen einen Server aus der Vorlage \"Docker image\"", + "ai_chat.inspection.mcp_docker.warning.incomplete": "{{count}} Docker MCP-Server vermisst wichtige Argumente wie run, -i oder Image-Name", + "ai_chat.inspection.mcp_docker.next_action.fix_key_args": "Korrigiere zuerst die wichtigen Docker MCP-Argumente und teste dann die Tool-Erkennung erneut", + "ai_chat.inspection.mcp_docker.warning.config_warnings": "{{count}} Docker MCP-Server hat noch Konfigurationswarnungen", + "ai_chat.inspection.mcp_docker.next_action.open_services": "Öffne die betroffenen Docker MCP-Services und prüfe Argumente und Timeout anhand der Konfigurationshinweise", + "ai_chat.inspection.mcp_docker.warning.no_tools": "{{count}} aktivierter Docker MCP-Server hat noch keine Tools erkannt", + "ai_chat.inspection.mcp_docker.next_action.refresh_tools": "Bestätige, dass lokales Docker verfügbar und das Image gezogen ist, und klicke auf \"Tool-Erkennung testen\", um die Toolliste zu aktualisieren", + "ai_chat.inspection.mcp_docker.message.with_incomplete": "Es gibt {{total}} Docker MCP-Server; bei {{count}} sind wichtige Argumente unvollständig", + "ai_chat.inspection.mcp_docker.message.with_enabled": "Es gibt {{total}} Docker MCP-Server; {{enabled}} sind aktiviert", + "ai_chat.inspection.mcp_docker.message.empty": "Es sind keine Docker MCP-Server konfiguriert", + "ai_chat.inspection.mcp_tool_schema.usage.no_input_schema": "Dieses MCP-Tool deklariert kein inputSchema; prüfe zuerst die Service-README oder teste mit einem leeren Objekt.", + "ai_chat.inspection.mcp_tool_schema.usage.required_params": "Vor dem Aufruf von {{alias}} müssen angegeben werden: {{parameters}}", + "ai_chat.inspection.mcp_tool_schema.usage.enum_values": "{{path}} muss einen dieser Enum-Werte verwenden: {{values}}", + "ai_chat.inspection.mcp_tool_schema.usage.nested_json": "Verschachtelte Objekt- und Array-Parameter müssen der JSON-Struktur folgen; übergib nicht das ganze Objekt als String.", + "ai_chat.inspection.mcp_tool_schema.usage.schema_fields_only": "Übergebe nur Felder, die im schema deklariert sind; wenn eine Feldbedeutung unklar ist, frage den Benutzer statt zu raten.", + "ai_chat.inspection.mcp_tool_schema.warning.no_tools": "Es wurden keine MCP-Tools erkannt; eventuell sind keine MCP-Services konfiguriert oder Service-Test/Erkennung ist fehlgeschlagen.", + "ai_chat.inspection.mcp_tool_schema.next_action.inspect_setup": "Rufe zuerst inspect_mcp_setup auf, um zu prüfen, ob MCP-Services aktiviert sind und Tools erkannt wurden.", + "ai_chat.inspection.mcp_tool_schema.warning.no_matches": "Kein passendes MCP-Tool gefunden.", + "ai_chat.inspection.mcp_tool_schema.next_action.lookup_alias": "Rufe zuerst inspect_mcp_setup auf, um die tatsächlich erkannten MCP-Tool-aliases zu prüfen, und suche dann mit exaktem alias.", + "ai_chat.inspection.mcp_tool_schema.warning.missing_schema": "Einige MCP-Tools deklarieren kein inputSchema, daher kann die Parameterdokumentation unvollständig sein.", + "ai_chat.inspection.mcp_tool_schema.next_action.read_readme": "Bei Tools ohne schema gehe zurück zur MCP-Service-README oder nutze Tool-Fehler, um Parameter zu bestätigen.", + "ai_chat.inspection.mcp_tool_schema.message.with_matches": "{{matched}} MCP-Tools gefunden und {{returned}} Parameter-schema-Zusammenfassungen zurückgegeben", + "ai_chat.inspection.mcp_tool_schema.message.no_matches": "Kein passendes MCP-Tool gefunden", + "ai_chat.inspection.mcp_tool_schema.message.empty": "Noch kein MCP-Tool-schema verfügbar", + "ai_chat.inspection.mcp_runtime.next_action.command_not_found": "Prüfe, ob command nur den Namen der ausführbaren Datei enthält, und bestätige, dass sie in PATH liegt oder als absoluter Pfad angegeben ist.", + "ai_chat.inspection.mcp_runtime.next_action.timeout": "Erhöhe timeoutSeconds auf 45 oder 60 und bestätige, dass der Dienst nach dem Start die stdio-Verbindung offen hält.", + "ai_chat.inspection.mcp_runtime.next_action.permission": "Prüfe Ausführungsrechte, Antivirus-/Systemblockaden und Zugriffsrechte auf das Arbeitsverzeichnis.", + "ai_chat.inspection.mcp_runtime.next_action.auth": "Prüfe, ob Token/API Key in den Umgebungsvariablen gesetzt, nicht abgelaufen und ausreichend berechtigt ist.", + "ai_chat.inspection.mcp_runtime.next_action.network": "Prüfe, ob Remote-Adresse, Proxy, VPN oder lokaler Port, die MCP benötigt, erreichbar sind.", + "ai_chat.inspection.mcp_runtime.next_action.stdio_closed": "Bestätige, dass die laut README nötigen --stdio/stdin-Argumente gesetzt sind; bei Docker muss args -i enthalten.", + "ai_chat.inspection.mcp_runtime.next_action.process_exit": "Führe den Startbefehl separat im Terminal aus und prüfe, warum der Prozess direkt nach dem Start beendet wird.", + "ai_chat.inspection.mcp_runtime.next_action.argument_error": "Rufe zuerst inspect_mcp_tool_schema auf, um das echte inputSchema zu lesen, und korrigiere dann das arguments JSON des Tools.", + "ai_chat.inspection.mcp_runtime.next_action.transport": "Neu hinzugefügte MCP-Server in GoNavi unterstützen derzeit nur stdio; für HTTP MCP nutze den GoNavi HTTP-Dienst oder die passende Remote-Anleitung.", + "ai_chat.inspection.mcp_runtime.next_action.unknown": "Prüfe die Konfiguration mit inspect_mcp_setup und rufe dann inspect_app_logs mit einem größeren Logfenster auf, um den Rohfehler zu bestätigen.", + "ai_chat.inspection.mcp_runtime.next_action.enabled_without_tools": "Einige aktivierte MCP-Server haben keine erkannten Tools; klicke zuerst auf \"Tool-Erkennung testen\" und bestätige, dass der Startbefehl eigenständig läuft.", + "ai_chat.inspection.mcp_runtime.next_action.fix_discovery_first": "Wenn die Toolliste leer ist, behebe zuerst Start-/Erkennungsfehler, bevor du einzelne Tool-arguments untersuchst.", + "ai_chat.inspection.mcp_runtime.next_action.expand_logs": "In den aktuellen Logs wurde kein MCP-Fehlersignal gefunden; wenn du das Problem gerade reproduziert hast, erhöhe lineLimit oder filtere exakt mit serverName.", + "ai_chat.inspection.mcp_runtime.warning.failure_events": "In den aktuellen Logs wurde {{count}} MCP-Laufzeitfehlersignal gefunden.", + "ai_chat.inspection.mcp_runtime.warning.enabled_without_tools": "{{count}} aktivierter MCP-Server hat derzeit keine erkannten Tools.", + "ai_chat.inspection.mcp_runtime.message.failure_events": "In den aktuellen Logs wurde {{count}} MCP-Laufzeitfehlersignal gefunden", + "ai_chat.inspection.mcp_runtime.message.no_failure_events": "In den aktuellen Logs wurde kein Signal für MCP-Start-, Tool-Erkennungs- oder Tool-Aufruffehler gefunden.", + "ai_chat.inspection.mcp_runtime.error.read_logs_failed": "MCP-Laufzeitfehlerlogs konnten nicht gelesen werden: {{detail}}", + "ai_chat.inspection.mcp_runtime.error.read_logs_unsupported": "Die aktuelle Umgebung unterstützt das Lesen von GoNavi-App-Logs nicht", + "ai_chat.inspection.diagnostics.error.read_app_logs_failed": "GoNavi-App-Logs konnten nicht gelesen werden: {{detail}}", + "ai_chat.inspection.diagnostics.error.read_ai_upstream_logs_failed": "AI-Upstream-Anforderungslogs konnten nicht gelesen werden: {{detail}}", + "ai_chat.inspection.diagnostics.error.read_recent_connection_failures_failed": "Aktuelle Verbindungsfehlerdatensätze konnten nicht gelesen werden: {{detail}}", + "ai_chat.inspection.diagnostics.error.read_app_logs_unsupported": "Die aktuelle Umgebung unterstützt das Lesen von GoNavi-App-Logs nicht", + "ai_chat.inspection.diagnostics.error.unknown": "Unbekannter Fehler", + "ai_chat.inspection.snapshot.error.inspect_current_connection": "Aktuelle Verbindung konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_connection_capabilities": "Fähigkeitsmatrix der aktuellen Verbindung konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_saved_connections": "Liste gespeicherter Verbindungen konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_redis_topology": "Redis-Topologiekonfiguration konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_external_sql_directories": "Externe SQL-Verzeichnisse konnten nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_external_sql_file": "Externe SQL-Datei konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_sessions": "Lokale AI-Sitzungsliste konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_active_tab": "Aktiver Tab konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_workspace_tabs": "Aktuelle Workspace-Tabs konnten nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_context": "Aktueller AI-Kontext konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_recent_sql_logs": "Aktuelle SQL-Logs konnten nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_recent_sql_activity": "Aktuelle SQL-Aktivität konnte nicht zusammengefasst werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_sql_editor_transaction": "SQL-Editor-Transaktionsstatus konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_mcp_runtime_failures": "MCP-Laufzeitfehlerdiagnose konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_last_render_error": "Letzter AI-Renderfehler konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_message_flow": "AI-Nachrichtenflussdiagnose konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_context_budget": "AI-Kontextbudgetdiagnose konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_codebase_hotspots": "Code-Hotspot-Diagnose konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_saved_queries": "Gespeicherte Abfragen konnten nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_sql_snippets": "SQL-Snippets konnten nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_shortcuts": "Tastenkürzelkonfiguration konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_app_health": "AI-App-Zustandsübersicht konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.snapshot.error.default": "Lokaler Prüfsnapshot konnte nicht gelesen werden: {{detail}}", + "ai_chat.inspection.upstream_logs.warning.invalid_json": "Der request body ist kein vollständiges JSON. Er wurde möglicherweise im Log gekürzt, daher kann keine strukturierte Zusammenfassung erzeugt werden.", + "ai_chat.inspection.upstream_logs.warning.not_json_object": "Der request body ist kein JSON object; Modell-, Nachrichten- und Tool-Felder können nicht erkannt werden.", + "ai_chat.inspection.upstream_logs.warning.missing_messages": "Es wurden keine Felder messages, contents, system oder prompt gefunden. Prüfe, ob das Upstream-Protokoll der Erwartung entspricht.", + "ai_chat.inspection.upstream_logs.warning.missing_tools": "Das payload enthält keine tools/functions; das Modell kann daher keinen Tool-Aufruf starten.", + "ai_chat.inspection.upstream_logs.warning.large_input": "Der Eingabetext ist groß. Grenze bei Bedarf den Kontext ein oder reduziere Log-/DDL-Inhalte.", + "ai_chat.inspection.upstream_logs.message.empty": "In den aktuellen Logs wurden keine AI-Upstream-Anfragen gefunden. Sende zuerst eine AI-Nachricht oder versuche es mit einem größeren lineLimit erneut.", + "ai_chat.inspection.upstream_logs.next_action.filter_request_body": "Um vollständige Eingaben zu prüfen, filtere zuerst exakt per requestId und prüfe dann, ob bodyPreview gekürzt wurde.", + "ai_chat.inspection.upstream_logs.next_action.inspect_timeout": "Wenn nur ein Start ohne Abschluss/Fehler vorhanden ist, prüfe weiter mit inspect_app_logs oder erhöhe lineLimit, um ein Timeout auszuschließen.", + "ai_chat.inspection.upstream_logs.next_action.confirm_logging": "Bestätige, dass der aktuelle Build AI-Upstream-Anfragelogs enthält.", + "ai_chat.inspection.upstream_logs.next_action.send_message": "Sende eine AI-Chatnachricht und rufe dieses Tool danach erneut auf.", + "ai_chat.inspection.upstream_logs.next_action.read_warn_error": "Wenn weiterhin keine Einträge vorhanden sind, rufe inspect_app_logs auf, um die letzten WARN/ERROR-Rohlogs zu lesen.", + "ai_chat.inspection.sql_risk.warning.unrecognized_operation": "Es wurde kein gültiges SQL-Operationsschlüsselwort erkannt.", + "ai_chat.inspection.sql_risk.warning.data_change": "Diese Anweisung ändert Daten. Bestätige vor der Ausführung Zieldatenbank, Bedingungen und Auswirkungsbereich.", + "ai_chat.inspection.sql_risk.warning.ddl_change": "Diese Anweisung ändert Datenbankstrukturen oder Objekte. Sichere vorher und bestätige den Rollback-Plan.", + "ai_chat.inspection.sql_risk.warning.routine_side_effect": "Diese Anweisung ruft eine Routine oder Prozedur auf und kann implizite Schreibvorgänge oder Nebenwirkungen haben.", + "ai_chat.inspection.sql_risk.warning.delete_missing_where": "DELETE enthält keine WHERE-Bedingung und kann die gesamte Tabelle löschen.", + "ai_chat.inspection.sql_risk.warning.update_missing_where": "UPDATE enthält keine WHERE-Bedingung und kann die gesamte Tabelle aktualisieren.", + "ai_chat.inspection.sql_risk.warning.truncate": "TRUNCATE leert Tabellendaten schnell und kann normalerweise nicht zeilenweise zurückgerollt werden.", + "ai_chat.inspection.sql_risk.warning.drop_object": "DROP löscht Datenbankobjekte. Bestätige vor der Ausführung Objekt und Backup.", + "ai_chat.inspection.sql_risk.warning.permission_change": "GRANT / REVOKE ändert Berechtigungsgrenzen. Bestätige Empfänger und Umfang.", + "ai_chat.inspection.sql_risk.warning.multi_statement": "{{count}} SQL-Anweisungen wurden erkannt. Bestätige vor der Stapelausführung den Auswirkungsbereich jeder Anweisung.", + "ai_chat.inspection.sql_risk.warning.safety_blocked": "Die aktuelle AI-Sicherheitsrichtlinie erlaubt kein SQL vom Typ {{operationType}}.", + "ai_chat.inspection.sql_risk.warning.safety_blocked_unknown": "Die aktuelle AI-Sicherheitsrichtlinie erlaubt diesen SQL-Operationstyp nicht.", + "ai_chat.inspection.sql_risk.error.inspect_failed": "SQL-Risiko konnte nicht geprüft werden: {{detail}}", + "ai_chat.inspection.sql_risk.message.no_active_query_sql": "Der aktuelle aktive Tab ist kein SQL-Abfragetab, oder der Editor enthält keinen SQL-Inhalt.", + "ai_chat.inspection.sql_risk.message.no_sql": "Es wurde kein SQL übergeben, und es gibt keinen lesbaren aktiven SQL-Abfragetab.", + "ai_chat.inspection.sql_risk.next_action.provide_sql": "Übergib zuerst den Parameter sql oder wechsle zu einem Abfragetab mit SQL-Entwurf.", + "ai_chat.inspection.sql_risk.next_action.explain_and_confirm": "Erkläre dem Benutzer zuerst die Risikopunkte und frage dann, ob fortgefahren werden soll.", + "ai_chat.inspection.sql_risk.next_action.confirm_write_scope": "Bei Schreib- oder DDL-Anweisungen zuerst WHERE, Backups, Zieldatenbank und Auswirkungsbereich bestätigen.", + "ai_chat.inspection.sql_risk.next_action.read_only_check_target": "Nur-Lese-Abfragen haben ein geringeres Risiko, aber Zielverbindung und Datenbankname sollten trotzdem zuerst geprüft werden.", + "ai_chat.inspection.database_bundle.error.db_name_required": "dbName darf nicht leer sein", + "ai_chat.inspection.database_bundle.error.table_name_required": "tableName darf nicht leer sein", + "ai_chat.inspection.database_bundle.error.database_overview_failed": "Datenbankstrukturübersicht konnte nicht erstellt werden: {{detail}}", + "ai_chat.inspection.database_bundle.error.table_snapshot_failed": "Tabellenstruktur-Snapshot konnte nicht erstellt werden: {{detail}}", + "ai_chat.inspection.database_bundle.error.unknown": "Unbekannter Fehler", + "ai_chat.inspection.database_bundle.warning.all_columns_failed": "Spaltenübersicht konnte nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.database_bundle.warning.columns_failed": "Spaltenliste konnte nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.database_bundle.warning.ddl_failed": "DDL konnte nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.database_bundle.warning.foreign_keys_failed": "Fremdschlüsselbeziehungen konnten nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.database_bundle.warning.indexes_failed": "Indexdefinitionen konnten nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.database_bundle.warning.sample_rows_failed": "Beispieldaten konnten nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.database_bundle.warning.tables_failed": "Tabellenliste konnte nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.database_bundle.warning.tables_failed_with_column_fallback": "Tabellenliste konnte nicht abgerufen werden, es wurde auf Spaltenübersicht zurückgegriffen: {{detail}}", + "ai_chat.inspection.database_bundle.warning.triggers_failed": "Trigger konnten nicht abgerufen werden: {{detail}}", + "ai_chat.inspection.setup.blocker.no_active_provider": "Kein aktiver AI-Anbieter ist ausgewählt", + "ai_chat.inspection.setup.blocker.missing_secret": "Dem aktiven Anbieter fehlt ein API Key / Secret", + "ai_chat.inspection.setup.blocker.missing_base_url": "Dem aktiven Anbieter fehlt eine base URL", + "ai_chat.inspection.setup.blocker.missing_model": "Beim aktiven Anbieter ist kein Modell ausgewählt", + "ai_chat.inspection.setup.next_action.select_provider": "Füge in den AI-Einstellungen zuerst einen aktiven Anbieter hinzu und wähle ihn aus", + "ai_chat.inspection.setup.next_action.fill_secret": "Trage das Secret des aktiven Anbieters ein", + "ai_chat.inspection.setup.next_action.fill_base_url": "Trage die baseUrl des aktiven Anbieters ein", + "ai_chat.inspection.setup.next_action.select_model": "Wähle ein verfügbares Modell für den aktiven Anbieter aus", + "ai_chat.inspection.setup.next_action.wait_models": "Warte, bis die Modellliste geladen ist, und bestätige dann das aktive Modell erneut", + "ai_chat.inspection.setup.next_action.add_mcp_server": "Um AI-Tool-Funktionen zu erweitern, füge mindestens einen MCP-Server hinzu und teste ihn", + "ai_chat.inspection.setup.next_action.connect_external_client": "Damit externe Agents GoNavi MCP verwenden können, verbinde lokale Clients wie Claude Code/Codex oder konfiguriere eine Remote-MCP-Bridge für Cloud-Agents", + "ai_chat.inspection.setup.next_action.test_mcp_servers": "Teste jeden aktivierten MCP-Server und bestätige, dass Befehl, Argumente und Umgebungsvariablen Tools korrekt erkennen", + "ai_chat.inspection.setup.next_action.add_guidance": "Um Antwortstil oder Workflow festzulegen, ergänze benutzerdefinierte Prompts oder aktiviere Skills", + "ai_chat.inspection.setup.next_action.attach_schema_context": "Für genauere SQL- oder Strukturvorschläge verknüpfe zuerst das Zieltabellen-schema mit dem AI-Kontext", + "ai_chat.inspection.setup.warning.loading_models": "Die Modellliste wird noch geladen, daher ist die Modellauswahl noch nicht abgeschlossen", + "ai_chat.inspection.setup.warning.no_mcp_servers": "Es sind noch keine MCP-Server konfiguriert", + "ai_chat.inspection.setup.warning.external_client_not_connected": "Claude Code / Codex ist noch nicht als lokaler Client mit dem aktuellen GoNavi MCP verbunden; OpenClaw/Hermans benötigen eine Remote-Bridge", + "ai_chat.inspection.setup.warning.no_mcp_tools": "MCP-Server sind aktiviert, aber es wurden noch keine verfügbaren MCP-Tools erkannt", + "ai_chat.inspection.setup.warning.no_guidance": "Es sind keine benutzerdefinierten Prompts oder Skills konfiguriert", + "ai_chat.inspection.setup.warning.no_schema_context": "Der Chat ist bereit, aber es ist noch kein Tabellen-schema-Kontext angehängt", + "ai_chat.inspection.setup.message.ready": "Der AI-Setup-Zustand ist bestanden; Anbieter, Chat-Voraussetzungen und MCP-Laufzeitpfad sind verfügbar", + "ai_chat.inspection.setup.message.blocked": "Das AI-Setup hat {{count}} Blocker; behebe zuerst den aktiven Anbieter und die Chat-Voraussetzungen", + "ai_chat.inspection.setup.message.needs_attention": "Das AI-Setup ist insgesamt nutzbar, aber {{count}} Empfehlungen können noch optimiert werden", + "ai_chat.inspection.ai_config.error.inspect_ai_setup_health": "Aktuelles AI-Setup konnte nicht geprüft werden", + "ai_chat.inspection.ai_config.error.inspect_ai_runtime": "Aktueller AI-Laufzeitstatus konnte nicht gelesen werden", + "ai_chat.inspection.ai_config.error.inspect_ai_safety": "Aktuelle AI-Sicherheitsgrenze konnte nicht gelesen werden", + "ai_chat.inspection.ai_config.error.inspect_ai_providers": "Aktuelle AI-Anbieterkonfiguration konnte nicht gelesen werden", + "ai_chat.inspection.ai_config.error.inspect_ai_chat_readiness": "AI-Chat-Voraussetzungen konnten nicht gelesen werden", + "ai_chat.inspection.ai_config.error.inspect_ai_tool_catalog": "AI-Toolkatalog konnte nicht gelesen werden", + "ai_chat.inspection.ai_config.error.inspect_mcp_setup": "MCP-Setup-Status konnte nicht gelesen werden", + "ai_chat.inspection.ai_config.error.inspect_mcp_remote_access": "MCP-Remotezugriff-Hinweise konnten nicht gelesen werden", + "ai_chat.inspection.mcp_remote.strategy.reverse_proxy.title": "Interner Reverse Proxy", + "ai_chat.inspection.mcp_remote.strategy.reverse_proxy.detail": "Geeignet, wenn Windows GoNavi und der Cloud-Agent bereits ein vertrauenswürdiges Intranet oder Gateway teilen.", + "ai_chat.inspection.mcp_remote.strategy.reverse_proxy.risk": "Beschränke Quell-IP, TLS und Bearer Token weiter auf Gateway-Ebene; nicht direkt öffentlich freigeben.", + "ai_chat.inspection.mcp_remote.strategy.ssh_reverse_tunnel.title": "SSH-Reverse-Tunnel", + "ai_chat.inspection.mcp_remote.strategy.ssh_reverse_tunnel.detail": "Geeignet, um Windows 127.0.0.1:8765 vorübergehend auf Cloud-Linux abzubilden. Einfach einzurichten, aber SSH-Konto und Port müssen kontrolliert sein.", + "ai_chat.inspection.mcp_remote.strategy.ssh_reverse_tunnel.risk": "Nach einem Tunnelabbruch ist der Cloud-Agent nicht verfügbar; passend für PoC oder kontrollierte Betriebsumgebungen.", + "ai_chat.inspection.mcp_remote.strategy.cloudflare_tunnel.title": "Cloudflare Tunnel", + "ai_chat.inspection.mcp_remote.strategy.cloudflare_tunnel.detail": "Geeignet für Windows-Rechner ohne festen öffentlichen Einstiegspunkt, mit zusätzlicher Identitätsprüfung über Cloudflare Access.", + "ai_chat.inspection.mcp_remote.strategy.cloudflare_tunnel.risk": "Access- / Zero-Trust-Regeln müssen aktiv sein; verlasse dich nicht nur auf eine zufällige URL.", + "ai_chat.inspection.mcp_remote.strategy.tailscale.title": "Tailscale / WireGuard", + "ai_chat.inspection.mcp_remote.strategy.tailscale.detail": "Geeignet, wenn Windows GoNavi und der Cloud-Agent im selben privaten Netzwerk liegen und bevorzugt interne Adressen nutzen.", + "ai_chat.inspection.mcp_remote.strategy.tailscale.risk": "Kontrolliere ACLs, sodass nur der Ziel-Agent den GoNavi MCP-Port erreichen kann.", + "ai_chat.inspection.mcp_remote.strategy.custom.title": "Eigene Bridge", + "ai_chat.inspection.mcp_remote.strategy.custom.detail": "Geeignet, wenn bereits ein Unternehmens-Gateway, Bastion Host oder dediziertes MCP-Gateway vorhanden ist.", + "ai_chat.inspection.mcp_remote.strategy.custom.risk": "TLS, Authentifizierung, Audit und Quellbeschränkungen müssen klar definiert sein, damit lokale Datenbankfunktionen nicht unbekannten Agents offengelegt werden.", + "ai_chat.inspection.mcp_remote.next_action.start_local_http": "Starte den GoNavi MCP HTTP-Modus auf Windows und prüfe, ob /healthz erreichbar ist.", + "ai_chat.inspection.mcp_remote.next_action.expose_mcp_only": "Gib nur /mcp über Tunnel, Reverse Proxy oder privates Netzwerk für den Ziel-Cloud-Agent frei.", + "ai_chat.inspection.mcp_remote.next_action.configure_agent": "Konfiguriere Streamable HTTP MCP URL und Authorization Bearer Token in OpenClaw/Hermans.", + "ai_chat.inspection.mcp_remote.next_action.inspect_connections": "Rufe zuerst get_connections auf, um connectionId zu erhalten, und lies danach Schemas; kopiere keine Datenbankpasswörter in den Cloud-Agent.", + "ai_chat.inspection.mcp_remote.warning.missing_public_url": "Es wurde keine für den Cloud-Agent erreichbare MCP URL angegeben; ein Remote-Agent kann nicht direkt auf Windows 127.0.0.1 zugreifen.", + "ai_chat.inspection.mcp_remote.warning.non_https_public_url": "Die Remote-MCP URL ist nicht HTTPS; wenn es keine private Netzwerkadresse ist, nutze TLS oder einen kontrollierten Tunnel.", + "ai_chat.inspection.mcp_remote.warning.missing_token": "Bearer Token wurde noch nicht bestätigt; HTTP MCP muss einen zufälligen token verwenden und darf nicht ohne Authentifizierung freigegeben werden.", + "ai_chat.inspection.mcp_remote.message.with_public_url": "Der Remote-Agent soll GoNavi MCP über {{publicUrl}} erreichen und sich mit Bearer Token authentifizieren", + "ai_chat.inspection.mcp_remote.message.no_public_url": "Der Remote-Agent muss den Windows GoNavi MCP HTTP-Endpunkt über einen kontrollierten Tunnel oder Reverse Proxy erreichen", + "ai_chat.inspection.mcp_remote.security.recommended_bind_address": "127.0.0.1, außer davor liegt ein kontrolliertes Gateway oder privates Netzwerk", + "ai_chat.inspection.ai_config.error.inspect_mcp_authoring_guide": "MCP-Erstellungshinweise konnten nicht gelesen werden", + "ai_chat.inspection.ai_config.error.inspect_mcp_draft": "MCP-Entwurf konnte nicht validiert werden", + "ai_chat.inspection.ai_config.error.inspect_mcp_docker_setup": "Docker-MCP-Setup konnte nicht geprüft werden", + "ai_chat.inspection.ai_config.error.inspect_mcp_tool_schema": "MCP-Toolparameter-schema konnte nicht gelesen werden", + "ai_chat.inspection.ai_config.error.inspect_ai_guidance": "Aktuelle AI-Prompts- und Skills-Konfiguration konnte nicht gelesen werden", + "ai_chat.inspection.current_connection.no_active": "Aktuell ist keine aktive Verbindung ausgewählt", + "ai_chat.inspection.current_connection.cache_missing": "Die aktuell aktive Verbindung ist im lokalen Cache nicht vorhanden", + "ai_chat.inspection.ai_context.linked_summary": "Aktuell sind {{count}} Tabellenschema-Kontexte verknüpft", + "ai_chat.inspection.ai_context.none_linked": "Aktuell ist kein AI-Tabellenschema-Kontext verknüpft", + "ai_chat.inspection.connection_capabilities.no_connection": "Es ist keine Verbindung für die Fähigkeitsanalyse verfügbar", + "ai_chat.inspection.connection_capabilities.cache_missing": "Die Zielverbindung ist im lokalen Cache nicht vorhanden", + "ai_chat.inspection.connection_capabilities.hint.readonly_result": "Abfrageergebnisse dieser Datenquelle werden standardmäßig schreibgeschützt angezeigt und können nicht direkt bearbeitet werden.", + "ai_chat.inspection.connection_capabilities.hint.editable_result": "Abfrageergebnisse dieser Datenquelle können bei verfügbaren Lokalisierungsbedingungen in den Bearbeitungspfad wechseln.", + "ai_chat.inspection.connection_capabilities.hint.manual_total_count": "Gesamtzahlen sollten manuelle oder verzögerte Zählung bevorzugen, statt sich direkt auf schnelle Gesamtzahlen zu verlassen.", + "ai_chat.inspection.connection_capabilities.hint.regular_total_count": "Gesamtzahlen können den regulären Zählpfad bevorzugen.", + "ai_chat.inspection.connection_capabilities.hint.approximate_table_count": "Beim Durchsuchen von Tabellen können ungefähre Zeilenzahlen angezeigt werden, um Zählaufwand bei großen Tabellen zu reduzieren.", + "ai_chat.inspection.connection_capabilities.hint.exact_table_count": "Beim Durchsuchen von Tabellen werden standardmäßig keine ungefähren Zeilenzahlen verwendet.", + "ai_chat.inspection.connection_capabilities.hint.message_publish_supported": "Diese Datenquelle bietet einen Testeingang zum Senden von Nachrichten und eignet sich für Topic/Queue-Integrationstests.", + "ai_chat.inspection.connection_capabilities.hint.message_publish_unsupported": "Diese Datenquelle stellt keinen Testeingang zum Senden von Nachrichten bereit.", + "ai_chat.inspection.connection_capabilities.summary": "Aktuelle Verbindung {{connectionName}} ({{type}}) stellt {{count}} Frontend-Fähigkeitssignale bereit", + "ai_chat.inspection.workspace.no_active_tab": "Aktuell ist kein aktiver Tab ausgewählt", + "ai_chat.inspection.redis_topology.label.single": "Standalone Redis", + "ai_chat.inspection.redis_topology.label.cluster": "Redis Cluster", + "ai_chat.inspection.redis_topology.label.sentinel": "Redis Sentinel", + "ai_chat.inspection.redis_topology.warning.missing_host": "host ist leer; trage vor dem Verbinden einen Redis-Knoten oder eine Sentinel-Adresse ein", + "ai_chat.inspection.redis_topology.warning.ssh_unsupported": "{{topologyLabel}} wird vom aktuellen Backend nicht mit SSH-Tunneln unterstützt; nutze direkte Verbindung, Proxy oder remote MCP HTTP", + "ai_chat.inspection.redis_topology.warning.missing_sentinel_master": "Sentinel master name ist leer; go-redis FailoverClient kann den primary node nicht finden", + "ai_chat.inspection.redis_topology.warning.single_sentinel_node": "Es ist nur ein Sentinel-Knoten konfiguriert; trage mindestens 2-3 Sentinel-Adressen ein, um einen Single Point of Failure zu vermeiden", + "ai_chat.inspection.redis_topology.warning.sentinel_default_redis_port": "Die primäre Sentinel-Adresse verwendet port 6379; bestätige, dass dies ein Sentinel port ist, üblich ist 26379", + "ai_chat.inspection.redis_topology.warning.cluster_single_seed": "Es ist nur ein Cluster seed node konfiguriert; füge mehrere master/replica nodes hinzu, um die Discovery-Zuverlässigkeit zu erhöhen", + "ai_chat.inspection.redis_topology.warning.cluster_logical_db": "Redis Cluster unterstützt physisch nur db0; GoNavi emuliert logische DB-Trennung mit dem Prefix __gonavi_db_N__:", + "ai_chat.inspection.redis_topology.warning.cluster_sentinel_fields_ignored": "Sentinel master und Sentinel user Felder wirken im Cluster mode nicht", + "ai_chat.inspection.redis_topology.warning.single_multiple_nodes": "Standalone mode enthält mehrere node addresses; das Backend nutzt den multi-node Cluster path, daher explizit auf Cluster mode wechseln", + "ai_chat.inspection.redis_topology.warning.single_sentinel_fields_ignored": "Sentinel-Felder wirken im Standalone mode nicht; wechsle zu Sentinel mode, wenn Sentinel discovery erforderlich ist", + "ai_chat.inspection.redis_topology.db_note.cluster_logical_namespace": "Redis Cluster unterstützt physisch nur db0; GoNavi emuliert eine multi-DB view mit dem Prefix __gonavi_db_N__:", + "ai_chat.inspection.redis_topology.db_note.sentinel_selected_db": "Nachdem Sentinel den master gefunden hat, verbindet GoNavi zur ausgewählten DB und behält die Sentinel-Einstellungen beim Wiederverbinden bei.", + "ai_chat.inspection.redis_topology.db_note.single_selected_db": "Standalone mode verwendet Redis SELECT DB direkt.", + "ai_chat.inspection.redis_topology.next_action.fill_host": "Trage zuerst den host ein; verwende Sentinel-Adressen für Sentinel mode und Redis Cluster seed nodes für Cluster mode.", + "ai_chat.inspection.redis_topology.next_action.fill_sentinel_master": "Trage den Sentinel master name ein, zum Beispiel mymaster.", + "ai_chat.inspection.redis_topology.next_action.disable_ssh": "Deaktiviere den SSH-Tunnel und nutze direkte Verbindung, Proxy/VPN oder GoNavi MCP HTTP, damit der remote Agent über lokales GoNavi auf Redis zugreifen kann.", + "ai_chat.inspection.redis_topology.next_action.check_sentinel_port": "Ändere den port der primären Sentinel-Adresse auf 26379, sofern dein Sentinel nicht ausdrücklich einen anderen port verwendet.", + "ai_chat.inspection.redis_topology.next_action.review_cluster_logical_db": "Prüfe, ob der Workload wirklich eine Redis Cluster multi-DB view benötigt; wenn es nur um key-Gruppierung geht, bevorzuge einen Application Namespace.", + "ai_chat.inspection.redis_topology.next_action.align_single_topology": "Wechsle explizit in den Cluster mode oder entferne zusätzliche nodes und behalte eine Standalone-Adresse, um Abweichungen zwischen konfigurierter und Backend-Topologie zu vermeiden.", + "ai_chat.inspection.redis_topology.next_action.test_connection": "Die Konfiguration wirkt für {{topologyLabel}} nutzbar; teste als Nächstes die Verbindung und prüfe den Redis DB/key tree.", + "ai_chat.inspection.redis_topology.recommendation.sentinel_addresses": "Bestätige, dass host und extra nodes Sentinel-Adressen sind, nicht Redis master addresses.", + "ai_chat.inspection.redis_topology.recommendation.separate_auth": "Trage Redis data-node credentials und Sentinel credentials getrennt ein; nicht vermischen.", + "ai_chat.inspection.redis_topology.recommendation.cluster_multiple_seeds": "Bevorzuge mindestens zwei seed nodes und bestätige, dass sie zum selben Redis Cluster gehören.", + "ai_chat.inspection.redis_topology.recommendation.cluster_namespace": "Wenn eine multi-DB view erforderlich ist, bevorzuge explizite Namespaces in business keys, um das physische db0-Limit von Cluster nicht falsch zu verstehen.", + "ai_chat.inspection.redis_topology.recommendation.single_one_address": "Standalone mode sollte eine Redis-Adresse enthalten; bei mehreren nodes nutze stattdessen Cluster oder Sentinel mode.", + "ai_chat.inspection.redis_topology.recommendation.network_for_cluster_sentinel": "Für netzübergreifenden Redis Cluster/Sentinel-Zugriff bevorzuge Netzwerkproxy, VPN oder GoNavi MCP HTTP statt eines Single-Port-SSH-Tunnels.", + "ai_chat.inspection.redis_topology.recommendation.check_tls": "TLS ist aktiviert; falls die Verbindung fehlschlägt, prüfe zuerst sslMode, CA-/certificate paths und server SNI.", + "ai_chat.inspection.external_sql_file.error.read_failed": "Fehler beim Lesen der externen SQL-Datei: {{detail}}", + "ai_chat.inspection.external_sql_file.error.file_path_required": "filePath darf nicht leer sein", + "ai_chat.inspection.external_sql_file.error.outside_configured_directory": "Die Zieldatei liegt nicht in den konfigurierten externen SQL-Verzeichnissen", + "ai_chat.inspection.external_sql_file.error.unsupported_runtime": "Die aktuelle Laufzeitumgebung unterstützt das Lesen lokaler SQL-Dateien noch nicht", + "ai_chat.inspection.external_sql_file.error.unknown": "Unbekannter Fehler", + "ai_chat.inspection.ai_sessions.untitled": "Unbenannte Sitzung", + "ai_chat.inspection.app_log.message.no_keyword_match": "In den letzten Logs wurden keine Einträge zum Schlüsselwort \"{{keyword}}\" gefunden.", + "ai_chat.inspection.app_log.message.no_readable_entries": "In den letzten Logs sind keine lesbaren Einträge verfügbar.", + "ai_chat.inspection.message_flow.warning.unresolved_tool_calls": "{{count}} Tool-Aufrufe haben keine passende tool-Ergebnismeldung.", + "ai_chat.inspection.message_flow.warning.consecutive_assistant": "{{count}} aufeinanderfolgende assistant-Nachrichtenpaare wurden gefunden; eine Antwort wurde möglicherweise auf mehrere Bubbles aufgeteilt.", + "ai_chat.inspection.message_flow.warning.empty_assistant": "{{count}} leere assistant-Nachrichten wurden gefunden.", + "ai_chat.inspection.message_flow.warning.loading_message": "Die Sitzung enthält noch eine loading-Nachricht; Streaming kann noch aktiv sein oder eine frühere Unterbrechung wurde nicht bereinigt.", + "ai_chat.inspection.message_flow.next_action.check_tool_results": "Prüfe zuerst, ob useAIChatLocalTools für jede tool_call_id eine tool-Nachricht schreibt.", + "ai_chat.inspection.message_flow.next_action.check_stream_append": "Prüfe, ob die Streaming-Append-Logik dieselbe assistantMsgId wiederverwendet, statt für dieselbe Antwort eine neue assistant-Nachricht anzulegen.", + "ai_chat.inspection.message_flow.next_action.check_empty_assistant": "Prüfe, ob Ausnahme- oder Abbruchpfade leere assistant-Platzhalternachrichten hinterlassen haben.", + "ai_chat.inspection.message_flow.next_action.inspect_render_or_logs": "Im Nachrichtenfluss wurde keine offensichtliche Strukturstörung gefunden; fahre mit inspect_ai_last_render_error oder inspect_app_logs für Rendering-/Laufzeitdiagnosen fort.", + "ai_chat.inspection.sql_editor_transaction.no_active_tab": "Aktuell ist kein aktiver Tab ausgewählt", + "ai_chat.inspection.sql_editor_transaction.not_sql_tab": "Der aktuell aktive Tab ist kein SQL-Editor-Tab", + "ai_chat.inspection.sql_editor_transaction.semantics.managed_dml": "Wenn der SQL-Editor INSERT/UPDATE/DELETE/MERGE/REPLACE-DML ausführt, wird zuerst eine verwaltete Transaktion geöffnet. Die Commit-Einstellung bestimmt nur, wann nach erfolgreicher Ausführung COMMIT erfolgt.", + "ai_chat.inspection.sql_editor_transaction.semantics.explicit_transaction": "Explizite Anweisungen zur Transaktionssteuerung wurden erkannt, daher legt GoNavi keine weitere vom SQL-Editor verwaltete Transaktion darum.", + "ai_chat.inspection.sql_editor_transaction.semantics.no_managed_transaction": "Das aktuelle SQL löst keine vom SQL-Editor verwaltete Transaktion aus. Schreibgeschützte Abfragen verwenden weiterhin den normalen Abfragepfad.", + "ai_chat.inspection.sql_editor_transaction.warning.pending_transactions": "Es gibt {{count}} vom SQL-Editor verwaltete Transaktionen, die auf Commit oder Rollback warten", + "ai_chat.inspection.sql_editor_transaction.warning.active_pending_transaction": "Der aktive SQL-Tab hat bereits eine ausstehende Transaktion. Vor neuer DML-Ausführung erst committen oder zurückrollen.", + "ai_chat.inspection.sql_editor_transaction.warning.auto_commit_managed_dml": "Auto-Commit ist aktiviert, aber DML geht weiterhin zuerst in eine verwaltete Transaktion und führt COMMIT erst nach Ablauf der Verzögerung aus.", + "ai_chat.inspection.sql_editor_transaction.warning.explicit_transaction_control": "Das aktuelle SQL enthält bereits explizite Transaktionssteuerung, daher übernimmt der SQL-Editor Commit oder Rollback nicht.", + "ai_chat.inspection.sql_editor_transaction.next_action.resolve_active_pending": "Den Benutzer bitten, in der Transaktionsleiste im Ergebnisbereich auf „Commit“ oder „Rollback“ zu klicken, oder das Ende des Auto-Commit-Countdowns abzuwarten.", + "ai_chat.inspection.sql_editor_transaction.next_action.switch_to_pending_tab": "Vor weiterer DML-Ausführung zum passenden SQL-Tab zurückwechseln und die ausstehende Transaktion auflösen.", + "ai_chat.inspection.sql_editor_transaction.next_action.explain_auto_commit": "Erklären, dass die aktuelle DML zuerst eine verwaltete Transaktion öffnet und nach erfolgreicher Ausführung nach etwa {{seconds}} Sekunden automatisch committet.", + "ai_chat.inspection.sql_editor_transaction.next_action.explain_manual_commit": "Erklären, dass die aktuelle DML zuerst eine verwaltete Transaktion öffnet und nach erfolgreicher Ausführung manuell Commit oder Rollback benötigt.", + "ai_chat.inspection.sql_editor_transaction.next_action.switch_to_sql_tab": "Zuerst zu einem Abfrage-Tab mit SQL-Entwurf wechseln oder den Benutzer bitten, das auszuführende SQL einzufügen.", + "ai_chat.inspection.sql_editor_transaction.next_action.inspect_recent_activity": "Die letzten Schreib- oder Transaktionsergebnisse in recentRelevantLogs prüfen und bei Bedarf inspect_recent_sql_activity für Details aufrufen.", + "ai_chat.inspection.sql_editor_transaction.commit_policy.semantics": "Der SQL-Editor führt INSERT/UPDATE/DELETE/MERGE/REPLACE-DML in einer verwalteten Transaktion aus. Manueller oder automatischer Modus steuert nur, wann COMMIT nach erfolgreicher Ausführung passiert, nicht ob eine Transaktion geöffnet wird.", + "ai_chat.inspection.sql_log.unspecified_database": "(Keine Datenbank angegeben)", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.desc": "Neueste SQL-Ausführungsprotokolle anzeigen", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.detail": "Akzeptiert optionale Filter limit und status und gibt aktuelle SQL-Ausführungsdatensätze mit Datenbank, Dauer, Erfolg oder Fehler, Fehlermeldung, betroffenen Zeilen und SQL-Text zurück. Nutze dies, um fehlgeschlagene Anweisungen nachzuverfolgen, langsame Abfragen zu finden und AI anhand realer Ausführungshistorie erklären oder optimieren zu lassen.", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.tool_description": "Ruft eine Zusammenfassung aktueller SQL-Ausführungsprotokolle ab, optional nach Erfolg oder Fehler gefiltert. Nutze dies, um kürzlich ausgeführtes SQL zu prüfen, Fehler zu diagnostizieren, langsame Abfragen zu finden und AI anhand realer Ausführungshistorie erklären oder optimieren zu lassen.", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.param.limit": "Optional. Anzahl der zurückzugebenden Protokolleinträge. Standard 20, Maximum 100.", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.param.status": "Optional. Nach Ausführungsstatus filtern: all, success oder error. Standard all.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.desc": "Verteilung der neuesten SQL-Aktivität zusammenfassen", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.detail": "Kann nach status, activityKind, dbName und keyword filtern und gibt eine strukturierte Zusammenfassung aktueller SQL-Aktivität zurück, einschließlich Verhältnis von Lesen/Schreiben/DDL, Verteilung der Anweisungstypen, Datenbankverteilung, aktuellen Fehlern, aktuellen Schreibvorgängen und langsamsten Anweisungen. Nutze dies, wenn gefragt wird, was zuletzt lief, ob Daten gelöscht wurden, welche Datenbank die meisten Fehler hat oder ob die Aktivität überwiegend lesend oder schreibend war.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.tool_description": "Fasst das strukturierte Profil aktueller SQL-Aktivität zusammen, optional nach Ausführungsstatus, Aktivitätstyp, Datenbankname und Schlüsselwort gefiltert. Nutze dies, um aktuelle Lese-/Schreibvorgänge, Fehlerhäufungen in einer Datenbank, DELETE- oder DDL-Aktivität zu prüfen und AI zuerst anhand der realen Ausführungslage urteilen zu lassen.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.limit": "Optional. Maximale Anzahl aktueller Aktivitätsbeispiele. Standard 30, Maximum 100.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.status": "Optional. Nach Ausführungsstatus filtern: all, success oder error. Standard all.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.activityKind": "Optional. Nach Aktivitätstyp filtern: all, read, write, ddl, transaction, session oder other. Standard all.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.dbName": "Optional. Nur Protokolle einschließen, deren Datenbankname dieses Schlüsselwort enthält.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.keyword": "Optional. Nach SQL-Text, Fehlermeldung, Anweisungstyp oder Datenbankname filtern.", + "ai_chat.inspection.tool_info.inspect_sql_editor_transaction.desc": "Commit-Status der SQL-Editor-Transaktion anzeigen", + "ai_chat.inspection.tool_info.inspect_sql_editor_transaction.detail": "Gibt die Semantik verwalteter DML-Transaktionen im SQL-Editor, die aktuelle manuelle oder automatische Commit-Einstellung, ob der aktive SQL-Tab in eine verwaltete Transaktion wechselt, ausstehende Transaktionen sowie aktuelle Schreib- oder Transaktionsausführungen zurück. Nutze dies bei Fragen zur Bedeutung von manuellem oder automatischem Commit, zu uncommitted transactions oder dazu, ob update/insert/delete automatisch committet.", + "ai_chat.inspection.tool_info.inspect_sql_editor_transaction.tool_description": "Liest einen Snapshot des Transaktionsstatus im SQL-Editor, einschließlich der realen Semantik, dass DML immer in eine verwaltete Transaktion geht, aktuellem Commit-Modus, Auto-Commit-Verzögerung, ob der aktive SQL-Tab eine verwaltete Transaktion auslöst, ausstehenden Transaktionen und aktuellen Schreib- oder Transaktionsprotokollen. Nutze dies bei Fragen zu manuellem Commit, Auto Commit, uncommitted transactions oder ob DML nach Ausführung committet.", + "ai_chat.inspection.tool_info.inspect_sql_editor_transaction.param.includeSqlPreview": "Optional. Ob eine SQL-Vorschau aus dem aktiven SQL-Tab zurückgegeben wird. Standard true.", + "ai_chat.inspection.tool_info.inspect_sql_risk.desc": "Ausführungsrisiko für aktuelles oder angegebenes SQL prüfen", + "ai_chat.inspection.tool_info.inspect_sql_risk.detail": "Liest übergebenes SQL oder den Inhalt des aktuellen aktiven Abfrage-Tabs, erkennt mehrere Anweisungen, Schreibvorgänge, DDL, DELETE/UPDATE ohne WHERE, DROP/TRUNCATE und weitere Risiken und kombiniert das Ergebnis mit der aktuellen AI-Sicherheitsrichtlinie, um zu entscheiden, ob Ausführung erlaubt ist. Nutze dies, bevor AI SQL ausführt, Risiken erklärt oder bestätigt, ob eine SQL-Anweisung laufen darf.", + "ai_chat.inspection.tool_info.inspect_sql_risk.tool_description": "Prüft das Ausführungsrisiko von übergebenem SQL oder dem aktuellen SQL im aktiven Abfrage-Tab und gibt Anweisungsanzahl, Aktivitätstyp, Risikostufe, Risikopunkte, nötige Benutzerbestätigung und das aktuelle Ergebnis der AI-Sicherheitsrichtlinie zurück. Nutze dies vor Antworten oder Fortsetzung, wenn Benutzer Ausführen, Löschen, Aktualisieren, DDL, Batch-SQL oder die Ausführbarkeit einer SQL-Anweisung anfragen.", + "ai_chat.inspection.tool_info.inspect_sql_risk.param.sql": "Optional. Zu prüfendes SQL. Wenn leer, wird standardmäßig der SQL-Entwurf des aktuellen aktiven Abfrage-Tabs gelesen.", + "ai_chat.inspection.tool_info.inspect_sql_risk.param.previewCharLimit": "Optional. Maximale Zeichenanzahl für die SQL-Vorschau. Standard 12000, Maximum 40000.", + "ai_chat.message.tool_call.inspect_current_connection": "Aktuelle Verbindungsübersicht lesen", + "ai_chat.message.tool_call.inspect_connection_capabilities": "Aktuelle Verbindungsfähigkeitsmatrix lesen", + "ai_chat.message.tool_call.inspect_saved_connections": "Lokal gespeicherte Verbindungen prüfen", + "ai_chat.message.tool_call.inspect_redis_topology": "Redis-Topologiekonfiguration diagnostizieren", + "ai_chat.message.tool_call.inspect_external_sql_directories": "Externe SQL-Verzeichnisse prüfen", + "ai_chat.message.tool_call.inspect_external_sql_file": "Externe SQL-Datei lesen", + "ai_chat.message.tool_call.inspect_ai_sessions": "Lokale AI-Verlaufssitzungen prüfen", + "ai_chat.message.tool_call.inspect_active_tab": "Aktiven Tab lesen", + "ai_chat.message.tool_call.inspect_workspace_tabs": "Aktuelle Arbeitsbereich-Tabs prüfen", + "ai_chat.message.tool_call.inspect_recent_sql_logs": "Neueste SQL-Ausführungsprotokolle prüfen", + "ai_chat.message.tool_call.inspect_recent_sql_activity": "Neueste SQL-Aktivität zusammenfassen", + "ai_chat.message.tool_call.inspect_sql_editor_transaction": "Transaktionsstatus des SQL-Editors lesen", + "ai_chat.message.tool_call.inspect_app_logs": "GoNavi-Anwendungsprotokolle prüfen", + "ai_chat.message.tool_call.inspect_recent_connection_failures": "Neueste Verbindungsfehler zusammenfassen", + "ai_chat.message.tool_call.inspect_ai_last_render_error": "Letzten AI-Renderingfehler lesen", + "ai_chat.message.tool_call.inspect_ai_message_flow": "Aktuellen AI-Nachrichtenfluss diagnostizieren", + "ai_chat.message.tool_call.inspect_ai_context_budget": "Risiko des AI-Kontextumfangs diagnostizieren", + "ai_chat.message.tool_call.inspect_codebase_hotspots": "Codebasis-Hotspotdateien lesen", + "ai_chat.message.tool_call.inspect_saved_queries": "Lokal gespeicherte Abfragen suchen", + "ai_chat.message.tool_call.inspect_sql_snippets": "SQL-Snippetvorlagen lesen", + "ai_chat.message.tool_call.inspect_shortcuts": "Aktuelle Tastenkürzel lesen", + "ai_chat.message.tool_call.preview_table_rows": "Echte Beispieldaten anzeigen", + "ai_chat.message.tool_call.execute_sql": "Schreibgeschützte SQL-Prüfung ausführen", "ai_chat.message.tool_call.running": "Datenprüfungen werden ausgeführt...", "ai_chat.message.tool_call.done": "Datenprüfungen abgeschlossen ({{count}} Elemente)", "ai_chat.message.wait.connecting": "Verbindung wird hergestellt", @@ -3452,12 +4672,20 @@ "ai_chat.panel.tool_error.unknown_function": "Unbekannte Funktion: {{functionName}}", "ai_chat.panel.tool_error.fetch_databases_failed": "Datenbankliste konnte nicht abgerufen werden: {{detail}}", "ai_chat.panel.tool_error.fetch_tables_failed": "Tabellenliste konnte nicht abgerufen werden: {{detail}}", + "ai_chat.panel.tool_error.fetch_all_columns_failed": "Datenbankweite Spaltenzusammenfassung konnte nicht abgerufen werden: {{detail}}", "ai_chat.panel.tool_result.columns_exact_fields": "⚠️ Dies ist die exakte Feldliste der Tabelle {{tableName}}. Verwende beim Erzeugen von SQL nur diese field-Werte als Spaltennamen, exakt wie angegeben. Nicht ändern, abkürzen oder eigene Feldnamen bilden.\nVerfügbare Felder: {{fieldNames}}\nDetails: {{detailJson}}", "ai_chat.panel.tool_error.fetch_columns_failed": "Spaltenliste konnte nicht abgerufen werden: {{detail}}", + "ai_chat.panel.tool_error.fetch_indexes_failed": "Indexdefinitionen konnten nicht abgerufen werden: {{detail}}", + "ai_chat.panel.tool_error.fetch_foreign_keys_failed": "Fremdschlüsselbeziehungen konnten nicht abgerufen werden: {{detail}}", + "ai_chat.panel.tool_error.fetch_triggers_failed": "Triggerdefinitionen konnten nicht abgerufen werden: {{detail}}", "ai_chat.panel.tool_error.fetch_table_ddl_failed": "CREATE TABLE-Anweisung konnte nicht abgerufen werden: {{detail}}", + "ai_chat.panel.tool_error.table_name_required": "tableName darf nicht leer sein", + "ai_chat.panel.tool_error.preview_table_rows_failed": "Tabellenbeispieldaten konnten nicht angezeigt werden: {{detail}}", "ai_chat.panel.tool_error.sql_blocked": "Die Sicherheitsrichtlinie hat diese Anfrage blockiert: Die aktuelle Sicherheitsstufe erlaubt kein {{operationType}} SQL. Zeigen Sie dem Benutzer das SQL und bitten Sie ihn, es manuell auszuführen.", "ai_chat.panel.tool_error.sql_execute_failed": "SQL-Ausführung fehlgeschlagen", "ai_chat.panel.tool_error.sql_execute_exception": "SQL-Ausnahme bei der Ausführung: {{detail}}", + "ai_chat.panel.tool_error.mcp_failed": "MCP-Tool-Aufruf fehlgeschlagen", + "ai_chat.panel.tool_error.mcp_failed_with_detail": "MCP-Tool-Aufruf fehlgeschlagen: {{detail}}", "ai_chat.panel.error.unknown": "Unbekannter Fehler", "ai_chat.panel.error.http_server": "HTTP {{code}} Serverfehler", "ai_chat.panel.error.html_response": "Der Server hat eine ungewöhnliche HTML-Antwort zurückgegeben, möglicherweise wegen Gateway-Timeout oder nicht verfügbarem Dienst", @@ -3488,6 +4716,10 @@ "ai_settings.nav.safety.description": "Risikostufe von AI-Operationen begrenzen", "ai_settings.nav.context.title": "Kontext", "ai_settings.nav.context.description": "Datenbankschema-Kontext konfigurieren", + "ai_settings.nav.mcp.title": "MCP-Dienste", + "ai_settings.nav.mcp.description": "GoNavi mit externen Clients verbinden und Tool-Quellen verwalten", + "ai_settings.nav.skills.title": "Skills", + "ai_settings.nav.skills.description": "Wiederverwendbare Prompt-Module konfigurieren", "ai_settings.nav.tools.title": "Integrierte Tools", "ai_settings.nav.tools.description": "Von AI nutzbare Datenproben anzeigen", "ai_settings.nav.prompts.title": "Integrierte Prompts", @@ -3523,7 +4755,7 @@ "ai_settings.provider_preset.volcengine_coding.label": "Volcengine Coding Plan", "ai_settings.provider_preset.volcengine_coding.desc": "Ark Code / Coding Plan Ausführung", "ai_settings.provider_preset.minimax.label": "MiniMax", - "ai_settings.provider_preset.minimax.desc": "M2.7 / M2.5-Reihe (Anthropic-kompatibel)", + "ai_settings.provider_preset.minimax.desc": "M3 / M2.7-Reihe (Anthropic-kompatibel)", "ai_settings.provider_preset.ollama.label": "Ollama", "ai_settings.provider_preset.ollama.desc": "Lokal bereitgestellte Open-Source-Modelle", "ai_settings.provider_preset.custom.label": "Benutzerdefiniert", @@ -3580,6 +4812,19 @@ "ai_settings.context.with_results.label": "Mit Abfrageergebnissen", "ai_settings.context.with_results.desc": "Sendet aktuelle Abfrageergebnisse als Kontext", "ai_settings.prompts.description": "Dies sind die schreibgeschützten Systemprompts der aktuellen GoNavi-Version. Sie werden je nach Szenario dynamisch in den Anfragekontext eingefügt.", + "ai_settings.prompts.user.title": "Benutzerdefinierte Prompts auf Benutzerebene", + "ai_settings.prompts.user.description": "Dieser Inhalt wird nach den eingebauten Systemprompts als system message angehängt. Nutzen Sie ihn für persönlichen Antwortstil, Ausgabevorgaben oder Teamregeln. Bei Sicherheitsgrenzen haben Systemregeln weiterhin Vorrang.", + "ai_settings.prompts.field.global.title": "Globaler Zusatzprompt", + "ai_settings.prompts.field.global.description": "Gilt für alle AI-Sitzungen, zum Beispiel \"zuerst das Fazit nennen\" oder \"Antworten knapp halten\".", + "ai_settings.prompts.field.database.title": "Zusatzprompt für Datenbanksitzungen", + "ai_settings.prompts.field.database.description": "Gilt nur für Datenbank- und SQL-Szenarien, zum Beispiel \"Feldnamen vor dem Erzeugen von SQL bestätigen\".", + "ai_settings.prompts.field.jvm.title": "Zusatzprompt für JVM-Ressourcenanalyse", + "ai_settings.prompts.field.jvm.description": "Gilt nur für JVM-Ressourcen-Browsing und Analyse.", + "ai_settings.prompts.field.jvm_diagnostic.title": "Zusatzprompt für JVM-Diagnose", + "ai_settings.prompts.field.jvm_diagnostic.description": "Gilt nur für den JVM-Diagnosearbeitsbereich, zum Beispiel \"erst den Plan, dann die Befehle nennen\".", + "ai_settings.prompts.placeholder.empty": "Leer lassen, um nichts Zusätzliches anzuhängen", + "ai_settings.prompts.action.save": "Benutzerdefinierte Prompts speichern", + "ai_settings.prompts.builtin.description": "Dies sind die schreibgeschützten Low-Level-AI-Prompts der aktuellen GoNavi-Version. Sie werden im passenden Anfragekontext vor den obigen Prompts auf Benutzerebene eingefügt.", "ai_settings.prompts.message.saved": "Benutzerdefinierte Prompts gespeichert", "ai_settings.prompts.message.save_failed": "Benutzerdefinierte Prompts konnten nicht gespeichert werden", "ai_settings.mcp_server.message.saved": "MCP-Dienst gespeichert", @@ -3589,6 +4834,323 @@ "ai_settings.mcp_server.message.test_success": "MCP-Dienstverbindung erfolgreich", "ai_settings.mcp_server.message.test_failed": "MCP-Diensttest fehlgeschlagen", "ai_settings.mcp_server.message.test_request_failed": "MCP-Dienst konnte nicht getestet werden", + "ai_settings.mcp_server.draft.default_name": "MCP-Dienst", + "ai_settings.mcp_server.command_preview.title": "Vorschau der automatischen Aufteilung", + "ai_settings.mcp_server.command_preview.description": "Nach dem Klick auf \"Automatisch in die Felder unten aufteilen\" wird dieses Analyseergebnis in den Startkonfigurationsbereich unter dem Dienstnamen geschrieben.", + "ai_settings.mcp_server.command_preview.env_title": "Umgebungsvariablen", + "ai_settings.mcp_server.command_preview.env_count": "Es werden {{count}} Umgebungsvariablen geschrieben.", + "ai_settings.mcp_server.command_preview.env_empty": "In diesem Befehl wurden keine vorangestellten Umgebungsvariablen erkannt.", + "ai_settings.mcp_server.command_preview.empty_value": "Keine", + "ai_settings.mcp_server.command_preview.command_title": "Startbefehl", + "ai_settings.mcp_server.command_preview.command_hint": "Hier bleibt nur das eigentliche ausfuehrbare Programm erhalten.", + "ai_settings.mcp_server.command_preview.args_title": "Befehlsargumente", + "ai_settings.mcp_server.command_preview.args_count": "Es wird in {{count}} separate Argument-Tags aufgeteilt.", + "ai_settings.mcp_server.command_preview.args_empty": "In diesem Befehl wurden keine zusaetzlichen Argumente erkannt.", + "ai_settings.mcp_server.validation.title": "Konfigurationsprüfung", + "ai_settings.mcp_server.validation.severity.error": "Korrektur erforderlich", + "ai_settings.mcp_server.validation.severity.warning": "Prüfung empfohlen", + "ai_settings.mcp_server.validation.severity.info": "Hinweis", + "ai_settings.mcp_server.validation.summary.errors": "{{count}} Problem muss vor dem Testen oder Speichern behoben werden.", + "ai_settings.mcp_server.validation.summary.warnings": "{{count}} Prüfpunkt gefunden. Testen und Speichern sind weiterhin möglich.", + "ai_settings.mcp_server.validation.summary.ready": "Die aktuelle Konfiguration kann getestet und gespeichert werden.", + "ai_settings.mcp_server.validation.issue.name_missing.title": "Dienstname ist leer", + "ai_settings.mcp_server.validation.issue.name_missing.detail": "Verwende einen Zwecknamen wie Browser, GitHub oder Filesystem; sonst ist der Dienst nach dem Speichern nur über den Befehl erkennbar.", + "ai_settings.mcp_server.validation.issue.transport_unsupported.title": "Transport wird nicht unterstützt", + "ai_settings.mcp_server.validation.issue.transport_unsupported.detail": "GoNavi kann hier nur stdio-MCP-Dienste hinzufügen. Lass den Transport auf stdio.", + "ai_settings.mcp_server.validation.issue.command_missing.title": "Startbefehl fehlt", + "ai_settings.mcp_server.validation.issue.command_missing.detail": "Gib mindestens node, uvx, python oder einen lokalen ausführbaren Pfad ein. Skriptname und --stdio gehören in die Befehlsargumente.", + "ai_settings.mcp_server.validation.issue.command_whole_line.title": "Startbefehl enthält möglicherweise die ganze Befehlszeile", + "ai_settings.mcp_server.validation.issue.command_whole_line.detail": "Trage im Startbefehl nur die ausführbare Datei selbst ein. Verschiebe Skriptname, Modulname, --stdio und Umgebungsvariablen in Argumente oder Umgebungsvariablen.", + "ai_settings.mcp_server.validation.issue.args_missing_for_launcher.title": "Befehlsargumente enthalten möglicherweise keinen Skript- oder Modulnamen", + "ai_settings.mcp_server.validation.issue.args_missing_for_launcher.detail": "Launcher wie node, python, uvx und npx benötigen meist zusätzlich server.js, -m your_server oder einen Paketnamen als Argument.", + "ai_settings.mcp_server.validation.issue.docker_run_missing.title": "Docker-Argumenten fehlt run", + "ai_settings.mcp_server.validation.issue.docker_run_missing.detail": "Docker MCP nutzt meist command=docker; run, --rm, -i, der Imagename und Dienstargumente werden separat in args eingetragen.", + "ai_settings.mcp_server.validation.issue.docker_interactive_missing.title": "Docker-Argumenten fehlt -i", + "ai_settings.mcp_server.validation.issue.docker_interactive_missing.detail": "MCP muss weiter von der Standardeingabe lesen. Füge bei docker run -i oder --interactive hinzu, sonst kann die Tool-Erkennung sofort getrennt werden.", + "ai_settings.mcp_server.validation.issue.docker_image_missing.title": "Docker-Argumenten fehlt möglicherweise der Imagename", + "ai_settings.mcp_server.validation.issue.docker_image_missing.detail": "Trage den Imagenamen aus der README nach den docker run-Optionen ein, zum Beispiel mcp/server-fetch:latest.", + "ai_settings.mcp_server.validation.issue.args_contain_env_or_shell_glue.title": "Befehlsargumente enthalten möglicherweise Umgebungsvariablen oder Shell-Verkettung", + "ai_settings.mcp_server.validation.issue.args_contain_env_or_shell_glue.detail": "KEY=VALUE, $env:KEY=VALUE, set, env und && gehören in die automatische Aufteilung vollständiger Befehle oder in das Feld für Umgebungsvariablen.", + "ai_settings.mcp_server.validation.issue.timeout_out_of_range.title": "Timeout liegt außerhalb des empfohlenen Bereichs", + "ai_settings.mcp_server.validation.issue.timeout_out_of_range.detail": "GoNavi begrenzt den Wert auf 3 bis 120 Sekunden. Reguläre lokale Dienste nutzen meist 20 Sekunden; langsam startende Dienste 45 oder 60 Sekunden.", + "ai_settings.mcp_server.validation.issue.env_invalid_lines.title": "Umgebungsvariablen enthalten ungültige Zeilen", + "ai_settings.mcp_server.validation.issue.env_invalid_lines.detail": "Jede Zeile muss KEY=VALUE sein. {{count}} Zeile(n) werden nicht gespeichert: {{lines}}", + "ai_settings.mcp_server.help.field_state.required": "Erforderlich", + "ai_settings.mcp_server.help.field_state.fixed": "Fest", + "ai_settings.mcp_server.help.field_state.optional": "Optional", + "ai_settings.mcp_server.help.example_prefix": "Beispiel: ", + "ai_settings.mcp_server.form.name.title": "Dienstname", + "ai_settings.mcp_server.form.name.description": "Gib diesem MCP einen Namen, den du wiedererkennst. Er erscheint später direkt in der AI-Toolliste. Vermeide unklare Namen wie server oder test.", + "ai_settings.mcp_server.form.name.placeholder": "Dienstname, zum Beispiel: Filesystem / Browser / GitHub", + "ai_settings.mcp_server.form.enabled.title": "Aktiviert", + "ai_settings.mcp_server.form.enabled.description": "Bei vorübergehender Nichtnutzung deaktivieren; die Konfiguration bleibt gespeichert, nimmt aber nicht an der AI-Toolerkennung teil.", + "ai_settings.mcp_server.form.enabled.option.enabled": "Aktiviert", + "ai_settings.mcp_server.form.enabled.option.disabled": "Deaktiviert", + "ai_settings.mcp_server.form.transport.title": "Transport", + "ai_settings.mcp_server.form.transport.description": "Derzeit wird nur stdio unterstützt. GoNavi startet diesen Prozess lokal und kommuniziert über Standardeingabe und Standardausgabe.", + "ai_settings.mcp_server.form.command.title": "Startbefehl", + "ai_settings.mcp_server.form.command.description": "Trage hier nur den eigentlichen Befehl ein. Bei Startern wie npx/node/uvx/python/docker gehören Paketname, Skriptname, Modulname oder docker run-Argumente in das Argumentfeld unten. Füge hier nicht den kompletten Befehl wie npx -y package --stdio, node server.js --stdio oder docker run -i image ein.", + "ai_settings.mcp_server.form.command.placeholder": "Startbefehl, zum Beispiel: npx / node / uvx / python / docker", + "ai_settings.mcp_server.form.timeout.title": "Timeout (Sekunden)", + "ai_settings.mcp_server.form.timeout.description": "Maximale Wartezeit für eine Toolerkennung oder einen Toolaufruf. Für die meisten lokalen Tools reicht der Standardwert 20 Sekunden; erhöhe ihn für entfernte Dienste oder langsam startende Skripte.", + "ai_settings.mcp_server.form.timeout.placeholder": "Timeout (Sekunden)", + "ai_settings.mcp_server.form.timeout.preset.default": "Standard 20 Sekunden", + "ai_settings.mcp_server.form.timeout.preset.relaxed": "Großzügig 45 Sekunden", + "ai_settings.mcp_server.form.timeout.preset.slow": "Langsamer Start 60 Sekunden", + "ai_settings.mcp_server.form.args.title": "Befehlsargumente", + "ai_settings.mcp_server.form.args.description": "Trage jedes Argument als eigenes Tag ein; der Befehl selbst gehört nicht hierher. Für npx -y package --stdio trennst du -y, package und --stdio. Für node server.js --stdio trennst du server.js und --stdio. Für docker run --rm -i image trennst du run, --rm, -i und den Imagenamen. Wenn du unsicher bist, nutze oben zuerst das Feld für den vollständigen Befehl zur automatischen Aufteilung.", + "ai_settings.mcp_server.form.args.placeholder": "Befehlsargumente, mit Enter hinzufügen, z. B.: -y / package / --stdio", + "ai_settings.mcp_server.form.launch_preview.title": "Vorschau des tatsächlichen Startbefehls", + "ai_settings.mcp_server.form.launch_preview.description": "GoNavi startet den Prozess in der unten gezeigten Form, damit du prüfen kannst, ob Befehl und Argumente richtig getrennt wurden.", + "ai_settings.mcp_server.form.env.title": "Umgebungsvariablen", + "ai_settings.mcp_server.form.env.description": "Eine KEY=VALUE-Zeile pro Variable, meist für API Key, Arbeitsverzeichnis, Dienstadresse und ähnliche Konfiguration. Leer lassen, wenn nicht benötigt. Diese Werte werden lokal gespeichert und beim Start des MCP-Prozesses als Umgebungsvariablen übergeben; schreibe kein export und lege keine Geheimnisse in Chatinhalte.", + "ai_settings.mcp_server.form.env.placeholder": "Umgebungsvariablen, eine KEY=VALUE-Zeile pro Variable, z. B.:\nOPENAI_API_KEY=...\nGITHUB_TOKEN=...", + "ai_settings.mcp_server.form.env_status.invalid": "{{validCount}} Umgebungsvariable(n) erkannt; zusätzlich werden {{invalidCount}} ungültige Zeile(n) diesmal nicht gespeichert: {{invalidLines}}", + "ai_settings.mcp_server.form.env_status.valid": "{{count}} Umgebungsvariable(n) erkannt.", + "ai_settings.mcp_server.form.env_status.empty": "Jede Zeile muss KEY=VALUE sein; Zeilen ohne Gleichheitszeichen oder mit Leerzeichen im key werden nicht gespeichert.", + "ai_settings.mcp_server.form.instructions.title": "Aktionshinweise", + "ai_settings.mcp_server.form.instructions.test_title": "Toolerkennung testen", + "ai_settings.mcp_server.form.instructions.test_description": "startet einmal mit den aktuellen Feldern, prüft die erkennbaren Tools und speichert die Konfiguration nicht.", + "ai_settings.mcp_server.form.instructions.save_title": "Speichern", + "ai_settings.mcp_server.form.instructions.save_description": "schreibt dieses MCP dauerhaft in die lokale Konfiguration.", + "ai_settings.mcp_server.form.instructions.tools_found": "Die oben aufgeführten Tools sind die Aliase aus dem letzten erfolgreichen Test.", + "ai_settings.mcp_server.form.instructions.test_first": "Teste zuerst erfolgreich und speichere dann; nach bestandenem Test werden oben die tatsächlich gefundenen Tools dieses Dienstes angezeigt.", + "ai_settings.mcp_server.form.action.test": "Toolerkennung testen", + "ai_settings.mcp_server.form.action.save": "Speichern", + "ai_settings.mcp_server.form.action.delete": "Löschen", + "ai_settings.mcp_server.form.action.delete_confirm": "Diesen MCP-Dienst löschen?", + "ai_settings.mcp_server.form.action.delete_ok": "Löschen", + "ai_settings.mcp_server.form.action.delete_cancel": "Abbrechen", + "ai_settings.mcp_server.guide.examples.title": "Ausfüllbeispiele", + "ai_settings.mcp_server.guide.examples.description": "Trage bei Startbefehl nur das ausführbare Programm selbst ein. Vermische keine Argumente. Häufige Formen:", + "ai_settings.mcp_server.guide.order.title": "Empfohlene Ausfüllreihenfolge", + "ai_settings.mcp_server.guide.order.description": "Neue Benutzer können in dieser Reihenfolge vorgehen: zuerst oben eine Vorlage wählen oder den vollständigen Befehl einfügen, dann die Pflichtfelder prüfen und nur bei Bedarf Argumente, Umgebungsvariablen und Timeout ergänzen.", + "ai_settings.mcp_server.guide.step.template.title": "Vorlage / vollständiger Befehl", + "ai_settings.mcp_server.guide.step.template.detail": "Wähle zuerst die passendste Vorlage oder füge eine vollständige Befehlszeile ein, damit GoNavi sie automatisch aufteilen kann.", + "ai_settings.mcp_server.guide.step.name.title": "Servicename", + "ai_settings.mcp_server.guide.step.name.detail": "Vergib einen klar erkennbaren Zwecknamen wie Browser, GitHub oder Filesystem.", + "ai_settings.mcp_server.guide.step.command.title": "Startbefehl", + "ai_settings.mcp_server.guide.step.command.detail": "Hier steht nur der Programmname oder Launcher selbst, nicht die komplette Befehlszeile.", + "ai_settings.mcp_server.guide.step.args.title": "Befehlsargumente", + "ai_settings.mcp_server.guide.step.args.detail": "Trenne Skriptname, Modulname, Docker run-Argumente und Optionen wie --stdio in einzelne Einträge.", + "ai_settings.mcp_server.guide.step.env_timeout.title": "Umgebungsvariablen / Timeout", + "ai_settings.mcp_server.guide.step.env_timeout.detail": "Ergänze sie nur, wenn der Service wirklich zusätzliche Konfiguration benötigt; andernfalls leer lassen.", + "ai_settings.mcp_server.guide.field_lookup.title": "Feld-Schnellreferenz", + "ai_settings.mcp_server.guide.field_lookup.description": "Wenn unklar ist, was in ein Parameterfeld gehört, beginne hier. Jedes Feld unten enthält konkrete Beispiele und Hinweise.", + "ai_settings.mcp_server.guide.field.fill_label": "Ausfüllen: ", + "ai_settings.mcp_server.argument_hints.category.secret": "Sensibel", + "ai_settings.mcp_server.argument_hints.category.path": "Pfad", + "ai_settings.mcp_server.argument_hints.category.endpoint": "Endpunkt", + "ai_settings.mcp_server.argument_hints.category.network": "Netzwerk", + "ai_settings.mcp_server.argument_hints.category.mode": "Modus", + "ai_settings.mcp_server.argument_hints.category.runtime": "Laufzeit", + "ai_settings.mcp_server.argument_hints.category.generic": "Fachlich", + "ai_settings.mcp_server.argument_hints.current_command": "Argumenthinweise für den aktuellen Befehl {{command}}", + "ai_settings.mcp_server.argument_hints.argument_details": "Argumente einzeln erklärt", + "ai_settings.mcp_server.argument_hints.masked_value": "Wert ist maskiert", + "ai_settings.mcp_server.argument_hints.value_hint_prefix": "Ausfüllen: ", + "ai_settings.mcp_server.argument_hints.business_arguments": "Erkannte fachliche Argumente", + "ai_settings.mcp_server.argument_hints.dont_screenshot": "Echten Wert nicht als Screenshot teilen", + "ai_settings.mcp_server.argument_hints.next_actions": "Nächster Schritt: {{actions}}", + "ai_settings.mcp_server.argument_hints.action_separator": "; ", + "ai_settings.mcp_server.argument_hints.required_complete": "Die Pflichtargumente wirken vollständig. Wenn der Test fehlschlägt, prüfen Sie README, fachliche Argumente und Umgebungsvariablen erneut.", + "ai_settings.mcp_server.argument_hints.fill_missing_required": "Fehlende Pflichtargumente ergänzen: {{args}}", + "ai_settings.mcp_server.argument_hints.split_inline_args": "Startbefehlsfeld aufteilen: {{command}} behalten, {{count}} Argument(e) verschieben", + "ai_settings.mcp_server.env_hints.category.secret": "Geheimnis", + "ai_settings.mcp_server.env_hints.category.endpoint": "Adresse", + "ai_settings.mcp_server.env_hints.category.proxy": "Proxy", + "ai_settings.mcp_server.env_hints.category.path": "Pfad", + "ai_settings.mcp_server.env_hints.category.runtime": "Laufzeit", + "ai_settings.mcp_server.env_hints.category.generic": "Benutzerdefiniert", + "ai_settings.mcp_server.env_hints.title": "Hinweise zur Nutzung von Umgebungsvariablen", + "ai_settings.mcp_server.env_hints.summary": "{{envVarCount}} Variable(n) erkannt; {{secretLikeCount}} wirken wie Geheimnisse. Hier werden nur Zweck und Risiko des key erklärt; value wird nicht angezeigt.", + "ai_settings.mcp_server.env_hints.recognized": "Erkannt", + "ai_settings.mcp_server.env_hints.value_hint_prefix": "Ausfüllen: ", + "ai_settings.mcp_server.env_hints.empty_value": " Der aktuelle Wert ist leer.", + "ai_settings.mcp_server.env_hints.placeholder_value": " Der aktuelle Wert wirkt wie ein Beispiel-Platzhalter.", + "ai_settings.mcp_server.env_hints.warning_prefix": "Achtung: {{warnings}}", + "ai_settings.mcp_server.env_hints.next_actions": "Nächster Schritt: {{actions}}", + "ai_settings.mcp_server.env_hints.action_separator": "; ", + "ai_settings.mcp_server.env_hints.known.github_token.label": "GitHub Token", + "ai_settings.mcp_server.env_hints.known.github_token.detail": "Usually used by GitHub MCP services to read repositories, issues, pull requests, or Actions.", + "ai_settings.mcp_server.env_hints.known.github_token.value_hint": "Enter a GitHub Personal Access Token with the minimum permissions required by the MCP README.", + "ai_settings.mcp_server.env_hints.known.gitlab_token.label": "GitLab Token", + "ai_settings.mcp_server.env_hints.known.gitlab_token.detail": "Usually used by GitLab MCP services to access projects, merge requests, or CI.", + "ai_settings.mcp_server.env_hints.known.gitlab_token.value_hint": "Enter a GitLab Access Token and restrict it to the required project scope.", + "ai_settings.mcp_server.env_hints.known.openai_api_key.label": "OpenAI API Key", + "ai_settings.mcp_server.env_hints.known.openai_api_key.detail": "Used by MCP services that depend on OpenAI APIs for model or embedding calls.", + "ai_settings.mcp_server.env_hints.known.openai_api_key.value_hint": "Enter the real API Key; do not put it in command, args, or chat messages.", + "ai_settings.mcp_server.env_hints.known.anthropic_api_key.label": "Anthropic API Key", + "ai_settings.mcp_server.env_hints.known.anthropic_api_key.detail": "Used by MCP services that depend on the Anthropic Claude API.", + "ai_settings.mcp_server.env_hints.known.anthropic_api_key.value_hint": "Enter the real API Key only after confirming the service requires this variable.", + "ai_settings.mcp_server.env_hints.known.gemini_api_key.label": "Gemini API Key", + "ai_settings.mcp_server.env_hints.known.gemini_api_key.detail": "Used by MCP services that depend on the Google Gemini API.", + "ai_settings.mcp_server.env_hints.known.gemini_api_key.value_hint": "Enter the real API Key; some services may require GOOGLE_API_KEY instead.", + "ai_settings.mcp_server.env_hints.known.google_api_key.label": "Google API Key", + "ai_settings.mcp_server.env_hints.known.google_api_key.detail": "Used by Google, Gemini, Maps, or Search MCP services.", + "ai_settings.mcp_server.env_hints.known.google_api_key.value_hint": "Enter the real API Key and confirm whether the README requires GOOGLE_API_KEY or GEMINI_API_KEY.", + "ai_settings.mcp_server.env_hints.known.slack_bot_token.label": "Slack Bot Token", + "ai_settings.mcp_server.env_hints.known.slack_bot_token.detail": "Used by Slack MCP services to read channels, messages, or send notifications.", + "ai_settings.mcp_server.env_hints.known.slack_bot_token.value_hint": "Enter the Bot Token starting with xoxb- and restrict workspace permissions.", + "ai_settings.mcp_server.env_hints.known.notion_api_key.label": "Notion API Key", + "ai_settings.mcp_server.env_hints.known.notion_api_key.detail": "Used by Notion MCP services to access pages, databases, or workspace content.", + "ai_settings.mcp_server.env_hints.known.notion_api_key.value_hint": "Enter the Notion integration secret and authorize only the required pages.", + "ai_settings.mcp_server.env_hints.known.database_url.label": "Datenbank-Verbindungszeichenfolge", + "ai_settings.mcp_server.env_hints.known.database_url.detail": "Lets the MCP service connect to a database itself; this gives database connection information to that MCP process.", + "ai_settings.mcp_server.env_hints.known.database_url.value_hint": "Fill this only when the MCP must connect to the database directly; prefer GoNavi MCP to avoid password exposure.", + "ai_settings.mcp_server.env_hints.known.http_proxy.label": "HTTP-Proxy", + "ai_settings.mcp_server.env_hints.known.http_proxy.detail": "Routes HTTP resource access from the MCP process through the specified proxy.", + "ai_settings.mcp_server.env_hints.known.http_proxy.value_hint": "Enter http://host:port; treat it as sensitive if the proxy includes a username or password.", + "ai_settings.mcp_server.env_hints.known.https_proxy.label": "HTTPS-Proxy", + "ai_settings.mcp_server.env_hints.known.https_proxy.detail": "Routes HTTPS resource access from the MCP process through the specified proxy.", + "ai_settings.mcp_server.env_hints.known.https_proxy.value_hint": "Enter http://host:port or https://host:port.", + "ai_settings.mcp_server.env_hints.known.no_proxy.label": "Proxy-Bypass-Liste", + "ai_settings.mcp_server.env_hints.known.no_proxy.detail": "Specifies which domains or addresses should bypass the proxy.", + "ai_settings.mcp_server.env_hints.known.no_proxy.value_hint": "Use comma-separated entries, for example localhost,127.0.0.1,.corp.local.", + "ai_settings.mcp_server.env_hints.known.docker_host.label": "Docker-Daemon-Adresse", + "ai_settings.mcp_server.env_hints.known.docker_host.detail": "Tells the docker CLI which Docker Engine to connect to.", + "ai_settings.mcp_server.env_hints.known.docker_host.value_hint": "Common on Windows: npipe:////./pipe/docker_engine; confirm security boundaries for remote Docker.", + "ai_settings.mcp_server.env_hints.known.gonavi_mcp_http_token.label": "GoNavi MCP HTTP Token", + "ai_settings.mcp_server.env_hints.known.gonavi_mcp_http_token.detail": "Used when a remote MCP HTTP service enables Bearer Token authentication.", + "ai_settings.mcp_server.env_hints.known.gonavi_mcp_http_token.value_hint": "Enter a high-entropy random token; do not reuse database passwords or model API Keys.", + "ai_settings.mcp_server.env_hints.known.node_env.label": "Node-Laufzeitumgebung", + "ai_settings.mcp_server.env_hints.known.node_env.detail": "Affects logging, debugging, or production mode for some Node MCP services.", + "ai_settings.mcp_server.env_hints.known.node_env.value_hint": "Usually production, development, or a value specified by the README.", + "ai_settings.mcp_server.env_hints.known.log_level.label": "Log-Level", + "ai_settings.mcp_server.env_hints.known.log_level.detail": "Controls how much log output the MCP service emits.", + "ai_settings.mcp_server.env_hints.known.log_level.value_hint": "Common values are debug, info, warn, and error; temporarily raise it for troubleshooting.", + "ai_settings.mcp_server.env_hints.inferred.secret.label": "Geheimnis / Token", + "ai_settings.mcp_server.env_hints.inferred.secret.detail": "The variable name looks like a secret, token, password, or connection string.", + "ai_settings.mcp_server.env_hints.inferred.secret.value_hint": "Enter the real value, but keep it only in local MCP configuration; do not put it in command, args, or chat content.", + "ai_settings.mcp_server.env_hints.inferred.proxy.label": "Proxy-Konfiguration", + "ai_settings.mcp_server.env_hints.inferred.proxy.detail": "The variable name looks like a network proxy setting.", + "ai_settings.mcp_server.env_hints.inferred.proxy.value_hint": "Follow the README or enterprise proxy format, for example http://127.0.0.1:7890.", + "ai_settings.mcp_server.env_hints.inferred.endpoint.label": "Dienst-Endpunkt", + "ai_settings.mcp_server.env_hints.inferred.endpoint.detail": "The variable name looks like a service URL, API endpoint, or host configuration.", + "ai_settings.mcp_server.env_hints.inferred.endpoint.value_hint": "Enter the URL, host, or endpoint the MCP Server needs to access.", + "ai_settings.mcp_server.env_hints.inferred.path.label": "Pfad / Konfigurationsdatei", + "ai_settings.mcp_server.env_hints.inferred.path.detail": "The variable name looks like a local path, directory, or config file location.", + "ai_settings.mcp_server.env_hints.inferred.path.value_hint": "Enter an absolute path accessible to the local MCP process; keep the drive letter for Windows paths.", + "ai_settings.mcp_server.env_hints.inferred.runtime.label": "Laufzeitschalter", + "ai_settings.mcp_server.env_hints.inferred.runtime.detail": "The variable name looks like a runtime environment, logging, or debug switch.", + "ai_settings.mcp_server.env_hints.inferred.runtime.value_hint": "Use the enum value specified by the README.", + "ai_settings.mcp_server.env_hints.inferred.generic.label": "Benutzerdefinierte Konfiguration", + "ai_settings.mcp_server.env_hints.inferred.generic.detail": "No built-in variable hint matched; follow the matching field description in the MCP README.", + "ai_settings.mcp_server.env_hints.inferred.generic.value_hint": "Confirm the variable name casing exactly matches the README.", + "ai_settings.mcp_server.env_hints.warning.empty": "{{count}} Umgebungsvariablenwerte sind leer und müssen vor dem Testen ausgefüllt oder entfernt werden.", + "ai_settings.mcp_server.env_hints.warning.placeholder": "{{count}} Umgebungsvariablen wirken noch wie Beispiel-Platzhalter.", + "ai_settings.mcp_server.env_hints.warning.docker_env_not_forwarded": "Bei command=docker werden diese Umgebungsvariablen nur an die docker CLI übergeben und gelangen nicht automatisch in den Container.", + "ai_settings.mcp_server.env_hints.next_action.empty": "Werte für {{keys}} eintragen oder nicht benötigte Variablen entfernen.", + "ai_settings.mcp_server.env_hints.next_action.placeholder": "{{keys}} vor dem Testen der Tool-Erkennung durch echte Werte ersetzen.", + "ai_settings.mcp_server.env_hints.next_action.docker_env": "Wenn MCP im Container diese Variablen benötigt, füge gemäß README -e KEY=VALUE oder --env KEY=VALUE zu args hinzu.", + "ai_settings.mcp_server.env_hints.next_action.secrets_local": "Geheimnisähnliche Variablen werden nur lokal gespeichert; sende echte Werte nicht an Chat, Issues oder Screenshots.", + "ai_settings.mcp_server.env_hints.next_action.keys_recognized": "Die Umgebungsvariablen-keys sind erkennbar; prüfe bei Testfehlern zuerst die in der README geforderte Schreibweise.", + "ai_settings.mcp_server.guide.field.avoid_label": "Nicht eintragen: ", + "ai_settings.mcp_server.guide.field.example_label": "Beispielwert:", + "ai_settings.mcp_server.guide.field.name.title": "Servicename", + "ai_settings.mcp_server.guide.field.name.summary": "Der Name, der nach dem Speichern dir und der AI angezeigt wird.", + "ai_settings.mcp_server.guide.field.name.detail": "Benenne ihn nach dem Zweck. Empfehlenswert sind gut erkennbare Namen wie Browser, GitHub oder Filesystem.", + "ai_settings.mcp_server.guide.field.name.fill": "Der Zweckname dieses MCP, zum Beispiel GitHub oder Filesystem.", + "ai_settings.mcp_server.guide.field.name.avoid": "Vermeide vage Namen wie server, test oder mcp1.", + "ai_settings.mcp_server.guide.field.enabled.title": "Aktiviert", + "ai_settings.mcp_server.guide.field.enabled.summary": "Steuert, ob diese Konfiguration aktuell an Tool-Erkennung und Aufrufen teilnimmt.", + "ai_settings.mcp_server.guide.field.enabled.detail": "Deaktivieren stoppt nur die Nutzung. Die unten eingetragene Konfiguration wird nicht gelöscht.", + "ai_settings.mcp_server.guide.field.enabled.fill": "Wähle deaktiviert, wenn der Service vorübergehend nicht genutzt wird; wähle aktiviert, wenn die AI ihn verwenden soll.", + "ai_settings.mcp_server.guide.field.enabled.avoid": "Lösche ihn nicht nur zum Pausieren, sonst müssen command, args und env erneut konfiguriert werden.", + "ai_settings.mcp_server.guide.field.enabled.example": "Aktiviert / Deaktiviert", + "ai_settings.mcp_server.guide.field.transport.title": "Transport", + "ai_settings.mcp_server.guide.field.transport.summary": "Wie GoNavi mit diesem MCP Server kommuniziert.", + "ai_settings.mcp_server.guide.field.transport.detail": "Derzeit fest auf stdio gesetzt: GoNavi startet einen lokalen Prozess und kommuniziert über Standard-Ein- und -Ausgabe.", + "ai_settings.mcp_server.guide.field.transport.fill": "stdio beibehalten.", + "ai_settings.mcp_server.guide.field.transport.avoid": "Trage kein HTTP, SSE, keine URL und keinen Port ein. Dieser Ablauf ist keine Remote-MCP-URL-Konfiguration.", + "ai_settings.mcp_server.guide.field.command.title": "Startbefehl", + "ai_settings.mcp_server.guide.field.command.summary": "Nur den Programmnamen oder Launcher selbst eintragen.", + "ai_settings.mcp_server.guide.field.command.detail": "Häufige Werte sind npx, node, uvx, python und docker. Paketnamen, Skriptnamen, run, -i und --stdio gehören in die Argumente.", + "ai_settings.mcp_server.guide.field.command.fill": "Trage npx, node, uvx, python, docker oder einen absoluten Pfad zu einer exe ein.", + "ai_settings.mcp_server.guide.field.command.avoid": "Trage nicht die komplette Befehlszeile ein, zum Beispiel nicht npx -y pkg --stdio.", + "ai_settings.mcp_server.guide.field.args.title": "Befehlsargumente", + "ai_settings.mcp_server.guide.field.args.summary": "Skriptnamen, Modulnamen und Optionen als einzelne Einträge aufteilen.", + "ai_settings.mcp_server.guide.field.args.detail": "npx -y pkg --stdio wird in -y, pkg und --stdio aufgeteilt. docker run --rm -i image wird in run, --rm, -i und image aufgeteilt.", + "ai_settings.mcp_server.guide.field.args.fill": "Trage -y, Paketname, Skriptname, -m, --stdio, run, --rm, -i, Image-Name und ähnliche Argumente einzeln ein.", + "ai_settings.mcp_server.guide.field.args.avoid": "Trage npx/node/uvx/python/docker hier nicht erneut ein und füge nicht mehrere Argumente als eine lange Zeichenkette ein.", + "ai_settings.mcp_server.guide.field.env.title": "Umgebungsvariablen", + "ai_settings.mcp_server.guide.field.env.summary": "Übergibt KEY=VALUE-Konfiguration an den MCP Server.", + "ai_settings.mcp_server.guide.field.env.detail": "Meist für API Key, Service-URL, Arbeitsverzeichnis und ähnliche Werte. Eine Variable pro Zeile; kein export schreiben.", + "ai_settings.mcp_server.guide.field.env.fill": "Eine KEY=VALUE-Zeile pro Variable, zum Beispiel GITHUB_TOKEN=....", + "ai_settings.mcp_server.guide.field.env.avoid": "Kein export, set oder $env:-Präfix schreiben und Umgebungsvariablen nicht in command oder args mischen.", + "ai_settings.mcp_server.guide.field.timeout.title": "Timeout (Sekunden)", + "ai_settings.mcp_server.guide.field.timeout.summary": "Maximale Wartezeit für eine Tool-Erkennung oder einen Tool-Aufruf.", + "ai_settings.mcp_server.guide.field.timeout.detail": "Normale lokale Tools funktionieren meist mit 20 Sekunden. Erhöhe den Wert bei langsamem Start oder Remote-Verbindungen.", + "ai_settings.mcp_server.guide.field.timeout.fill": "Normalerweise 20; bei langsamem Start 45 oder 60.", + "ai_settings.mcp_server.guide.field.timeout.avoid": "Wähle keinen willkürlich zu kleinen Wert. Unter 3 Sekunden kann die Tool-Erkennung leicht fälschlich fehlschlagen.", + "ai_settings.mcp_server.guide.troubleshooting.title": "Häufige Einrichtungsfehler", + "ai_settings.mcp_server.guide.troubleshooting.description": "Wenn ein Test fehlschlägt, nutze diesen Abschnitt, um das zu korrigierende Feld zu finden. Meist ist nicht MCP defekt, sondern Befehl, Argumente oder Umgebungsvariablen wurden falsch getrennt.", + "ai_settings.mcp_server.guide.troubleshooting.cause_label": "Häufige Ursache: ", + "ai_settings.mcp_server.guide.troubleshooting.fix_label": "Lösung: ", + "ai_settings.mcp_server.guide.troubleshooting.example_label": "Beispiel:", + "ai_settings.mcp_server.guide.troubleshooting.command_not_found.symptom": "Der Test meldet, dass der Befehl nicht gefunden wurde", + "ai_settings.mcp_server.guide.troubleshooting.command_not_found.cause": "Der Startbefehl enthält die ganze Befehlszeile, der Befehl liegt nicht auf PATH, oder ein Windows-Pfad enthält Leerzeichen, ist aber kein echter exe-Pfad.", + "ai_settings.mcp_server.guide.troubleshooting.command_not_found.fix": "Trage beim Startbefehl nur das ausführbare Programm selbst ein. Skriptnamen und --stdio gehören in die Befehlsargumente. Wenn der Befehl nicht auf PATH liegt, nutze den absoluten Pfad.", + "ai_settings.mcp_server.guide.troubleshooting.timeout_or_no_tools.symptom": "Der Test läuft in den Timeout oder findet 0 Tools", + "ai_settings.mcp_server.guide.troubleshooting.timeout_or_no_tools.cause": "Der Service startet langsam, ein stdio-Argument fehlt, dem Docker-Container fehlt -i, oder der Service unterstützt nur HTTP/SSE.", + "ai_settings.mcp_server.guide.troubleshooting.timeout_or_no_tools.fix": "Prüfe zuerst, ob der Service stdio unterstützt, und ergänze dann --stdio oder Docker -i. Erhöhe bei langsamem Start den Timeout auf 45 oder 60 Sekunden.", + "ai_settings.mcp_server.guide.troubleshooting.auth_failed.symptom": "Authentifizierung fehlgeschlagen, 401 oder 403", + "ai_settings.mcp_server.guide.troubleshooting.auth_failed.cause": "API Key, Token, Service-URL oder andere Umgebungsvariablen fehlen, oder KEY=VALUE ist ungültig.", + "ai_settings.mcp_server.guide.troubleshooting.auth_failed.fix": "Schreibe pro Umgebungsvariable eine KEY=VALUE-Zeile. Kein export schreiben und Umgebungsvariablen nicht mit dem Startbefehl vermischen.", + "ai_settings.mcp_server.guide.troubleshooting.stdio_only.symptom": "README enthält nur URL- oder SSE-Konfiguration", + "ai_settings.mcp_server.guide.troubleshooting.stdio_only.cause": "Das ist meist kein lokaler stdio-Prozess, und der aktuelle GoNavi-Hinzufügeablauf unterstützt ihn nicht direkt.", + "ai_settings.mcp_server.guide.troubleshooting.stdio_only.fix": "Bevorzuge die stdio-Startmethode dieses Services. Wenn nur HTTP/SSE verfügbar ist, nutze zuerst ein offizielles Gateway oder einen lokalen Wrapper zur Umwandlung in stdio.", + "ai_settings.mcp_server.guide.full_command.title": "Nur eine vollständige Befehlszeile?", + "ai_settings.mcp_server.guide.full_command.description": "Füge den vollständigen Befehl direkt ein. GoNavi teilt ihn in Startbefehl / Befehlsargumente / Umgebungsvariablen auf. Unterstützt werden Unix KEY=VALUE, Windows PowerShell $env:KEY=VALUE; und cmd set KEY=VALUE && Präfixe.", + "ai_settings.mcp_server.guide.full_command.placeholder": "Füge den vollständigen Befehl ein, zum Beispiel:\n{{example}}", + "ai_settings.mcp_server.guide.full_command.parsed_summary": "Wird zerlegt als: Befehl {{command}}, {{argsCount}} Argument(e), {{envCount}} Umgebungsvariable(n).", + "ai_settings.mcp_server.guide.full_command.support_hint": "Unterstützt Pfade in Anführungszeichen, Argumente mit Leerzeichen sowie KEY=VALUE / $env:KEY=VALUE; / set KEY=VALUE && Umgebungsvariablen-Präfixe.", + "ai_settings.mcp_server.guide.full_command.apply": "Automatisch in die Felder unten aufteilen", + "ai_settings.mcp_server.quick_add.title": "Schnell aus einem Befehl hinzufügen", + "ai_settings.mcp_server.quick_add.description": "Wählen Sie die passendste Vorlage oder fügen Sie den vollständigen Startbefehl aus dem README ein. GoNavi teilt ihn in command, args und env auf und erstellt einen bearbeitbaren MCP-Entwurf.", + "ai_settings.mcp_server.quick_add.templates_title": "Häufige Startvorlagen", + "ai_settings.mcp_server.quick_add.templates_description": "Wenn Sie nicht sicher sind, wie command und args aufzuteilen sind, klicken Sie auf eine Vorlage. Jede Karte zeigt den Befehl, den GoNavi tatsächlich startet.", + "ai_settings.mcp_server.quick_add.action.parse_and_add": "Analysieren und Entwurf hinzufügen", + "ai_settings.mcp_server.command_parse.error.unclosed_quote": "Der Befehl enthält ein nicht geschlossenes Anführungszeichen. Prüfen Sie ihn und versuchen Sie es erneut.", + "ai_settings.mcp_server.command_parse.error.empty": "Fügen Sie zuerst den vollständigen Befehl ein.", + "ai_settings.mcp_server.command_parse.error.missing_command": "Es wurde kein Startbefehl erkannt. Geben Sie mindestens einen ausführbaren Programmnamen an.", + "ai_settings.mcp_server.command_parse.error.failed": "Der vollständige Befehl konnte nicht analysiert werden. Prüfen Sie das Befehlsformat.", + "ai_settings.mcp_server.template.npx.title": "npx-Paket", + "ai_settings.mcp_server.template.npx.description": "Für npm-MCP-Pakete, deren README `npx -y xxx --stdio` verwendet.", + "ai_settings.mcp_server.template.npx.detail": "Das Beispiel nutzt `npx -y @modelcontextprotocol/server-filesystem --stdio`; ersetzen Sie Paketname und Pfadargumente durch reale Werte.", + "ai_settings.mcp_server.template.npx.seed_name": "npx-Paket", + "ai_settings.mcp_server.template.uvx.title": "uvx-Tool", + "ai_settings.mcp_server.template.uvx.description": "Für veröffentlichte MCP-Pakete im Python/uv-Ökosystem.", + "ai_settings.mcp_server.template.uvx.detail": "Das Beispiel nutzt `uvx some-mcp-server`; ersetzen Sie den Paketnamen vor dem Speichern.", + "ai_settings.mcp_server.template.uvx.seed_name": "uvx-Tool", + "ai_settings.mcp_server.template.node.title": "Node-Skript", + "ai_settings.mcp_server.template.node.description": "Für lokale js/ts-Skripte oder über npm installierte node-Starter.", + "ai_settings.mcp_server.template.node.detail": "Das Beispiel nutzt `node server.js --stdio`; Skriptname und Argumente können angepasst werden.", + "ai_settings.mcp_server.template.node.seed_name": "Node-Skript", + "ai_settings.mcp_server.template.python.title": "Python-Modul", + "ai_settings.mcp_server.template.python.description": "Für Dienste, die als Modul gestartet werden, etwa `python -m xxx`.", + "ai_settings.mcp_server.template.python.detail": "Das Beispiel nutzt `python -m your_mcp_server`; ersetzen Sie den Modulnamen durch den realen Wert.", + "ai_settings.mcp_server.template.python.seed_name": "Python-Modul", + "ai_settings.mcp_server.template.docker.title": "Docker-Image", + "ai_settings.mcp_server.template.docker.description": "Für containerisierte MCP-Dienste, deren README `docker run -i --rm image` verwendet. Docker muss lokal installiert sein.", + "ai_settings.mcp_server.template.docker.detail": "Das Beispiel nutzt `docker run --rm -i mcp/server-fetch:latest`; Container-Token werden meist mit -e KEY=VALUE in den Argumenten übergeben.", + "ai_settings.mcp_server.template.docker.seed_name": "Docker MCP", + "ai_settings.mcp_server.template.exe.title": "Lokale EXE", + "ai_settings.mcp_server.template.exe.description": "Für kompilierte lokale Binärdateien oder interne Unternehmenstools.", + "ai_settings.mcp_server.template.exe.detail": "Das Beispiel nutzt `your-mcp-server.exe stdio`; ersetzen Sie den exe-Pfad durch den realen Wert.", + "ai_settings.mcp_server.template.exe.seed_name": "Lokale EXE", + "ai_settings.mcp_server.guide.note.command_only": "Der Startbefehl darf nur das Programm selbst enthalten. Skriptnamen, Modulnamen oder --stdio nicht hineinmischen.", + "ai_settings.mcp_server.guide.note.npx": "Wenn README ein npx-Beispiel zeigt, setze command auf npx und trage -y, Paketname und --stdio einzeln in args ein. Nicht den ganzen npx-Befehl in command eintragen.", + "ai_settings.mcp_server.guide.note.docker": "Wenn README ein Docker-Beispiel zeigt, setze command auf docker und trage run, --rm, -i, Image-Name und Container-Argumente einzeln in args ein. Container-Token werden meist mit -e KEY=VALUE übergeben.", + "ai_settings.mcp_server.guide.note.full_command": "Wenn README nur eine vollständige Befehlszeile liefert, füge sie zuerst in das Feld für vollständige Befehle ein. Unterstützt werden KEY=VALUE, env KEY=VALUE, PowerShell $env:KEY=VALUE; und Windows set KEY=VALUE && Präfixe.", + "ai_settings.mcp_server.guide.note.env_lines": "Eine KEY=VALUE-Zeile pro Umgebungsvariable verwenden. Kein export schreiben und nicht mit dem Startbefehl vermischen.", + "ai_settings.mcp_server.guide.note.secrets": "Geheime Umgebungsvariablen werden in der lokalen Konfiguration gespeichert und nur beim Start des MCP-Prozesses als Prozessumgebung übergeben. Keine Secrets in Chat-Inhalte schreiben.", + "ai_settings.mcp_server.guide.note.test_discovery": "Tool-Erkennungstest startet den Service nur temporär zur Prüfung und speichert die Konfiguration nicht automatisch.", + "ai_settings.mcp_server.section.quick_reference.title": "Schnellreferenz für neue MCP-Parameter", + "ai_settings.mcp_server.section.quick_reference.description": "Prüfe diese Felder vor dem Hinzufügen eines Services: `command` enthält nur das ausführbare Programm, `args` enthält Skriptnamen und --stdio, `env` nutzt eine KEY=VALUE-Zeile pro Eintrag, und `timeout` steuert die Wartezeit für eine Tool-Erkennung oder einen Aufruf.", + "ai_settings.mcp_server.section.quick_reference.footer": "Unterstützt Befehl, Argumente, Umgebungsvariablen und Timeout. Wenn du unsicher bist, lies zuerst die Karten der Feld-Schnellreferenz. Nach dem Speichern erscheint der Service automatisch in der AI-Toolliste.", + "ai_settings.mcp_server.section.action.add_server": "MCP-Service hinzufügen", + "ai_settings.mcp_server.section.empty": "Noch kein MCP-Service vorhanden. Häufige Formen sind `npx -y package --stdio`, `node server.js`, `uvx some-mcp-server`, `python -m server` und `docker run --rm -i image`.", "ai_settings.clipboard.error.unsupported": "Kopieren in die Zwischenablage wird in dieser Umgebung nicht unterstützt", "ai_settings.mcp_http.error.control_unsupported_runtime": "Die aktuelle Laufzeit unterstützt keine Steuerung des MCP HTTP-Dienstes", "ai_settings.mcp_http.error.start_unsupported_version": "Diese Version unterstützt das Starten des MCP HTTP-Dienstes nicht", @@ -3601,6 +5163,38 @@ "ai_settings.mcp_http.message.authorization_header_required": "Starten Sie zuerst den MCP HTTP-Dienst, um den Authorization Header zu erzeugen", "ai_settings.mcp_http.message.authorization_header_copied": "Authorization Header kopiert", "ai_settings.mcp_http.status.not_running": "GoNavi MCP HTTP-Dienst läuft nicht", + "ai_settings.mcp_http.panel.title": "GoNavi MCP HTTP-Dienst", + "ai_settings.mcp_http.panel.status.running": "Gestartet", + "ai_settings.mcp_http.panel.status.stopped": "Gestoppt", + "ai_settings.mcp_http.panel.description": "Für Remote-Agents wie OpenClaw und Hermans. Nach dem Aktivieren lauscht der Dienst auf einer lokalen Adresse und stellt nur Struktur-Lesewerkzeuge für Verbindungen, Datenbanken, Tabellen, Spalten und DDL bereit.", + "ai_settings.mcp_http.panel.switch.on": "Ein", + "ai_settings.mcp_http.panel.switch.off": "Aus", + "ai_settings.mcp_http.panel.addr_label": "Lauschadresse / Port", + "ai_settings.mcp_http.panel.authorization_placeholder": "Bearer gnv_xxx (leer lassen für automatische Generierung)", + "ai_settings.mcp_http.panel.running_hint": "Der Dienst läuft. Konfigurieren Sie URL und Authorization Header im Remote-MCP-Client.", + "ai_settings.mcp_http.panel.stopped_hint": "Sie können den lokalen Lauschport und Bearer Token anpassen. Wenn Authorization leer ist, wird beim Start automatisch ein zufälliger Token erzeugt.", + "ai_settings.mcp_http.panel.copy_url": "URL kopieren", + "ai_settings.mcp_http.panel.copy_authorization": "Authorization kopieren", + "ai_settings.skill.description": "Ein Skill ist kein weiterer großer Prompt. Er besteht aus einem benannten Prompt-Modul + scope + Tool-Abhängigkeiten. Behalte ihn in dieser Phase im Hauptrepository. Ein separates GitHub-Repository ist erst nötig, wenn später ein gemeinsames skill pack verteilt werden soll.", + "ai_settings.skill.hint": "Wenn aktiviert, wird er nach scope in die passende Sitzung injiziert. Fehlt ein erforderliches Tool, wird dieser Skill automatisch übersprungen.", + "ai_settings.skill.action.add": "Skill hinzufügen", + "ai_settings.skill.empty": "Noch keine Skills. Du kannst dedizierte system prompts für Datenbank-, JVM- und Diagnoseszenarien definieren.", + "ai_settings.skill.name_placeholder": "Skill-Name, zum Beispiel: SQL-Review / JVM-Diagnoseplan", + "ai_settings.skill.status.enabled": "Aktiviert", + "ai_settings.skill.status.disabled": "Deaktiviert", + "ai_settings.skill.description_placeholder": "Private Notiz, zum Beispiel: Feldnamen und Risiken vor der SQL-Ausgabe bestätigen", + "ai_settings.skill.scope.global.label": "Global", + "ai_settings.skill.scope.global.desc": "Für alle AI-Sitzungen aktiviert", + "ai_settings.skill.scope.database.label": "Datenbank", + "ai_settings.skill.scope.database.desc": "Nur für SQL- / Datenbankszenarien aktiviert", + "ai_settings.skill.scope.jvm.label": "JVM-Ressourcen", + "ai_settings.skill.scope.jvm.desc": "Nur für JVM-Ressourcenanalysen aktiviert", + "ai_settings.skill.scope.jvm_diagnostic.label": "JVM-Diagnose", + "ai_settings.skill.scope.jvm_diagnostic.desc": "Nur für die JVM-Diagnose-Workbench aktiviert", + "ai_settings.skill.scopes_placeholder": "Auswählen, wo dieser Skill gelten soll", + "ai_settings.skill.required_tools_placeholder": "Optional: Tools deklarieren, von denen dieser Skill abhängt", + "ai_settings.skill.system_prompt_placeholder": "Gib den system prompt ein, den dieser Skill anhängen soll. Fokussiere eine klare Fähigkeit und vermeide Duplikate globaler Prompts.", + "ai_settings.skill.confirm_delete": "Diesen Skill löschen?", "ai_settings.skill.message.saved": "Skill gespeichert", "ai_settings.skill.message.save_failed": "Skill konnte nicht gespeichert werden", "ai_settings.skill.message.deleted": "Skill gelöscht", @@ -3645,6 +5239,7 @@ "driver_manager.action.install_enable": "Installieren und aktivieren", "driver_manager.action.remove": "Entfernen", "driver_manager.action.logs": "Protokoll", + "driver_manager.action.switch_version": "Version wechseln", "driver_manager.column.data_source": "Datenquelle", "driver_manager.column.package_size": "Paketgröße", "driver_manager.column.status": "Zustand", @@ -3688,6 +5283,8 @@ "driver_manager.message.load_version_failed": "Versionsliste für {{name}} konnte nicht geladen werden", "driver_manager.message.load_version_failed_detail": "Versionsliste für {{name}} konnte nicht geladen werden: {{detail}}", "driver_manager.message.install_start": "Installation gestartet", + "driver_manager.message.install_watchdog_timeout": "Die Installation von {{name}} ist nach {{minutes}} Minuten noch nicht abgeschlossen. Der Hintergrundprozess lädt oder erstellt möglicherweise noch Dateien. Aktualisiere den Status später; falls dies wiederholt auftritt, prüfe den Proxy oder importiere ein lokales Treiberpaket.", + "driver_manager.message.install_failed_fallback": "Installation von {{name}} fehlgeschlagen", "driver_manager.message.install_failed": "{{name}} konnte nicht installiert werden", "driver_manager.message.install_failed_detail": "{{name}} konnte nicht installiert werden: {{detail}}", "driver_manager.message.install_success": "{{name}}{{version}} installiert und aktiviert", @@ -3744,12 +5341,15 @@ "driver_manager.progress.agent_install_start": "Installation des {{name}}-Treiberagenten gestartet", "driver_manager.progress.agent_install_done": "Installation des {{name}}-Treiberagenten abgeschlossen", "driver_manager.progress.download_prebuilt_agent": "Vorkompilierten {{name}}-Treiberagenten herunterladen", + "driver_manager.progress.download_prebuilt_package": "Vorkompiliertes {{name}}-Treiberpaket herunterladen", "driver_manager.progress.download_bundle": "{{name}}-Treiberbundle herunterladen", + "driver_manager.progress.wait_bundle": "Auf Abschluss des {{name}}-Treiberbundle-Downloads warten", "driver_manager.progress.extract_agent_from_bundle": "{{name}}-Agent aus Treiberbundle extrahieren", "driver_manager.progress.unzip_agent": "{{name}}-Treiberagent extrahieren", "driver_manager.progress.source_build_preferred": "Lokalen Quellbuild zuerst für {{name}}-Treiberagent verwenden", "driver_manager.progress.dev_build_fallback": "Kein vorkompiliertes Paket gefunden; lokaler Entwicklungsbuild wird versucht", "driver_manager.progress.plan.source_only": "Installation des {{name}}-Treiberagenten wird vorbereitet (Version {{version}}); diese Version erlaubt nur lokale Quellbuilds", + "driver_manager.progress.plan.require_source_first": "Installation des {{name}}-Treiberagenten wird vorbereitet (Version {{version}}); der Entwicklungsbuild verwendet nur lokalen Quellcode und nutzt keine Release-Pakete als Fallback", "driver_manager.progress.plan.source_first": "Installation des {{name}}-Treiberagenten wird vorbereitet (Version {{version}}); zuerst lokaler Quellbuild, danach Download-Fallback", "driver_manager.progress.plan.direct_then_bundle": "Installation des {{name}}-Treiberagenten wird vorbereitet (Version {{version}}); {{direct}} vorkompilierte Direktlinks, danach {{bundle}} Treiberbundle-Quellen", "driver_manager.progress.plan.explicit_direct": "Installation des {{name}}-Treiberagenten wird vorbereitet (Version {{version}}); nur explizite Versionsassets, {{direct}} vorkompilierte Direktlinks", @@ -3787,7 +5387,18 @@ "driver_manager.version.placeholder.load_on_expand": "Aufklappen, um Versionen zu laden", "driver_manager.version.installed_locked_with_version": "{{version}} (installiert; zum Ändern entfernen)", "driver_manager.version.installed_locked": "Installiert (zum Ändern entfernen)", + "driver_manager.version.switch_pending": "Aktuell installiert: {{installedVersion}}; ausgewählt: {{targetVersion}}. Klicke auf „Version wechseln“, um die Änderung anzuwenden.", + "driver_manager.version.current_fallback": "aktuelle Version", + "driver_manager.version.target_fallback": "Zielversion", + "driver_manager.version.installed_with_version": "{{version}} (installiert{{suffix}})", + "driver_manager.version.installed": "Installiert{{suffix}}", + "driver_manager.version.needs_reinstall_suffix": ", Neuinstallation erforderlich", "driver_manager.version.mongodb_hint": "Derzeit werden nur MongoDB 1.17.x und 2.x unterstützt. Ältere 1.x-Versionen stehen nicht zur Installation bereit.", + "driver_manager.version.unlabeled": "Version ohne Kennzeichnung", + "driver_manager.version.latest_suffix": " (neueste)", + "driver_manager.version.recommended_suffix": " (empfohlen)", + "driver_manager.package_size.built_in": "Integriert", + "driver_manager.package_size.pending_release": "Noch nicht veröffentlicht", "driver_manager.backend.dialog.select_download_directory": "Treiber-Downloadordner auswählen", "driver_manager.backend.dialog.select_package_file": "Treiberpaketdatei auswählen (kein JDBC Jar)", "driver_manager.backend.dialog.select_package_directory": "Treiberpaketordner auswählen", @@ -3819,9 +5430,15 @@ "driver_manager.backend.error.version_empty": "Version ist leer", "driver_manager.backend.error.asset_name_empty": "Treiber-Assetname ist leer", "driver_manager.backend.error.mongo_version_unsupported": "MongoDB-Version {{version}} wird nicht unterstützt; unterstützt werden nur 1.17.x und 2.x", + "driver_manager.backend.error.driver_version_unsupported": "{{name}}-Version {{version}} wird nicht unterstützt", "driver_manager.backend.error.open_directory_unsupported": "Ordner können auf dieser Plattform nicht geöffnet werden: {{platform}}", "driver_manager.backend.error.open_directory_failed": "Treiberordner konnte nicht geöffnet werden: {{detail}}", "driver_manager.backend.error.create_directory_failed": "Treiberordner konnte nicht erstellt werden: {{detail}}", + "driver_manager.backend.error.create_named_directory_failed": "{{name}}-Treiberordner konnte nicht erstellt werden: {{detail}}", + "driver_manager.backend.error.remove_installed_agent_failed": "Installierter {{name}}-Treiber-Agent konnte nicht entfernt werden: {{detail}}", + "driver_manager.backend.error.agent_path_occupied_by_directory": "Pfad des {{name}}-Treiber-Agents ist durch einen Ordner belegt: {{path}}", + "driver_manager.backend.error.copy_bundled_agent_failed": "Gebündelter {{name}}-Treiber-Agent konnte nicht kopiert werden: {{detail}}", + "driver_manager.backend.error.bundled_agent_hash_failed": "Pruefsumme des gebündelten {{name}}-Treiber-Agents konnte nicht berechnet werden: {{detail}}", "driver_manager.backend.error.remove_package_failed": "Treiberpaket konnte nicht entfernt werden: {{detail}}", "driver_manager.backend.error.manifest_scheme_unsupported": "Nicht unterstütztes Treiber-Manifest-URL-Schema: {{scheme}}", "driver_manager.backend.error.manifest_fetch_failed": "Treiber-Manifest konnte nicht abgerufen werden: {{detail}}", @@ -3850,19 +5467,68 @@ "driver_manager.backend.error.local_package_path_empty": "Lokaler Treiberpaketpfad ist leer", "driver_manager.backend.error.local_directory_path_empty": "Lokaler Treiberordnerpfad ist leer", "driver_manager.backend.error.file_path_empty": "Dateipfad ist leer", + "driver_manager.backend.error.metadata_payload_encode_failed": "Driver-Metadaten konnten nicht serialisiert werden: {{detail}}", + "driver_manager.backend.error.metadata_file_write_failed": "Driver-Metadatendatei konnte nicht geschrieben werden: {{detail}}", + "driver_manager.backend.error.zip_entry_empty": "Zip-Eintrag ist leer", "driver_manager.backend.error.download_url_empty": "Download-URL ist leer", "driver_manager.backend.error.bundle_url_empty": "Download-URL des Treiberbundles ist leer", "driver_manager.backend.error.read_local_package_failed": "Lokales Treiberpaket konnte nicht gelesen werden: {{detail}}", "driver_manager.backend.error.read_local_directory_failed": "Lokaler Treiberordner konnte nicht gelesen werden: {{detail}}", + "driver_manager.backend.error.local_directory_not_directory": "Pfad des lokalen Treiberordners ist kein Ordner: {{path}}", + "driver_manager.backend.error.local_directory_scan_limit": "Lokaler Treiberordner enthält zu viele Einträge (über {{max}}). Grenzen Sie den Ordner ein oder wählen Sie direkt eine zip-/Einzeldatei.", "driver_manager.backend.error.scan_local_directory_failed": "Lokaler Treiberordner konnte nicht gescannt werden: {{detail}}", + "driver_manager.backend.error.local_directory_entry_missing": "{{name}}-Agent-Datei wurde im Ordner nicht gefunden (bevorzugter Pfad: {{path}}, Kandidatendateinamen: {{assetCandidates}} / {{baseCandidates}})", "driver_manager.backend.error.open_local_package_failed": "Lokales Treiberpaket konnte nicht geöffnet werden: {{detail}}", + "driver_manager.backend.error.local_package_entry_missing": "{{name}}-Agent-Datei wurde im lokalen Treiberpaket nicht gefunden (erwarteter Pfad: {{path}})", "driver_manager.backend.error.read_local_package_entry_failed": "Eintrag im lokalen Treiberpaket konnte nicht gelesen werden: {{detail}}", + "driver_manager.backend.error.import_local_agent_failed": "Lokaler Treiber-Agent konnte nicht importiert werden: {{detail}}", + "driver_manager.backend.error.import_local_agent_runtime_failed": "Laufzeitabhaengigkeiten des lokalen Treiber-Agents konnten nicht importiert werden: {{detail}}", + "driver_manager.backend.error.runtime_dependency_directory_empty": "Verzeichnis fuer Laufzeitabhaengigkeiten ist leer", + "driver_manager.backend.error.copy_runtime_dependency_entry_failed": "Laufzeitabhaengigkeit {{name}} konnte nicht kopiert werden: {{detail}}", + "driver_manager.backend.error.runtime_dependency_target_directory_empty": "Zielverzeichnis fuer Laufzeitabhaengigkeiten ist leer", + "driver_manager.backend.error.runtime_dependency_entry_missing": "Im Treiberpaket fehlt die Laufzeitabhaengigkeit: {{name}}", + "driver_manager.backend.error.extract_runtime_dependency_failed": "Laufzeitabhaengigkeit {{name}} konnte nicht entpackt werden: {{detail}}", "driver_manager.backend.error.download_failed": "Download fehlgeschlagen: {{detail}}", "driver_manager.backend.error.bundle_download_failed": "Treiberbundle konnte nicht heruntergeladen werden: {{detail}}", "driver_manager.backend.error.open_bundle_failed": "Treiberbundle konnte nicht geöffnet werden: {{detail}}", "driver_manager.backend.error.read_bundle_entry_failed": "Eintrag im Treiberbundle konnte nicht gelesen werden: {{detail}}", "driver_manager.backend.error.source_build_failed": "Lokaler Quellbuild fehlgeschlagen: {{detail}}", + "driver_manager.backend.error.source_build_tag_unconfigured": "Für den Treibertyp sind keine Build-Tags konfiguriert: {{driverType}}", + "driver_manager.backend.error.source_build_workdir_unavailable": "Aktuelles Verzeichnis konnte nicht ermittelt werden: {{detail}}", + "driver_manager.backend.error.source_build_project_root_missing": "Der Quellcode des gemeinsamen Treiber-Agenten wurde im Projekt nicht gefunden. Bitte verwenden Sie einen veröffentlichten Build", + "driver_manager.backend.error.source_build_go_mod_read_failed": "go.mod konnte nicht gelesen werden: {{detail}}", + "driver_manager.backend.error.source_build_module_dependency_missing": "Die Treiberabhängigkeit wurde in go.mod nicht gefunden: {{modulePath}}", + "driver_manager.backend.error.source_build_temp_directory_create_failed": "Temporäres Verzeichnis für den Treiber-Build konnte nicht erstellt werden: {{detail}}", + "driver_manager.backend.error.source_build_temp_go_mod_write_failed": "Temporäre go.mod konnte nicht geschrieben werden: {{detail}}", + "driver_manager.backend.error.source_build_temp_go_sum_write_failed": "Temporäre go.sum konnte nicht geschrieben werden: {{detail}}", + "driver_manager.backend.error.source_build_module_or_version_empty": "Treibermodul oder Version ist leer", + "driver_manager.backend.error.source_build_duckdb_windows_cgo_toolchain_prepare_failed": "Vorbereitung der DuckDB Windows CGO-Toolchain fehlgeschlagen: {{detail}}", + "driver_manager.backend.error.source_build_duckdb_windows_dynamic_library_prepare_failed": "Vorbereitung der DuckDB Windows-Dynamikbibliothek fehlgeschlagen: {{detail}}", + "driver_manager.backend.error.source_build_duckdb_windows_toolchain_install_hint": "Bitte installieren Sie zuerst die MSYS2 UCRT64-Toolchain: winget install --id MSYS2.MSYS2 -e; fuehren Sie dann C:\\msys64\\usr\\bin\\bash.exe -lc \"pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-binutils\" aus", + "driver_manager.backend.error.source_build_duckdb_windows_gcc_not_found": "Es wurde kein verwendbares gcc.exe/g++.exe gefunden; {{hint}}", + "driver_manager.backend.error.source_build_duckdb_windows_gcc_not_found_with_checked": "Es wurde kein verwendbares gcc.exe/g++.exe gefunden. Geprueft: {{checked}}; {{hint}}", + "driver_manager.backend.error.source_build_duckdb_windows_dynamic_library_missing_files": "Im offiziellen DuckDB-Dynamikbibliothekspaket fehlen Dateien: {{files}}", + "driver_manager.backend.error.source_build_duckdb_windows_dlltool_resolve_failed": "DuckDB Windows dlltool konnte nicht gefunden werden: {{detail}}", "driver_manager.backend.error.prebuilt_downloads_failed": "Vorkompiliertes Paket konnte nicht heruntergeladen werden: {{detail}}", + "driver_manager.backend.error.install_prebuilt_package_failed": "Vorkompiliertes Treiberpaket konnte nicht installiert werden: {{detail}}", + "driver_manager.backend.error.agent_hash_failed": "Pruefsumme des Treiber-Agents konnte nicht berechnet werden: {{detail}}", + "driver_manager.backend.error.agent_metadata_unavailable": "Versionsmetadaten des {{name}}-driver-agent sind nicht verfuegbar. Installiere den zur aktuellen Version passenden driver-agent: {{detail}}", + "driver_manager.backend.error.agent_revision_mismatch": "Revision des {{name}}-driver-agent stimmt nicht ueberein (installiert: {{actual}}, erforderlich: {{expected}}). Installiere den zur aktuellen Version passenden driver-agent.", + "driver_manager.backend.error.agent_revision_mismatch_empty_actual": "Revision des {{name}}-driver-agent stimmt nicht ueberein (installiert: leer, erforderlich: {{expected}}). Installiere den zur aktuellen Version passenden driver-agent.", + "driver_manager.backend.error.runtime_dependency_required": "{{name}} benoetigt auf der aktuellen Plattform mitgelieferte Laufzeitabhaengigkeiten ({{files}}); eine Einzeldatei-Agent-Installation wird nicht unterstuetzt. Verwenden Sie das Treiberbundle, eine dedizierte Treiber-zip-Datei oder einen lokalen Quellbuild.", + "driver_manager.backend.error.chmod_agent_failed": "Berechtigungen des Treiber-Agents konnten nicht gesetzt werden: {{detail}}", + "driver_manager.backend.error.replace_agent_failed": "Treiber-Agent konnte nicht ersetzt werden: {{detail}}", + "driver_manager.backend.error.bundle_entry_missing": "{{name}} wurde im Treiberbundle nicht gefunden (erwarteter Pfad: {{path}})", + "driver_manager.backend.error.create_agent_temp_file_failed": "Temporaere Datei fuer Treiber-Agent konnte nicht erstellt werden: {{detail}}", + "driver_manager.backend.error.write_agent_failed": "Treiber-Agent konnte nicht geschrieben werden: {{detail}}", + "driver_manager.backend.error.sync_agent_failed": "Treiber-Agent konnte nicht auf den Datentraeger geschrieben werden: {{detail}}", + "driver_manager.backend.error.close_agent_file_failed": "Treiber-Agent-Datei konnte nicht geschlossen werden: {{detail}}", + "driver_manager.backend.error.go_not_found_prebuilt_missing": "Go ist in der aktuellen Umgebung nicht installiert, und es wurde kein verfuegbares vorkompiliertes {{name}}-Agent-Paket gefunden", + "driver_manager.backend.error.source_build_timeout": "Build des {{name}}-Treiber-Agents ist abgelaufen (ueber {{timeout}}). Verwenden Sie bevorzugt ein vorkompiliertes Treiberpaket oder den Import eines lokalen Treiberpakets.", + "driver_manager.backend.error.source_build_command_failed": "{{name}}-Treiber-Agent konnte nicht gebaut werden: {{detail}}; Ausgabe: {{output}}", + "driver_manager.backend.error.copy_runtime_dependency_failed": "{{name}}-Laufzeitabhaengigkeiten konnten nicht kopiert werden: {{detail}}", + "driver_manager.backend.error.named_chmod_agent_failed": "Berechtigungen des {{name}}-Treiber-Agents konnten nicht gesetzt werden: {{detail}}", + "driver_manager.backend.error.named_agent_hash_failed": "Pruefsumme des {{name}}-Treiber-Agents konnte nicht berechnet werden: {{detail}}", "driver_manager.backend.status.built_in_available": "Integrierter Treiber ist verbindungsbereit", "driver_manager.backend.status.optional_enabled": "Go-Treiber ist aktiviert und verbindungsbereit", "driver_manager.backend.status.installed_pending_with_version": "Treiber ist installiert (Version: {{version}}) und wartet auf Aktivierung", @@ -3873,6 +5539,9 @@ "driver_manager.backend.status.installed_revision": "installierte Revision {{revision}}.", "driver_manager.backend.status.expected_revision": "erwartete Revision {{revision}}.", "driver_manager.backend.status.affected_connections": "Betrifft {{count}} gespeicherte Verbindungen", + "driver_manager.backend.status.agent_revision_update_detail": "Grund: Die aktuelle GoNavi-Version erfordert den aktualisierten {{name}} driver-agent (Revision: {{expected}}). Auswirkung: Der driver-agent ist eine separate Binärdatei und wird nicht automatisch mit dem Hauptprogramm aktualisiert. Ohne Neuinstallation läuft weiter die alte Agent-Logik, sodass treiberseitige Fehlerbehebungen oder Optimierungen nicht wirksam werden und alte Probleme bestehen bleiben können. Eine Neuinstallation des passenden driver-agent wird dringend empfohlen.", + "driver_manager.backend.status.agent_revision_update_detail_with_actual": "Grund: Die aktuelle GoNavi-Version erfordert den aktualisierten {{name}} driver-agent (Revision: {{expected}}). Auswirkung: Der driver-agent ist eine separate Binärdatei und wird nicht automatisch mit dem Hauptprogramm aktualisiert. Ohne Neuinstallation läuft weiter die alte Agent-Logik, sodass treiberseitige Fehlerbehebungen oder Optimierungen nicht wirksam werden und alte Probleme bestehen bleiben können. Eine Neuinstallation des passenden driver-agent wird dringend empfohlen (installierter Marker: {{actual}}, erforderlich: {{expected}}).", + "driver_manager.backend.status.mongodb_compatibility_update_detail": "Grund: Die empfohlene MongoDB-kompatible Treiberversion ist {{recommended}}, installiert ist {{installed}}. Auswirkung: Der MongoDB 2.x driver-agent verwendet den offiziellen v2-Treiber und erfordert MongoDB Server 4.2+; bei Verbindungen zu MongoDB 4.0 kann eine Inkompatibilität mit wire version 7 auftreten. Eine Neuinstallation des passenden driver-agent wird dringend empfohlen.", "driver_manager.backend.status.unrecognized_driver_type": "Nicht erkannter Datenquellentyp", "driver_manager.backend.status.slim_build_required": "{{name}} ist im aktuellen slim build nicht enthalten. Installiere die Full-Version, um diesen Treiber zu verwenden.", "driver_manager.backend.status.agent_path_failed": "Pfad des {{name}}-Treiberagenten konnte nicht aufgelöst werden. Installiere und aktiviere ihn im Driver Manager erneut.", @@ -3885,6 +5554,7 @@ "driver_manager.backend.network.probe.go_module_proxy": "Go-Modulproxy", "driver_manager.backend.network.error.probe_url_empty": "Prüf-URL ist leer", "driver_manager.backend.network.error.probe_host_missing": "Prüf-URL enthält keinen Host", + "driver_manager.backend.network.error.timeout": "Zeitüberschreitung bei der Netzwerkverbindung", "driver_manager.backend.network.summary.download_chain_unreachable": "Die GitHub API ist erreichbar, aber die Treiber-Downloadkette ist nicht erreichbar. Aktiviere zuerst den globalen GoNavi-Proxy, erlaube github.com, api.github.com, release-assets.githubusercontent.com, objects.githubusercontent.com und raw.githubusercontent.com in den Proxyregeln, und erwäge danach TUN-Modus, falls es weiterhin fehlschlägt.", "driver_manager.backend.progress.plan.source_only": "Installation des {{name}}-Treiberagenten wird vorbereitet (Version {{version}}); diese Version erlaubt nur lokale Quellbuilds", "driver_manager.backend.progress.plan.source_first": "Installation des {{name}}-Treiberagenten wird vorbereitet (Version {{version}}); zuerst lokaler Quellbuild, danach Download-Fallback", @@ -4446,6 +6116,9 @@ "file.backend.dialog.export_table": "{{table}} exportieren", "file.backend.dialog.export_tables_sql": "Tabellen exportieren (SQL)", "file.backend.dialog.import_data": "In {{table}} importieren", + "file.backend.dialog.select_ca_server_certificate_file": "CA-/Serverzertifikatdatei auswählen", + "file.backend.dialog.select_client_certificate_file": "Clientzertifikatdatei auswählen", + "file.backend.dialog.select_client_private_key_file": "Client-Private-Key-Datei auswählen", "file.backend.dialog.select_config_file": "Konfigurationsdatei auswählen", "file.backend.dialog.select_database_file": "Datenbankdatei auswählen", "file.backend.dialog.select_duckdb_file": "DuckDB-Datendatei auswählen", @@ -4453,6 +6126,7 @@ "file.backend.dialog.select_sql_file": "SQL-Datei auswählen", "file.backend.dialog.select_sqlite_file": "SQLite-Datendatei auswählen", "file.backend.dialog.select_ssh_key_file": "SSH-Private-Key-Datei auswählen", + "file.backend.dialog.select_tls_certificate_file": "TLS-Zertifikatdatei auswählen", "file.backend.html_export.document_title": "GoNavi Datenexport", "file.backend.html_export.empty_rows": "(0 Zeilen)", "file.backend.html_export.heading": "GoNavi Datenexport", @@ -4471,9 +6145,17 @@ "file.backend.error.connection_package_password_required": "Das Kennwort des Wiederherstellungspakets darf nicht leer sein", "file.backend.error.connection_package_payload_too_large": "Der Inhalt des Verbindungspakets ist zu groß", "file.backend.error.connection_package_unsupported": "Nicht unterstütztes Format des Verbindungs-Wiederherstellungspakets", + "file.backend.error.create_directory_failed": "Verzeichnis konnte nicht erstellt werden: {{detail}}", + "file.backend.error.create_sql_file_failed": "SQL-Datei konnte nicht erstellt werden: {{detail}}", "file.backend.error.database_name_required": "Datenbankname darf nicht leer sein", + "file.backend.error.delete_sql_directory_failed": "Verzeichnis konnte nicht gelöscht werden: {{detail}} (nur leere Verzeichnisse können gelöscht werden)", + "file.backend.error.delete_sql_file_failed": "SQL-Datei konnte nicht gelöscht werden: {{detail}}", + "file.backend.error.directory_exists": "Verzeichnis ist bereits vorhanden", + "file.backend.error.directory_name_no_separator": "Verzeichnisname darf keine Pfadtrennzeichen enthalten", + "file.backend.error.directory_name_required": "Verzeichnisname darf nicht leer sein", "file.backend.error.directory_path_required": "Verzeichnispfad darf nicht leer sein", "file.backend.error.export_unsupported_format": "Nicht unterstütztes Exportformat: {{format}}", + "file.backend.error.app_log_file_not_found": "GoNavi-Logdatei wurde nicht gefunden", "file.backend.error.file_path_empty": "Dateipfad ist leer", "file.backend.error.file_path_required": "Dateipfad darf nicht leer sein", "file.backend.error.import_file_empty": "Dateipfad darf nicht leer sein", @@ -4490,13 +6172,33 @@ "file.backend.error.invalid_export_mode": "Ungültiger Exportmodus", "file.backend.error.mysql_workbench_no_connections": "Im XML wurden keine gültigen Verbindungskonfigurationen gefunden", "file.backend.error.mysql_workbench_parse_failed": "MySQL Workbench-XML konnte nicht geparst werden: {{detail}}", + "file.backend.error.navicat_connection_password_parse_failed": "Passwort für Verbindung {{name}} konnte nicht verarbeitet werden", + "file.backend.error.navicat_connection_proxy_password_parse_failed": "Proxy-Passwort für Verbindung {{name}} konnte nicht verarbeitet werden", + "file.backend.error.navicat_connection_ssh_password_parse_failed": "SSH-Passwort für Verbindung {{name}} konnte nicht verarbeitet werden", + "file.backend.error.navicat_ncx_no_connections": "In Navicat NCX wurde keine von GoNavi unterstützte gültige Verbindungskonfiguration gefunden", + "file.backend.error.navicat_ncx_parse_failed": "Navicat NCX konnte nicht geparst werden", + "file.backend.error.navicat_secret_decrypt_failed": "Navicat-Passwort konnte nicht entschlüsselt werden", "file.backend.error.open_file_failed": "Datei konnte nicht geöffnet werden: {{detail}}", "file.backend.error.query_required": "Abfrage darf nicht leer sein", + "file.backend.error.read_directory_info_failed": "Verzeichnisinformationen konnten nicht gelesen werden: {{detail}}", "file.backend.error.read_file_error_summary": "Dateilesefehler: {{detail}}. Ausgeführt: {{count}}.", "file.backend.error.read_file_info_failed": "Dateiinformationen konnten nicht gelesen werden: {{detail}}", + "file.backend.error.read_target_directory_info_failed": "Informationen zum Zielverzeichnis konnten nicht gelesen werden: {{detail}}", + "file.backend.error.read_target_file_info_failed": "Informationen zur Zieldatei konnten nicht gelesen werden: {{detail}}", + "file.backend.error.rename_directory_failed": "Verzeichnis konnte nicht umbenannt werden: {{detail}}", + "file.backend.error.rename_sql_file_failed": "SQL-Datei konnte nicht umbenannt werden: {{detail}}", "file.backend.error.selected_path_not_directory": "Der ausgewählte Pfad ist kein Verzeichnis", "file.backend.error.selected_path_not_sql_file": "Der ausgewählte Pfad ist keine SQL-Datei", "file.backend.error.select_with_query_required": "Nur SELECT/WITH-Abfrageexport wird unterstützt", + "file.backend.error.schema_export_no_objects": "Im Schema {{schema}} wurden keine exportierbaren Tabellen oder Ansichten gefunden", + "file.backend.error.schema_name_required": "Schemaname darf nicht leer sein", + "file.backend.error.sql_file_batch_execution_failed": "Batch-Ausführung ab Anweisung {{index}} fehlgeschlagen: {{detail}}", + "file.backend.error.sql_file_batch_rollback_failed": "Batch-Ausführung fehlgeschlagen: {{detail}}; Rollback fehlgeschlagen: {{rollbackDetail}}", + "file.backend.error.sql_file_exists": "SQL-Datei ist bereits vorhanden", + "file.backend.error.sql_file_extension_required": "Es werden nur SQL-Dateien unterstützt", + "file.backend.error.sql_file_name_no_separator": "SQL-Dateiname darf keine Pfadtrennzeichen enthalten", + "file.backend.error.sql_file_name_required": "SQL-Dateiname darf nicht leer sein", + "file.backend.error.sql_file_statement_execution_failed": "Anweisung {{index}} fehlgeschlagen: {{detail}}", "file.backend.error.task_not_found": "Aufgabe nicht gefunden", "file.backend.error.table_data_batch_limit": "Es können höchstens {{max}} Tabellen gleichzeitig verarbeitet werden; aktuell ausgewählt: {{count}}", "file.backend.error.table_data_clear_failed": "Leeren von {{table}} fehlgeschlagen: {{detail}}", @@ -4506,6 +6208,8 @@ "file.backend.error.table_data_truncate_failed": "TRUNCATE für {{table}} fehlgeschlagen: {{detail}}", "file.backend.error.table_data_truncate_failed_partial": "TRUNCATE für {{table}} fehlgeschlagen: {{detail}}. Warnung: Für die ersten {{count}} Tabellen wurde bereits TRUNCATE ausgeführt und sie können nicht wiederhergestellt werden", "file.backend.error.table_data_truncate_unsupported": "Der aktuelle Datenbanktyp {{type}} unterstützt TRUNCATE für Tabellen nicht. Verwenden Sie stattdessen Leeren", + "file.backend.error.target_directory_exists": "Zielverzeichnis ist bereits vorhanden", + "file.backend.error.target_sql_file_exists": "Ziel-SQL-Datei ist bereits vorhanden", "file.backend.error.write_failed": "Schreiben fehlgeschlagen: {{detail}}", "file.backend.message.cancel_requested": "Abbruchanforderung gesendet", "file.backend.message.execution_cancelled": "Ausführung abgebrochen. Ausgeführt: {{executed}}, fehlgeschlagen: {{failed}}, Dauer: {{duration}}.", @@ -4523,6 +6227,7 @@ "file.backend.message.user_cancelled": "Benutzer hat die Ausführung abgebrochen", "file.backend.filter.all_files": "Alle Dateien", "file.backend.filter.all_files_pattern": "Alle Dateien (*.*)", + "file.backend.filter.certificate_files": "Zertifikatdateien", "file.backend.filter.connection_package": "GoNavi-Verbindungspaket (*.gonavi-conn)", "file.backend.filter.database_files": "Datenbankdateien", "file.backend.filter.data_files": "Datendateien", @@ -4532,7 +6237,52 @@ "file.backend.filter.private_key_files": "Private-Key-Dateien", "file.backend.filter.sql_files": "SQL-Dateien (*.sql)", "file.backend.filter.sqlite_files": "SQLite-Dateien", + "ai_service.backend.builtin_prompt.title.general_chat": "Allgemeiner Chat-Assistent", + "ai_service.backend.builtin_prompt.body.general_chat": "Du bist der GoNavi AI-Assistent, ein spezialisiertes Expertensystem, das tief in den GoNavi-Datenbank- und Cache-Client integriert ist.\nDein Ziel ist es, für Entwickler, DBAs und Data Scientists das nützlichste zweite Gehirn zu sein und professionelle, präzise sowie zukunftsorientierte Lösungen für datenbezogene Aufgaben zu liefern.\n\nKernrolle und Interaktionston:\n- Fachlich fundiert: Beurteile Datenbankprodukte wie MySQL, PostgreSQL, DuckDB und Redis solide, einschließlich Ausführungsplänen, Indizierung und Speicherverhalten.\n- Direkt und praktisch: Vermeide leeres Gerede. Wenn die Absicht des Benutzers klar ist, beginne mit elegantem Code oder Schritten, die direkt nutzbar sind.\n- Strukturiert und gut lesbar: Nutze Markdown-Überschriften, Hervorhebungen und Codeblöcke mit korrekter Sprachkennung wie sql, json oder bash.\n- Produktionssicherheit zuerst: Wenn eine SQL-Anweisung ernsthafte Risiken erzeugen kann, etwa DELETE oder UPDATE ohne WHERE-Klausel oder eine Abfrage, die eine große Produktionstabelle sperren könnte, gib vor dem Fortfahren eine klare Warnung aus.\n\nFähigkeitskarte:\n1. Natürliche Sprache zu Datenoperationen: Übersetze menschliche Absichten in präzise Abfragen oder Befehle.\n2. Ausführungslogik erklären: Erläutere die Logik und Performance-Auswirkungen hinter Abfragen.\n3. Expertenoptimierung: Erkenne Engpässe und schlage Index-, Rewrite- oder Ausführungsstrategien vor.\n4. Daten-Insights: Extrahiere sinnvolle Muster aus Ergebnismengen, statt Zeilen nur nachzuerzählen.\n5. Architekturreview: Bewerte Grenzen des Schemas und schlage Entwicklungspfade vor, die Datenwachstum aushalten.\n\nInteraktionsregeln:\n- Verwende professionelle, kooperative Sprache und passe dich an die vom Benutzer gewählte Oberflächensprache an.\n- Wenn Datenbankcode angefordert wird, verbinde die Antwort mit den Best Practices der jeweiligen Engine. Ist die genaue Version unbekannt, nutze einen standardorientierten Ausgangspunkt und weise auf wichtige Versionsunterschiede hin, zum Beispiel MySQL 8 Window Functions.\n- Lehne nicht vorschnell ab: Wenn der Benutzer SQL verlangt, aber keine detaillierte DDL angehängt ist, nutze den Gesprächskontext und reine Tabellennamenlisten, um die wahrscheinliche Zieltabelle abzuleiten. Ist das nicht möglich, erkläre die bekannten Informationen und frage, welche Tabelle abgefragt werden soll.", + "ai_service.backend.builtin_prompt.title.sql_generate": "SQL-Generator", + "ai_service.backend.builtin_prompt.body.sql_generate": "Du bist der GoNavi AI-Assistent, ein Experte für Datenbankentwicklung und den Aufbau von SQL-Abfragen. Erzeuge aus der natürlichsprachlichen Anfrage des Benutzers präzise, elegante und performante SQL-Abfragen oder Redis-Befehle.\n\nStrenge Ausgaberegeln:\n1. Priorisiere reine Codeausgabe: Setze Code immer in einen markdown-Codeblock mit korrekter Sprachkennung, zum Beispiel sql oder bash.\n2. Bleibe knapp: Vermeide lange Vorreden und komme direkt zur Antwort.\n3. Schütze die Produktionssicherheit: Bevorzuge parametrisierte Abfragen oder defensive Muster, um SQL-Injection zu vermeiden. Bei DELETE- oder UPDATE-Anweisungen ohne explizite Bedingungen musst du eine deutliche Red-Line-Warnung ausgeben.\n4. Optimiere auf Performance: Ergänze bei großen Abfragen standardmäßig sinnvolle LIMIT-Begrenzungen, etwa LIMIT 100, und bevorzuge effiziente Muster für JOIN und Aggregation.\n5. Kommentiere nur bei Bedarf: Bei komplex verschachtelter Logik füge im Codeblock kurze einzeilige Kommentare hinzu, die die Idee erklären.", + "ai_service.backend.builtin_prompt.body.sql_explain": "Du bist der GoNavi AI-Assistent, ein erfahrener Senior-Datenbankentwickler mit tiefem Praxiswissen. Erkläre die zugrunde liegende Absicht und Ausführungslogik der SQL-Anweisung des Benutzers in professioneller, klar strukturierter und gut verständlicher Entwicklersprache.\n\nErklärungsrichtlinien:\n1. Makrologik zerlegen: Fasse in einem knappen Satz zusammen, welches fachliche Problem diese SQL-Anweisung lösen soll.\n2. Schrittweise Ausführung erläutern: Zerlege die wichtigsten Klauseln in der tatsächlichen Ausführungsreihenfolge des Executors, etwa FROM -> JOIN -> WHERE -> GROUP BY -> SELECT -> ORDER BY.\n3. Performance-Risiken erkennen: Weise auf wahrscheinliche Fallen hin, zum Beispiel implizite Typkonvertierungen, Funktionsaufrufe ohne Indexnutzung, mögliche Cartesian products oder Full table scans.\n4. Sorgfältig formatieren: Stelle Kernpunkte als Liste dar, hebe wichtige Begriffe fett hervor und halte auch längere Erklärungen gut lesbar.", + "ai_service.backend.builtin_prompt.title.sql_explain": "SQL-Erklärer", + "ai_service.backend.builtin_prompt.title.sql_optimize": "SQL-Optimierer", + "ai_service.backend.builtin_prompt.body.sql_optimize": "Du bist der GoNavi AI Assistant, ein Full-Stack-Performance-Engineer und Senior-DBA mit Erfahrung in hochgradig parallel belasteten Systemen im großen Maßstab. Diagnostiziere die ursprüngliche SQL-Anweisung des Nutzers präzise und liefere eine konkrete Performance-Refactoring-Empfehlung.\n\nAnforderungen an Diagnose und Empfehlung:\n1. Performance-Engpässe erkennen: Benenne die Schwachstellen der Anweisung genau, etwa eine ungeeignete treibende Tabelle, nicht nutzbare Covering Indexes oder unnötige Unterabfragen.\n2. Refaktorierte SQL-Version: Wenn Performance-Potenzial besteht, zeige eine gründlich optimierte, leistungsfähige Variante und wahre dabei die logische Äquivalenz.\n3. Ursache erklären: Sage nicht nur, was geändert werden soll, sondern erkläre auch, warum der Executor dadurch schneller arbeitet.\n4. Index-Aufbau empfehlen: Wenn die aktuelle Struktur die Last nicht tragen kann, schlage konkrete DDL-nahe CREATE INDEX-Anweisungen vor und nenne die Begründung, etwa Leftmost-Prefix-Matching.\n5. Priorität bewerten: Schließe die Antwort mit der Dringlichkeit der Optimierung ab: hoch für blockierende oder sperrkritische Risiken, mittel für Durchsatzengpässe und niedrig für langfristige Feinabstimmung.", + "ai_service.backend.builtin_prompt.body.data_analyze": "Du bist der GoNavi AI Assistant, ein erfahrener Datenanalyseexperte mit scharfem Geschäftssinn. Prüfe die Datenstichprobe aus der Abfrage des Nutzers und extrahiere die darin verborgenen wertvollen Informationen.\n\nZiele der Analyse:\n1. Harte Statistik: Fasse die Gesamtzahl der Zeilen und zentrale numerische Kennzahlen zusammen, etwa Extremwerte, Durchschnittswerte und aggregierte Mediane.\n2. Trends und Anomalien: Wenn die Daten Zeitstempel enthalten, erkenne steigende oder fallende Trends; wenn Ausreißer vorhanden sind, markiere sie klar.\n3. Geschäftlichen Wert herausarbeiten: Gib die Daten nicht nur wieder. Kombiniere sichtbare Muster mit AI-Urteilskraft und nenne eine konstruktive Handlungsempfehlung für Entscheider oder Entwickler.\n4. Darstellungsformat: Strukturiere die Analyse als knappen Mini-Report mit Titel und verdichteten Stichpunkten und vermeide flache, mechanische Nacherzählung.", + "ai_service.backend.builtin_prompt.title.data_analyze": "Datenanalyse", + "ai_service.backend.builtin_prompt.body.schema_insight": "Du bist der GoNavi AI Assistant, ein leitender Datenbankarchitekt für den gesamten Datenbank-Lebenszyklus. In diesem Modus prüfst du die vom Nutzer bereitgestellten Tabellenstrukturen streng nach Normalisierung, Wartbarkeit und Zukunftsfähigkeit.\n\nPrüfperspektiven:\n1. Normalisierungsabwägung: Erkenne offensichtliche denormalisierte Entwürfe und beurteile, ob die Redundanz sinnvoll der Performance dient oder schlicht ein Strukturfehler ist.\n2. Indexrobustheit: Bewerte Primärschlüsselentscheidungen wie Auto-Increment gegenüber UUID, redundante Indizes, die Schreibvorgänge verlangsamen, und fehlende häufig genutzte zusammengesetzte Indizes.\n3. Physische Kapazitätsvorausschau: Prüfe Datentypen wie übergroße VARCHAR-Felder oder unnötige BIGINT-Spalten, die Speicher verschwenden können.\n4. Code-nahe Anleitung: Wenn strukturelle Defekte vorliegen, benenne nicht nur das Problem. Gib bei Bedarf konkrete ALTER TABLE-Verbesserungsskripte an.", + "ai_service.backend.builtin_prompt.title.schema_insight": "Schemareview", + "ai_service.backend.database_context.title": "## Aktueller Datenbankkontext", + "ai_service.backend.database_context.database_type": "Datenbanktyp: {{type}}", + "ai_service.backend.database_context.database_name": "Datenbankname: {{name}}", + "ai_service.backend.database_context.table_schema": "### Tabellenstruktur", + "ai_service.backend.database_context.table_heading": "#### Tabelle: {{table}}", + "ai_service.backend.database_context.row_count": "[ca. {{count}} Zeilen]", + "ai_service.backend.database_context.column_name": "Spalte", + "ai_service.backend.database_context.column_type": "Typ", + "ai_service.backend.database_context.column_nullable": "Nullable", + "ai_service.backend.database_context.column_primary_key": "Primärschlüssel", + "ai_service.backend.database_context.column_comment": "Kommentar", + "ai_service.backend.database_context.value_yes": "Ja", + "ai_service.backend.database_context.value_no": "Nein", + "ai_service.backend.database_context.indexes": "**Indizes:**", + "ai_service.backend.database_context.unique_index": " (eindeutig)", + "ai_service.backend.database_context.sample_data": "**Beispieldaten ({{count}} Zeilen):**", + "ai_service.backend.provider.image_fallback_prompt": "Bitte beschreibe und analysiere dieses Bild.", + "ai_service.backend.provider.image_omitted_notice": "[Bild ausgelassen: Das aktuelle Modell oder die Upstream-API unterstützt keine Bildeingaben. Wechsle zu einem vision-fähigen Modell und sende das Bild erneut.]", "ai_service.backend.message.provider_test_success": "Endpunkt-Verbindungstest erfolgreich", + "ai_service.backend.message.mcp_test_success": "MCP-Dienstverbindung erfolgreich; {{count}} Tools gefunden", + "ai_service.backend.message.skill_unnamed": "Unbenanntes Skill", + "ai_service.backend.error.mcp_command_required": "MCP-Befehl darf nicht leer sein", + "ai_service.backend.error.mcp_tool_alias_invalid": "Ungültiger MCP-Tool-Alias: {{alias}}", + "ai_service.backend.error.mcp_transport_unsupported": "Nicht unterstützter MCP-Transport: {{transport}}", + "ai_service.backend.error.mcp_server_not_found": "MCP-Dienst wurde nicht gefunden: {{serverID}}", + "ai_service.backend.error.mcp_server_disabled": "MCP-Dienst ist deaktiviert: {{name}}", + "ai_service.backend.error.mcp_tool_arguments_parse_failed": "MCP-Tool-Argumente konnten nicht geparst werden: {{detail}}", + "ai_service.backend.error.mcp_http_start_failed": "Starten des GoNavi MCP HTTP-Dienstes fehlgeschlagen: {{detail}}", + "ai_service.backend.error.mcp_http_stop_failed": "Stoppen des GoNavi MCP HTTP-Dienstes fehlgeschlagen: {{detail}}", + "ai_service.backend.error.mcp_http_process_exited": "Der GoNavi MCP HTTP-Dienst wurde unerwartet beendet: {{detail}}", + "ai_service.backend.error.mcp_http_executable_resolve_failed": "Die aktuelle GoNavi-Programmdatei konnte nicht ermittelt werden: {{detail}}", + "ai_service.backend.error.mcp_http_subprocess_exited": "Der MCP HTTP-Unterprozess wurde beendet", + "ai_service.backend.error.mcp_http_health_status_failed": "healthz gab HTTP {{statusCode}} zurück", + "ai_service.backend.error.mcp_http_token_generate_failed": "MCP HTTP-Token konnte nicht erzeugt werden: {{detail}}", "ai_service.backend.error.provider_test_failed": "Verbindungstest fehlgeschlagen: {{detail}}", "ai_service.backend.error.provider_auth_failed": "API Key ist ungültig oder die Anfrage wurde abgelehnt (HTTP {{status}}){{body}}", "ai_service.backend.error.provider_http_status_failed": "Endpunkt hat einen unerwarteten Status zurückgegeben (HTTP {{status}}){{body}}", @@ -4783,6 +6533,8 @@ "app.theme.font_family.title": "Schriftfamilie", "app.theme.font_family.ui_title": "UI-Schriftfamilie", "app.theme.font_family.mono_title": "Monospace-Schriftfamilie", + "app.theme.font_family.default_ui_option": "Standard-UI-Schrift", + "app.theme.font_family.default_mono_option": "Standard-Code-Schrift", "app.theme.font_family.load_failed": "Systemschriften konnten nicht geladen werden", "app.theme.font_family.load_failed_fallback": "Systemschriften konnten nicht geladen werden. Häufige Schriftvorgaben werden verwendet: {{error}}", "app.theme.font_family.loaded_ui_hint": "{{count}} Schriftfamilien wurden auf diesem System gelesen. Tippen Sie zum Suchen. Leeren setzt auf die Standard-UI-Schrift zurück.", @@ -4812,5 +6564,731 @@ "sidebar.v2_database_menu.export_backup_section": "Export und Sicherung", "sidebar.v2_database_menu.export_all_table_schema_sql": "Schemas aller Tabellen exportieren · SQL", "sidebar.v2_database_menu.backup_all_tables_sql": "Alle Tabellen sichern · Schema + Daten SQL", - "ai_settings.message.load_provider_failed": "Anbieterkonfiguration konnte nicht gelesen werden" + "sidebar.v2_schema_menu.meta": "{{database}} · Schema-Aktionen", + "sidebar.v2_schema_menu.edit_schema": "Schema bearbeiten", + "sidebar.v2_schema_menu.export_current_schema_sql": "Tabellenstrukturen des aktuellen Schemas exportieren · SQL", + "sidebar.v2_schema_menu.backup_current_schema_sql": "Alle Tabellen des aktuellen Schemas sichern · Schema + Daten", + "sidebar.v2_schema_menu.delete_schema_cascade": "Schema löschen · DROP CASCADE", + "ai_settings.message.load_provider_failed": "Anbieterkonfiguration konnte nicht gelesen werden", + "ai_settings.mcp_server.tool_schema_summary.title": "Gefundene Tools und Parameterhinweise", + "ai_settings.mcp_server.tool_schema_summary.parameter_counts": "{{count}} Parameter, {{requiredCount}} erforderlich; ein Stern markiert Pflichtfelder.", + "ai_settings.mcp_server.tool_schema_summary.no_input_schema": "Kein inputSchema deklariert; prüfen Sie vor dem Aufruf die Servicedokumentation oder verwenden Sie /mcptool.", + "ai_settings.mcp_server.tool_schema_summary.minimal_arguments_example": "Minimales arguments-Beispiel:", + "ai_settings.mcp_server.tool_schema_summary.more_parameters": "{{count}} weitere Parameter; verwenden Sie /mcptool, um das vollständige schema anzusehen", + "ai_settings.mcp_server.remote_quick_start.default_agent_name": "Remote Agent", + "ai_settings.mcp_server.remote_quick_start.title": "{{displayName}} Remote-MCP-Schnellkonfiguration", + "ai_settings.mcp_server.remote_quick_start.description": "Diese Snippets sind für Cloud-Agents, Setups ohne GUI/CLI und Windows GoNavi gedacht. In der Cloud werden nur MCP URL und Bearer Token gespeichert, keine Datenbankzugangsdaten; schema-only veröffentlicht standardmäßig nur Struktur-Tools.", + "ai_settings.mcp_server.remote_quick_start.badge.required": "Pflicht", + "ai_settings.mcp_server.remote_quick_start.badge.optional": "Optional", + "ai_settings.mcp_server.remote_quick_start.fill_prefix": "Ausfüllen: ", + "ai_settings.mcp_server.remote_quick_start.example_prefix": "Beispiel: ", + "ai_settings.mcp_server.remote_quick_start.avoid_prefix": "Vermeiden: ", + "ai_settings.mcp_server.remote_quick_start.card.cloud_agent": "Im Cloud-Agent konfigurieren", + "ai_settings.mcp_server.remote_quick_start.card.cli_config": "Konfiguration ohne GUI / per CLI erzeugen", + "ai_settings.mcp_server.remote_quick_start.card.cli_config_note": "Erzeugt eine Remote-MCP-Konfiguration, die in {{displayName}} eingefügt werden kann, ohne Datenbankpasswörter zu lesen oder auszugeben.", + "ai_settings.mcp_server.remote_quick_start.card.windows_launch": "GoNavi MCP HTTP unter Windows starten", + "ai_settings.mcp_server.remote_quick_start.card.standalone_binary": "Eigenständiges Binary: {{command}}", + "ai_settings.mcp_server.remote_quick_start.section.verification": "Prüfreihenfolge", + "ai_settings.mcp_server.remote_quick_start.section.security": "Sicherheitsgrenze", + "ai_settings.mcp_server.remote_quick_start.parameter.public_url.title": "Öffentliche/Tunnel-URL", + "ai_settings.mcp_server.remote_quick_start.parameter.public_url.fill": "Trage die Streamable HTTP MCP-Adresse ein, die der Cloud-Agent erreichen kann. Sie endet normalerweise mit /mcp.", + "ai_settings.mcp_server.remote_quick_start.parameter.public_url.avoid": "Verwende nicht die lokale Windows-Adresse 127.0.0.1; Cloud-Linux kann sie nicht erreichen.", + "ai_settings.mcp_server.remote_quick_start.parameter.bearer_token.title": "Bearer Token", + "ai_settings.mcp_server.remote_quick_start.parameter.bearer_token.fill": "Trage ein langes zufälliges token ein; Windows-Startbefehl und Cloud-Agent-Konfiguration müssen übereinstimmen.", + "ai_settings.mcp_server.remote_quick_start.parameter.bearer_token.avoid": "Verwende kein leeres oder kurzes token und trage hier kein Datenbankpasswort ein.", + "ai_settings.mcp_server.remote_quick_start.parameter.local_addr.title": "Lokale Listen-Adresse", + "ai_settings.mcp_server.remote_quick_start.parameter.local_addr.fill": "Windows GoNavi HTTP MCP lauscht standardmäßig auf 127.0.0.1:8765 und wird dann über Tunnel oder Reverse Proxy weitergeleitet.", + "ai_settings.mcp_server.remote_quick_start.parameter.local_addr.avoid": "Binde ohne Gateway-Isolation nicht direkt an 0.0.0.0 und stelle es nicht öffentlich bereit.", + "ai_settings.mcp_server.remote_quick_start.parameter.path.title": "MCP-Pfad", + "ai_settings.mcp_server.remote_quick_start.parameter.path.fill": "Halte den Pfad im lokalen Startbefehl, in der Tunnel-URL und in der Cloud-Agent-Konfiguration identisch.", + "ai_settings.mcp_server.remote_quick_start.parameter.path.avoid": "Verwende nicht an einer Stelle /mcp und an anderer Stelle /api/mcp; unterschiedliche Pfade führen zu 404.", + "ai_settings.mcp_server.remote_quick_start.parameter.server_id.title": "Service-ID", + "ai_settings.mcp_server.remote_quick_start.parameter.server_id.fill": "Benenne diesen MCP-Dienst für den Cloud-Agent. gonavi ist als Standard ausreichend.", + "ai_settings.mcp_server.remote_quick_start.parameter.server_id.avoid": "Benenne ihn nicht häufig um, sonst können bestehende Tool-Referenzen im Agent brechen.", + "ai_settings.mcp_server.remote_quick_start.verification.healthz": "Öffne unter Windows zuerst http://127.0.0.1:8765/healthz, um zu bestätigen, dass der GoNavi MCP HTTP-Dienst läuft.", + "ai_settings.mcp_server.remote_quick_start.verification.configure_agent": "Konfiguriere Streamable HTTP MCP in {{displayName}} und setze die URL auf die per Tunnel oder Reverse Proxy erreichbare /mcp-Adresse.", + "ai_settings.mcp_server.remote_quick_start.verification.inspect_schema": "Rufe zuerst get_connections auf, um connectionId zu erhalten, und lies dann get_databases / get_tables / get_columns.", + "ai_settings.mcp_server.remote_quick_start.security.credentials_stay_local": "Datenbankkonten und Passwörter bleiben in Windows GoNavi; schreibe keine Datenbankpasswörter in diese Konfiguration.", + "ai_settings.mcp_server.remote_quick_start.security.schema_only": "--schema-only registriert execute_sql standardmäßig nicht, daher erhält der Remote-Agent nur Struktur-Tools.", + "ai_settings.mcp_server.remote_quick_start.security.token_required": "HTTP MCP muss einen zufälligen Bearer Token verwenden und hinter HTTPS, einem privaten Netzwerk oder einem kontrollierten Tunnel liegen.", + "ai_settings.mcp_server.remote_quick_start.security.execute_sql": "Wenn du --schema-only entfernst und execute_sql freigibst, gelten weiterhin die GoNavi AI-Sicherheitskontrollen; Schreiboperationen müssen allowMutating=true ausdrücklich übergeben.", + "ai_settings.mcp_server.remote_quick_start.guide.title": "GoNavi MCP-Remotezugriff-Anleitung - {{displayName}}", + "ai_settings.mcp_server.remote_quick_start.guide.goal_heading": "Ziel:", + "ai_settings.mcp_server.remote_quick_start.guide.goal.credentials_stay_local": "Datenbankverbindungen, Konten und Passwörter bleiben in Windows GoNavi. Der Cloud-Agent muss keine Datenbankpasswörter speichern.", + "ai_settings.mcp_server.remote_quick_start.guide.goal.tools_only": "Der Cloud-Agent liest nur Ergebnisse wie get_connections/get_databases/get_tables/get_columns/get_table_ddl über MCP tools.", + "ai_settings.mcp_server.remote_quick_start.guide.goal.schema_only": "Remotezugriff nutzt standardmäßig schema-only und registriert execute_sql nicht; geeignet, um OpenClaw/Hermans nur Strukturzugriff zu geben.", + "ai_settings.mcp_server.remote_quick_start.guide.boundary_heading": "Aktuelle Grenze:", + "ai_settings.mcp_server.remote_quick_start.guide.boundary.local_stdio": "Der lokale Einstieg von GoNavi MCP ist stdio und eignet sich für Clients wie Claude Code / Codex, die auf demselben Rechner wie GoNavi laufen.", + "ai_settings.mcp_server.remote_quick_start.guide.boundary.remote_cloud": "Wenn OpenClaw/Hermans auf Cloud-Linux läuft, kann es den lokalen Windows-stdio-Befehl nicht direkt verwenden; starte GoNavi Streamable HTTP auf Windows und lasse den Cloud-Agent über Tunnel oder Reverse Proxy zugreifen.", + "ai_settings.mcp_server.remote_quick_start.guide.access_heading": "Empfohlener Zugriff:", + "ai_settings.mcp_server.remote_quick_start.guide.step.keep_windows_accessible": "1. Halte GoNavi auf Windows erreichbar; GoNavi liest gespeicherte Verbindungen und Systemanmeldedaten.", + "ai_settings.mcp_server.remote_quick_start.guide.step.run_command": "2. Führe dies auf Windows oder im vertrauenswürdigen Intranet aus: {{launchCommand}}.", + "ai_settings.mcp_server.remote_quick_start.guide.step.configure_remote_server": "3. Füge in {{displayName}} einen Remote MCP Server hinzu, wähle Streamable HTTP als transport, setze die URL auf die getunnelte bzw. per Reverse Proxy erreichbare /mcp-Adresse und setze Authorization: Bearer .", + "ai_settings.mcp_server.remote_quick_start.guide.step.inspect_schema": "4. Rufe zuerst get_connections auf, um connectionId zu erhalten, und danach Schema-Tools; schreibe database host/user/password nicht in die Cloud-Agent-Konfiguration.", + "ai_settings.mcp_server.remote_quick_start.guide.config_heading": "Kopierbarer Konfigurationsausschnitt (für Agents mit mcpServers JSON):", + "ai_settings.mcp_server.remote_quick_start.guide.config_command_heading": "Konfiguration ohne GUI / per CLI erzeugen:", + "ai_settings.mcp_server.remote_quick_start.guide.launch_command_heading": "CLI- / Dienst-Startbefehl:", + "ai_settings.mcp_server.remote_quick_start.guide.env_fallback": "Oder setze die Umgebungsvariable GONAVI_MCP_HTTP_TOKEN= und führe dann {{standaloneCommand}} aus", + "ai_settings.mcp_server.remote_quick_start.guide.execute_sql_note": "Wenn Remote-SQL-Ausführung ausdrücklich benötigt wird, entferne --schema-only; execute_sql bleibt unter GoNavi AI-Sicherheitskontrollen, und Schreibvorgänge müssen allowMutating=true ausdrücklich übergeben.", + "ai_settings.mcp_server.remote_quick_start.guide.current_hint": "Aktueller Hinweis: {{message}}", + "ai_chat.system.inspection_guidance.inspect_ai_runtime": "Bei Fragen zu aktuellem Modell, Sicherheitsstufe, verfuegbaren Tools oder aktivierten Skills / MCP-Tools zuerst inspect_ai_runtime aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_safety": "Bei Fragen zu Schreibschutz, DDL, allowMutating oder Sicherheitsgrenzen zuerst inspect_ai_safety aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_context": "Bei Fragen zu AI-Kontext, zugeordneten Tabellen oder eingebundenen Tabellenschemata zuerst inspect_ai_context aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_app_health": "Bei Fragen zu instabiler AI, GoNavi AI Gesamtzustand, Verbindungen, MCP, Logs oder Antwort-Bubbles zuerst inspect_app_health aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_support_bundle": "Bei Fragen zu Supportmaterial fuer AI, MCP, Verbindungen, Logs und Kontext zuerst inspect_ai_support_bundle aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_tool_catalog": "Bei Fragen zu Tool-Auswahl, Parameterhinweisen oder passendem Diagnose-Tool zuerst inspect_ai_tool_catalog aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_setup_health": "Bei Fragen zu AI-Einrichtung, Provider, Sendebereitschaft, MCP oder Guidance zuerst inspect_ai_setup_health aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_chat_readiness": "Bei Fragen zu Sendebereitschaft und fehlender AI-Chat-Konfiguration zuerst inspect_ai_chat_readiness aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_upstream_logs": "Bei Fragen zu Upstream-Anfragen, requestId, payload oder nicht ausgeloesten Tools zuerst inspect_ai_upstream_logs aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_providers": "Bei Fragen zu Providern, API Key, Modellliste oder Modellauswahl zuerst inspect_ai_providers aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_mcp_setup": "Bei Fragen zu MCP-Konfiguration, Claude/Codex-Anbindung oder externem Client-Zugriff zuerst inspect_mcp_setup aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_mcp_runtime_failures": "Bei Fragen zu MCP-Laufzeitfehlern, stdio, Docker MCP oder HTTP MCP Startproblemen zuerst inspect_mcp_runtime_failures aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_mcp_authoring_guide": "Bei Fragen zu command, args, env, timeout oder Vorlagen fuer neue MCP-Server zuerst inspect_mcp_authoring_guide aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_mcp_draft": "Bei Fragen zu MCP-README-Befehl, command/args/env/timeout-Entwurf oder GoNavi MCP-Eingabe zuerst inspect_mcp_draft aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_mcp_tool_schema": "Bei Fragen zu MCP-Tool-Argumenten, inputSchema oder arguments JSON zuerst inspect_mcp_tool_schema aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_guidance": "Bei Fragen zu aktiven Prompts, Skills oder database/JVM prompt zuerst inspect_ai_guidance aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_shortcuts": "Bei Fragen zu Shortcuts, Win/Mac-Unterschieden oder geaenderten Tastenkombinationen zuerst inspect_shortcuts aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_recent_connection_failures": "Bei Fragen zu Verbindungsfehlern, Cooldown, Validierung, SSH-Tunnel oder Parameterkompatibilitaet zuerst inspect_recent_connection_failures aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_app_logs": "Bei Fragen zu gonavi.log, aktuellen Logs, Startfehlern, MCP- oder Datenbankverbindungsfehlern zuerst inspect_app_logs aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_last_render_error": "Bei Fragen zu leeren AI-Nachrichten oder Renderfehlern in Antwort-Bubbles zuerst inspect_ai_last_render_error aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_message_flow": "Bei Fragen zu aufgeteilten Antworten, Tool-Call-Fortsetzung oder Nachrichtenfluss zuerst inspect_ai_message_flow aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_context_budget": "Bei Fragen zu zu grossem Kontext, langen Tool-Ergebnissen oder instabilen Modellantworten zuerst inspect_ai_context_budget aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_codebase_hotspots": "Bei Fragen zu grossen Dateien, Komponentenaufteilung oder AI/MCP/UI-Aenderungsrisiken zuerst inspect_codebase_hotspots aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_current_connection": "Bei Fragen zu aktueller Verbindung, Datenquelle, Datenbank, Adresse, SSH oder Proxy zuerst inspect_current_connection aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_connection_capabilities": "Bei Fragen zu Datenbankaktionen, Ergebnisbearbeitung oder Faehigkeiten der Datenquelle zuerst inspect_connection_capabilities aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_saved_connections": "Bei Fragen zu lokal gespeicherten Verbindungen, MySQL/PostgreSQL/Redis oder SSH/Proxy zuerst inspect_saved_connections aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_redis_topology": "Bei Fragen zu Redis Sentinel, Redis Cluster, Sentinel master, Redis DB oder mehreren Redis-Knoten zuerst inspect_redis_topology aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_external_sql_directories": "Bei Fragen zu externen SQL-Verzeichnissen, Skripten oder Herkunft einer SQL-Datei zuerst inspect_external_sql_directories aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_external_sql_file": "Bei Fragen zu konkreten externen SQL-Dateipfaden oder report.sql/job.sql zuerst inspect_external_sql_file aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_recent_sql_activity": "Bei Fragen zu kuerzlich ausgefuehrtem SQL, geloeschten Daten oder Fehlerhaeufigkeit pro Datenbank zuerst inspect_recent_sql_activity aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_sql_editor_transaction": "Bei Fragen zu Manual Commit, Autocommit, offenen Transaktionen oder DML-Transaktionssemantik zuerst inspect_sql_editor_transaction aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_sql_risk": "Bei Fragen zu Ausfuehren, Loeschen, Aktualisieren, DDL, Batch-SQL oder SQL-Risiko zuerst inspect_sql_risk aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_saved_queries": "Bei Fragen zu gespeicherten Abfragen, SQL-Historie oder frueheren Skripten zuerst inspect_saved_queries aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_ai_sessions": "Bei Fragen zu frueheren AI-Gespraechen oder passenden Sitzungsverlaeufen zuerst inspect_ai_sessions aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.inspection_guidance.inspect_sql_snippets": "Bei Fragen zu SQL-Snippets, Vorlagenpraefixen oder haeufig genutzten Vorlagen zuerst inspect_sql_snippets aufrufen, den echten GoNavi Zustand lesen und nicht aus Erinnerung oder UI-Vermutungen antworten.", + "ai_chat.system.context.custom_prompt.global": "Der Benutzer hat einen zusaetzlichen globalen Prompt hinterlegt. Befolge ihn, solange er Sicherheitsregeln und Fakten nicht widerspricht:\n{{content}}", + "ai_chat.system.context.custom_prompt.database": "Der Benutzer hat einen zusaetzlichen Prompt fuer die Datenbanksitzung hinterlegt. Befolge ihn, solange er Sicherheitsregeln und Fakten nicht widerspricht:\n{{content}}", + "ai_chat.system.context.custom_prompt.jvm": "Der Benutzer hat einen zusaetzlichen Prompt fuer die JVM-Ressourcenanalyse hinterlegt. Befolge ihn, solange er Sicherheitsregeln und Fakten nicht widerspricht:\n{{content}}", + "ai_chat.system.context.custom_prompt.jvm_diagnostic": "Der Benutzer hat einen zusaetzlichen Prompt fuer die JVM-Diagnose hinterlegt. Befolge ihn, solange er Sicherheitsregeln und Fakten nicht widerspricht:\n{{content}}", + "ai_chat.system.context.skill_prompt.required_tools": "\nAbhaengige Tools: {{requiredTools}}", + "ai_chat.system.context.skill_prompt": "Der aktive Skill \"{{skillName}}\" ({{skillDescription}}) gilt fuer diese Antwort. Befolge seine Einschraenkungen und Arbeitsweise:{{requiredTools}}\n{{content}}", + "ai_chat.system.context.skill_prompt_without_description": "Der aktive Skill \"{{skillName}}\" gilt fuer diese Antwort. Befolge seine Einschraenkungen und Arbeitsweise:{{requiredTools}}\n{{content}}", + "ai_chat.system.context.database_with_schema": "Du bist ein professioneller Datenbankassistent. Der Datenbanktyp der aktuellen Verbindung ist {{dbType}}. Erzeuge SQL im {{dbType}}-Dialekt. Die folgenden Tabellenstrukturen wurden vom Benutzer angehaengt; beruecksichtige sie vorrangig:\n\n{{ddlChunks}}", + "ai_chat.system.context.database_with_target": "Du bist ein professioneller Datenbankassistent. Der Datenbanktyp der aktuellen Verbindung ist {{dbType}}, der aktuelle Datenbankname ist {{dbName}}. Wenn der Benutzer eine bestimmte Tabelle oder Informationen zur aktuellen Datenbank benoetigt, rufe das bereitgestellte Tool get_tables auf, um Tabelleninformationen aktiv zu laden.", + "ai_chat.system.context.no_connections": "keine Verbindungen", + "ai_chat.system.context.database_without_context": "Du bist ein professioneller Datenbankassistent. Der Benutzer hat in der Oberflaeche keine konkrete Datenbank und keine Tabelle als Kontext ausgewaehlt.\n\nWichtige Regeln:\n1. Wenn du eine Zieltabelle fuer den Benutzer finden musst, rate niemals den Tabellennamen. Rufe Tools auf, um echte Daten zu laden.\n2. Vollstaendiger Ablauf: get_connections -> get_databases -> get_tables -> get_columns -> SQL erzeugen. Kein Schritt darf uebersprungen werden.\n3. Die Verbindungsprioritaet ist wichtig. Pruefe Verbindungen nach dem Laden in dieser Reihenfolge:\n - Erste Prioritaet: host ist localhost oder 127.0.0.1, oder der Verbindungsname weist auf eine lokale Umgebung hin.\n - Zweite Prioritaet: name oder host weist auf Entwicklung/lokal hin, oder host ist eine private IP wie 10.x, 192.168.x oder 172.16-31.x.\n - Dritte Prioritaet: andere Verbindungen wie Test oder Produktion.\n Wenn die Zieltabelle in einer hoeher priorisierten Verbindung gefunden wurde, verwende diese Verbindung direkt und suche nicht in niedriger priorisierten Verbindungen.\n4. Wenn die Zieltabelle in der aktuellen Datenbank nicht gefunden wird, pruefe weitere Datenbanken und gib nicht auf.\n5. Beende die Suche nur, wenn alle moeglichen Datenbanken geprueft wurden oder die Zieltabelle eindeutig gefunden wurde.\n6. Bei normalen Fragen ohne Datenbankabfrage antworte normal.\n\nSQL-Erzeugungsregeln:\n7. Wenn der Benutzer aktuellen Tab, aktuelles SQL, aktuellen Editor oder diese Anweisung erwaehnt, aber keinen Inhalt einfuegt, rufe zuerst inspect_active_tab auf, statt den geoeffneten Inhalt zu raten.\n8. Wenn der Benutzer fragt, welche Tabs offen sind oder welche Abfragen im Workspace liegen, rufe zuerst inspect_workspace_tabs auf und entscheide danach, welcher Tab genauer geprueft wird.\n9. Feldgenauigkeit ist eine absolute Regel. Vor dem Erzeugen von SQL muss get_columns aufgerufen werden, um die echte Feldliste der Zieltabelle zu laden. Jeder Feldname im SQL muss exakt dem von get_columns gelieferten field-Wert entsprechen, einschliesslich Gross-/Kleinschreibung. Felder duerfen nicht zusammengesetzt, abgekuerzt oder geraten werden.\n10. Wenn der Benutzer nach Indexoptimierung, Join-Beziehungen, Trigger-Nebenwirkungen, Constraints oder DDL-Details fragt, rufe nach get_columns bei Bedarf get_indexes, get_foreign_keys, get_triggers und get_table_ddl auf und gib dann die Schlussfolgerung.\n11. Verwende beim Erzeugen von SQL keinen qualifizierten Praefix im Format \"database.table\"; schreibe nur den Tabellennamen.\n12. Beim Bericht muessen Verbindungsname/ID und Datenbankname aus denselben tatsaechlichen get_tables-Parametern stammen. Mische nicht connectionId einer Verbindung mit dbName einer anderen.\n13. Wenn mehrere Datenbanken aehnliche Namen haben, sage klar, in welcher Datenbank die Zieltabelle liegt.\n14. Die erste Zeile jedes SQL-Codeblocks muss einen Kontextkommentar in exakt diesem Format enthalten: -- @context connectionId= dbName=. connectionId und dbName muessen aus denselben erfolgreichen get_tables-Aufrufparametern stammen. Beispiel:\n```sql\n-- @context connectionId=1770778676549 dbName=mkefu_test\nSELECT * FROM users WHERE status = 1;\n```\n\nVorhandene Verbindungen: [{{connList}}]", + "ai_chat.system.context.jvm_diagnostic_policy.read_only": "Standardmaessig mit Read-only-Diagnose denken: nur observe-, trace- und Troubleshooting-Befehle erzeugen und nie annehmen, dass bereits etwas ausgefuehrt wurde.", + "ai_chat.system.context.jvm_diagnostic_policy.writable": "Diagnosebefehle duerfen erzeugt werden, aber zuerst muss ein Plan gegeben werden; der Benutzer entscheidet, ob er ausgefuehrt wird.", + "ai_chat.system.context.permission.allowed": "erlaubt", + "ai_chat.system.context.permission.denied": "verboten", + "ai_chat.system.context.jvm_diagnostic_prompt": "Du bist der JVM-Diagnoseassistent von GoNavi. Der aktuelle Tab ist eine Arthas-kompatible Diagnose-Workbench. Ziel ist ein strukturierter Diagnoseplan, der in die Diagnosekonsole zurueckgefuehrt werden kann.\n\nAktuelle Verbindung: {{connectionName}}\nZielhost: {{host}}\nDiagnose-transport: {{transport}}\nLaufzeitumgebung: {{environment}}\nVerbindungsrichtlinie: {{connectionPolicy}}\nBefehlsrechte: observe={{observeAllowed}}, trace={{traceAllowed}}, mutating={{mutatingAllowed}}\n\nAntwortregeln:\n1. Eine kurze Analyse ist erlaubt, aber die Antwort muss genau einen ```json Codeblock enthalten.\n2. JSON-Felder sind strikt auf intent, transport, command, riskLevel, reason und expectedSignals begrenzt.\n3. transport muss der aktuelle Wert {{transport}} sein; erfinde keinen anderen transport.\n4. command muss ein einzelner Diagnosebefehl sein, ohne Shell-Prompt, Zeilenverkettung, mehrere Befehle oder Code-Fence.\n5. riskLevel darf nur low, medium oder high sein.\n6. expectedSignals muss ein Array von Strings sein, das die nach der Ausfuehrung zu beobachtenden Signale beschreibt.\n7. Wenn Rechte eine Operationsklasse nicht erlauben, gib keinen Befehl dieser Klasse aus. Wenn die Anfrage nicht erfuellt werden kann, nenne die Einschraenkung direkt.", + "ai_chat.system.context.jvm_runtime_policy.read_only": "Dies ist eine Read-only-Verbindung. Analysiere nur und erzeuge Aenderungsplaene; nimm niemals an, dass Schreibvorgaenge bereits ausgefuehrt wurden.", + "ai_chat.system.context.jvm_runtime_policy.writable": "Dies ist eine schreibbare Verbindung, aber jede Aenderung muss zuerst eine Vorschau erzeugen und auf menschliche Bestaetigung warten.", + "ai_chat.system.context.jvm_runtime_resource_path": "Aktueller Ressourcenpfad: {{resourcePath}}", + "ai_chat.system.context.jvm_runtime_resource_path_unselected": "Derzeit ist kein konkreter Ressourcenpfad ausgewaehlt.", + "ai_chat.system.context.jvm_runtime_prompt": "Du bist der JVM-Laufzeitanalyseassistent von GoNavi. Der aktuelle Kontext ist nicht SQL, sondern der JVM-Ressourcen-Workspace.\n\nAktuelle Verbindung: {{connectionName}}\nZielhost: {{host}}\nProvider-Modus: {{providerMode}}\nLaufzeitumgebung: {{environment}}\nVerbindungsrichtlinie: {{connectionPolicy}}\n{{resourcePathLine}}\n\nAntwortregeln:\n1. Du darfst Ressourcenstruktur, Risiken, Aenderungsvorschlaege und Rollback-Vorschlaege erklaeren.\n2. Wenn der Benutzer einen JVM-Aenderungsplan verlangt, gib genau einen ```json Codeblock aus und beschraenke JSON-Felder strikt auf targetType, selector, action, payload und reason.\n3. action soll bevorzugt aus supportedActions im aktuellen Ressourcen-Snapshot oder in den Metadaten stammen. Wenn dort nichts deklariert ist, leite vorsichtig aus dem Snapshot ab.\n4. selector.resourcePath soll bevorzugt den aktuellen Ressourcenpfad verwenden. Wenn der Pfad unbekannt ist, sage klar, dass keine genaue Zielauswahl moeglich ist, statt einen Pfad zu erfinden.\n5. payload darf nur {\"format\":\"json\",\"value\":{...}} oder {\"format\":\"text\",\"value\":\"...\"} verwenden; gib keine Skripte, Befehle oder nackten Werte aus.\n6. Gib keine Skripte, Befehle oder Aussagen wie \"bereits erfolgreich ausgefuehrt\" aus.", + "ai_settings.mcp_server.argument_hints.command_field_warning": "Das Startbefehlsfeld enthält noch {{count}} Argumente: {{args}}. Lass in command nur {{command}} stehen und verschiebe den Rest in die Befehlsargumente.", + "ai_settings.mcp_server.argument_hints.hidden_value": "", + "ai_settings.mcp_server.argument_hints.possible_secret_hidden": "", + "ai_settings.mcp_server.argument_hints.next_action.add_step": "{{label}} ergänzen, Beispiel: {{example}}", + "ai_settings.mcp_server.argument_hints.generic.label": "Nicht erkanntes Argument", + "ai_settings.mcp_server.argument_hints.generic.detail": "GoNavi kann die fachliche Bedeutung von --{{flag}} nicht aus dem Argumentnamen ableiten, gibt es aber in aktueller Reihenfolge an den MCP-Prozess weiter.", + "ai_settings.mcp_server.argument_hints.generic.value_hint": "Prüfe im MCP README, ob dieses Argument einen Wert braucht. Falls ja, setze den Wert als nächstes Argument oder nutze --name=value.", + "ai_settings.mcp_server.argument_hints.detail.value_label": "Wert für {{label}}", + "ai_settings.mcp_server.argument_hints.detail.sensitive_value_detail": "Dies ist der sensible Wert für das vorherige Argument {{argument}} und wird im Hinweis maskiert.", + "ai_settings.mcp_server.argument_hints.detail.value_detail": "Dies ist der Wert für das vorherige Argument {{argument}}.", + "ai_settings.mcp_server.argument_hints.profile.npm.title": "npx / npm Argumentreihenfolge", + "ai_settings.mcp_server.argument_hints.profile.npm.summary": "npm-MCP-Server benötigen meist Bestätigungsflag, Paketname und --stdio als getrennte Argumente.", + "ai_settings.mcp_server.argument_hints.profile.npm.order": "Empfohlene Reihenfolge: -y -> Paket -> --stdio -> Dienstargumente", + "ai_settings.mcp_server.argument_hints.profile.node.title": "Node-Skript Argumentreihenfolge", + "ai_settings.mcp_server.argument_hints.profile.node.summary": "Bei Node-Startern bleibt in command nur node/bun/deno; Skriptpfad und --stdio gehören in args.", + "ai_settings.mcp_server.argument_hints.profile.node.order": "Empfohlene Reihenfolge: Skriptpfad -> --stdio -> Dienstargumente", + "ai_settings.mcp_server.argument_hints.profile.python.title": "Python Argumentreihenfolge", + "ai_settings.mcp_server.argument_hints.profile.python.summary": "Python-MCP-Server nutzen oft python -m module_name; -m und Modulname bleiben getrennte Argumente.", + "ai_settings.mcp_server.argument_hints.profile.python.order": "Empfohlene Reihenfolge: -m -> Modulname -> --stdio", + "ai_settings.mcp_server.argument_hints.profile.uvx.title": "uvx Argumentreihenfolge", + "ai_settings.mcp_server.argument_hints.profile.uvx.summary": "uvx-MCP-Server beginnen meist mit dem Paketnamen und ergänzen stdio- oder Konfigurationsargumente aus dem README.", + "ai_settings.mcp_server.argument_hints.profile.uvx.order": "Empfohlene Reihenfolge: Paket -> --stdio -> Dienstargumente", + "ai_settings.mcp_server.argument_hints.profile.docker.title": "Docker-MCP Argumentreihenfolge", + "ai_settings.mcp_server.argument_hints.profile.docker.summary": "Bei Docker bleibt in command nur docker; run, -i, --rm, Image-Name und Containerargumente gehören in args.", + "ai_settings.mcp_server.argument_hints.profile.docker.order": "Empfohlene Reihenfolge: run -> --rm -> -i -> -e KEY=VALUE -> Image-Name -> Dienstargumente", + "ai_settings.mcp_server.argument_hints.profile.executable.title": "Hinweise für lokale ausführbare Dateien", + "ai_settings.mcp_server.argument_hints.profile.executable.summary": "Bei eigenen oder kompilierten MCP-Servern gilt das README; GoNavi übergibt Argumente unverändert in Tag-Reihenfolge.", + "ai_settings.mcp_server.argument_hints.profile.executable.order": "Übliche Reihenfolge: stdio/--stdio -> Konfigurationsdatei oder Dienstargument", + "ai_settings.mcp_server.argument_hints.step.yes.label": "Installationsbestätigung überspringen", + "ai_settings.mcp_server.argument_hints.step.yes.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.package.label": "MCP-Paketname", + "ai_settings.mcp_server.argument_hints.step.package.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.stdio.label": "stdio-Argument", + "ai_settings.mcp_server.argument_hints.step.stdio.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.scope.label": "Zugelassenes Verzeichnis oder Dienstargument", + "ai_settings.mcp_server.argument_hints.step.scope.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.script.label": "Skriptpfad", + "ai_settings.mcp_server.argument_hints.step.script.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.business.label": "Dienstargument", + "ai_settings.mcp_server.argument_hints.step.business.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.module-flag.label": "Modul-Flag oder Skript", + "ai_settings.mcp_server.argument_hints.step.module-flag.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.module-name.label": "Modulname", + "ai_settings.mcp_server.argument_hints.step.module-name.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.run.label": "run-Unterbefehl", + "ai_settings.mcp_server.argument_hints.step.run.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.interactive.label": "Standardeingabe offen halten", + "ai_settings.mcp_server.argument_hints.step.interactive.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.cleanup.label": "Container nach Ende aufräumen", + "ai_settings.mcp_server.argument_hints.step.cleanup.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.image.label": "Image-Name", + "ai_settings.mcp_server.argument_hints.step.image.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.step.container-env.label": "Container-Umgebungsvariable", + "ai_settings.mcp_server.argument_hints.step.container-env.detail": "Folge dem Startbeispiel im README und trage dieses Argument als eigenen Tag ein.", + "ai_settings.mcp_server.argument_hints.business.api_key.label": "API-Key", + "ai_settings.mcp_server.argument_hints.business.api_key.detail": "Übergib Authentifizierungsdaten nur, wenn das README ein Befehlsargument verlangt; bevorzuge Umgebungsvariablen.", + "ai_settings.mcp_server.argument_hints.business.api_key.value_hint": "Trage den echten Wert lokal ein; nicht in Chat oder Screenshots einfügen.", + "ai_settings.mcp_server.argument_hints.business.token.label": "Token", + "ai_settings.mcp_server.argument_hints.business.token.detail": "Übergib Authentifizierungsdaten nur, wenn das README ein Befehlsargument verlangt; bevorzuge Umgebungsvariablen.", + "ai_settings.mcp_server.argument_hints.business.token.value_hint": "Trage den echten Wert lokal ein; nicht in Chat oder Screenshots einfügen.", + "ai_settings.mcp_server.argument_hints.business.access_token.label": "Zugriffstoken", + "ai_settings.mcp_server.argument_hints.business.access_token.detail": "Übergib Authentifizierungsdaten nur, wenn das README ein Befehlsargument verlangt; bevorzuge Umgebungsvariablen.", + "ai_settings.mcp_server.argument_hints.business.access_token.value_hint": "Trage den echten Wert lokal ein; nicht in Chat oder Screenshots einfügen.", + "ai_settings.mcp_server.argument_hints.business.password.label": "Passwort", + "ai_settings.mcp_server.argument_hints.business.password.detail": "Übergib Authentifizierungsdaten nur, wenn das README ein Befehlsargument verlangt; bevorzuge Umgebungsvariablen.", + "ai_settings.mcp_server.argument_hints.business.password.value_hint": "Trage den echten Wert lokal ein; nicht in Chat oder Screenshots einfügen.", + "ai_settings.mcp_server.argument_hints.business.secret.label": "Geheimnis", + "ai_settings.mcp_server.argument_hints.business.secret.detail": "Übergib Authentifizierungsdaten nur, wenn das README ein Befehlsargument verlangt; bevorzuge Umgebungsvariablen.", + "ai_settings.mcp_server.argument_hints.business.secret.value_hint": "Trage den echten Wert lokal ein; nicht in Chat oder Screenshots einfügen.", + "ai_settings.mcp_server.argument_hints.business.config.label": "Konfigurationsdatei", + "ai_settings.mcp_server.argument_hints.business.config.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.config.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.config_file.label": "Konfigurationsdatei", + "ai_settings.mcp_server.argument_hints.business.config_file.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.config_file.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.short_config.label": "Konfigurationsdatei", + "ai_settings.mcp_server.argument_hints.business.short_config.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.short_config.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.directory.label": "Zugelassenes Verzeichnis", + "ai_settings.mcp_server.argument_hints.business.directory.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.directory.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.dir.label": "Verzeichnis", + "ai_settings.mcp_server.argument_hints.business.dir.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.dir.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.root.label": "Stammverzeichnis", + "ai_settings.mcp_server.argument_hints.business.root.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.root.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.workspace.label": "Arbeitsbereichsverzeichnis", + "ai_settings.mcp_server.argument_hints.business.workspace.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.workspace.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.path.label": "Pfad", + "ai_settings.mcp_server.argument_hints.business.path.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.path.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.url.label": "Service-URL", + "ai_settings.mcp_server.argument_hints.business.url.detail": "Gibt eine Serviceadresse oder einen API-Einstiegspunkt für den MCP-Dienst an.", + "ai_settings.mcp_server.argument_hints.business.url.value_hint": "Gib die vollständige Adresse ein, ohne Geheimnisse in die URL einzubauen.", + "ai_settings.mcp_server.argument_hints.business.endpoint.label": "Endpunkt", + "ai_settings.mcp_server.argument_hints.business.endpoint.detail": "Gibt eine Serviceadresse oder einen API-Einstiegspunkt für den MCP-Dienst an.", + "ai_settings.mcp_server.argument_hints.business.endpoint.value_hint": "Gib die vollständige Adresse ein, ohne Geheimnisse in die URL einzubauen.", + "ai_settings.mcp_server.argument_hints.business.base_url.label": "Basis-URL", + "ai_settings.mcp_server.argument_hints.business.base_url.detail": "Gibt eine Serviceadresse oder einen API-Einstiegspunkt für den MCP-Dienst an.", + "ai_settings.mcp_server.argument_hints.business.base_url.value_hint": "Gib die vollständige Adresse ein, ohne Geheimnisse in die URL einzubauen.", + "ai_settings.mcp_server.argument_hints.business.host.label": "Hostadresse", + "ai_settings.mcp_server.argument_hints.business.host.detail": "Konfiguriert Host, Port oder Netzwerkoptionen für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.host.value_hint": "Nutze Host oder Port aus dem README.", + "ai_settings.mcp_server.argument_hints.business.port.label": "Port", + "ai_settings.mcp_server.argument_hints.business.port.detail": "Konfiguriert Host, Port oder Netzwerkoptionen für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.port.value_hint": "Nutze Host oder Port aus dem README.", + "ai_settings.mcp_server.argument_hints.business.transport.label": "Transportmodus", + "ai_settings.mcp_server.argument_hints.business.transport.detail": "Steuert Transport, Profil, Berechtigungen oder Laufmodus des MCP-Dienstes.", + "ai_settings.mcp_server.argument_hints.business.transport.value_hint": "Nutze Enum-Wert oder Schaltersemantik aus dem README.", + "ai_settings.mcp_server.argument_hints.business.mode.label": "Ausführungsmodus", + "ai_settings.mcp_server.argument_hints.business.mode.detail": "Steuert Transport, Profil, Berechtigungen oder Laufmodus des MCP-Dienstes.", + "ai_settings.mcp_server.argument_hints.business.mode.value_hint": "Nutze Enum-Wert oder Schaltersemantik aus dem README.", + "ai_settings.mcp_server.argument_hints.business.profile.label": "Profil", + "ai_settings.mcp_server.argument_hints.business.profile.detail": "Steuert Transport, Profil, Berechtigungen oder Laufmodus des MCP-Dienstes.", + "ai_settings.mcp_server.argument_hints.business.profile.value_hint": "Nutze Enum-Wert oder Schaltersemantik aus dem README.", + "ai_settings.mcp_server.argument_hints.business.read_only.label": "Nur-Lesen-Modus", + "ai_settings.mcp_server.argument_hints.business.read_only.detail": "Steuert Transport, Profil, Berechtigungen oder Laufmodus des MCP-Dienstes.", + "ai_settings.mcp_server.argument_hints.business.read_only.value_hint": "Nutze Enum-Wert oder Schaltersemantik aus dem README.", + "ai_settings.mcp_server.argument_hints.business.readonly.label": "Nur-Lesen-Modus", + "ai_settings.mcp_server.argument_hints.business.readonly.detail": "Steuert Transport, Profil, Berechtigungen oder Laufmodus des MCP-Dienstes.", + "ai_settings.mcp_server.argument_hints.business.readonly.value_hint": "Nutze Enum-Wert oder Schaltersemantik aus dem README.", + "ai_settings.mcp_server.argument_hints.business.headless.label": "Headless-Modus", + "ai_settings.mcp_server.argument_hints.business.headless.detail": "Steuert das Laufzeitverhalten des gestarteten MCP-Prozesses.", + "ai_settings.mcp_server.argument_hints.business.headless.value_hint": "Meist ein Schalterargument; prüfe, ob ein zusätzlicher Wert nötig ist.", + "ai_settings.mcp_server.argument_hints.business.executable_path.label": "Browser- oder Programm-Pfad", + "ai_settings.mcp_server.argument_hints.business.executable_path.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.executable_path.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.repo.label": "Repository-Pfad", + "ai_settings.mcp_server.argument_hints.business.repo.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.repo.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.inferred_path.label": "Pfad / Konfiguration", + "ai_settings.mcp_server.argument_hints.business.inferred_path.detail": "Verweist auf lokale Dateien, Verzeichnisse, Repositories oder Programme für den MCP-Dienst.", + "ai_settings.mcp_server.argument_hints.business.inferred_path.value_hint": "Nutze einen absoluten lokalen Pfad und den kleinstmöglichen Umfang.", + "ai_settings.mcp_server.argument_hints.business.inferred_endpoint.label": "Adresse / Endpunkt", + "ai_settings.mcp_server.argument_hints.business.inferred_endpoint.detail": "Gibt eine Serviceadresse oder einen API-Einstiegspunkt für den MCP-Dienst an.", + "ai_settings.mcp_server.argument_hints.business.inferred_endpoint.value_hint": "Gib die vollständige Adresse ein, ohne Geheimnisse in die URL einzubauen.", + "ai_settings.mcp_server.argument_hints.business.inferred_mode.label": "Modusargument", + "ai_settings.mcp_server.argument_hints.business.inferred_mode.detail": "Steuert Transport, Profil, Berechtigungen oder Laufmodus des MCP-Dienstes.", + "ai_settings.mcp_server.argument_hints.business.inferred_mode.value_hint": "Nutze Enum-Wert oder Schaltersemantik aus dem README.", + "ai_settings.mcp_server.argument_hints.detail.stdio.label": "stdio-Kommunikationsmodus", + "ai_settings.mcp_server.argument_hints.detail.stdio.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.stdio.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.skip_install_confirm.label": "Installationsbestätigung überspringen", + "ai_settings.mcp_server.argument_hints.detail.skip_install_confirm.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.skip_install_confirm.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.python_module_flag.label": "Python-Modulstart", + "ai_settings.mcp_server.argument_hints.detail.python_module_flag.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.python_module_flag.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.docker_run.label": "Docker run-Unterbefehl", + "ai_settings.mcp_server.argument_hints.detail.docker_run.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.docker_run.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.docker_interactive.label": "Standardeingabe offen halten", + "ai_settings.mcp_server.argument_hints.detail.docker_interactive.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.docker_interactive.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.docker_cleanup.label": "Container nach Ende aufräumen", + "ai_settings.mcp_server.argument_hints.detail.docker_cleanup.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.docker_cleanup.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.docker_image_or_arg.label": "Docker-Image oder Containerargument", + "ai_settings.mcp_server.argument_hints.detail.docker_image_or_arg.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.docker_image_or_arg.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.npm_package_or_arg.label": "MCP-Paket oder Positionsargument", + "ai_settings.mcp_server.argument_hints.detail.npm_package_or_arg.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.npm_package_or_arg.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.uvx_package_or_arg.label": "Python-MCP-Paket oder Positionsargument", + "ai_settings.mcp_server.argument_hints.detail.uvx_package_or_arg.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.uvx_package_or_arg.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.script_or_arg.label": "Skript oder Positionsargument", + "ai_settings.mcp_server.argument_hints.detail.script_or_arg.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.script_or_arg.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.python_module_name.label": "Python-Modulname", + "ai_settings.mcp_server.argument_hints.detail.python_module_name.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.python_module_name.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.python_script_or_arg.label": "Python-Skript oder Positionsargument", + "ai_settings.mcp_server.argument_hints.detail.python_script_or_arg.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.python_script_or_arg.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_settings.mcp_server.argument_hints.detail.positional.label": "Positionsargument", + "ai_settings.mcp_server.argument_hints.detail.positional.detail": "Dieser Wert wird als Startargument an den MCP-Prozess übergeben. Prüfe README und aktuelle Reihenfolge.", + "ai_settings.mcp_server.argument_hints.detail.positional.value_hint": "Trage einen Wert ein, der zum README-Beispiel passt.", + "ai_chat.builtin_tools.database.execute_sql.desc": "Execute a SQL query and return results", + "ai_chat.builtin_tools.database.execute_sql.detail": "Pass connectionId, dbName, and sql, then execute SQL on the target database and return results (up to 50 rows). Controlled by safety level; read-only mode only allows SELECT/SHOW/DESCRIBE.", + "ai_chat.builtin_tools.database.execute_sql.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.execute_sql.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.execute_sql.parameters.sql.description": "SQL statement to execute", + "ai_chat.builtin_tools.database.execute_sql.params": "connectionId, dbName, sql", + "ai_chat.builtin_tools.database.execute_sql.tool_description": "Execute SQL on the specified connection and database and return results. Controlled by safety level; read-only mode only allows query operations such as SELECT/SHOW/DESCRIBE. Results return at most 50 rows.", + "ai_chat.builtin_tools.database.get_all_columns.desc": "Get field summaries for all tables in a database", + "ai_chat.builtin_tools.database.get_all_columns.detail": "Pass connectionId and dbName, then return a cross-table field list including table name, field name, type, and comment. Useful when the user knows a business field but not which table contains it.", + "ai_chat.builtin_tools.database.get_all_columns.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.get_all_columns.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.get_all_columns.params": "connectionId, dbName", + "ai_chat.builtin_tools.database.get_all_columns.tool_description": "Get field summaries for all tables in the specified database, returning table names, field names, types, and comments. Use it for field-to-table lookup, cross-table field comparison, and data map exploration.", + "ai_chat.builtin_tools.database.get_columns.desc": "Get the field structure of a specified table", + "ai_chat.builtin_tools.database.get_columns.detail": "Pass connectionId, dbName, and tableName, then return each field's name, type, nullability, default value, and comment. AI must call this before generating SQL to confirm real field names.", + "ai_chat.builtin_tools.database.get_columns.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.get_columns.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.get_columns.parameters.tableName.description": "Table name", + "ai_chat.builtin_tools.database.get_columns.params": "connectionId, dbName, tableName", + "ai_chat.builtin_tools.database.get_columns.tool_description": "Get the field list of the specified table, including field name, type, nullability, default value, comment, and related metadata. Before generating SQL, call this tool to confirm real field names and do not guess field names.", + "ai_chat.builtin_tools.database.get_connections.desc": "Get all available database connections", + "ai_chat.builtin_tools.database.get_connections.detail": "Returns connection ID, name, type (such as MySQL or PostgreSQL), and Host address. AI uses the returned data to decide which connection to explore first.", + "ai_chat.builtin_tools.database.get_connections.params": "No parameters", + "ai_chat.builtin_tools.database.get_connections.tool_description": "When database querying or operations are needed but the user has not selected any connection context, get all database connections available in the current app. Returned data includes connection ID (id) and name (name).", + "ai_chat.builtin_tools.database.get_databases.desc": "Get all databases under a specified connection", + "ai_chat.builtin_tools.database.get_databases.detail": "Pass connectionId and return the database or Schema name list under that connection.", + "ai_chat.builtin_tools.database.get_databases.parameters.connectionId.description": "Connection ID (from get_connections)", + "ai_chat.builtin_tools.database.get_databases.params": "connectionId: connection ID", + "ai_chat.builtin_tools.database.get_databases.tool_description": "Get all database (Database/Schema) names under the specified connectionId.", + "ai_chat.builtin_tools.database.get_foreign_keys.desc": "Get foreign-key relationships for a specified table", + "ai_chat.builtin_tools.database.get_foreign_keys.detail": "Pass connectionId, dbName, and tableName, then return foreign-key mappings from the current table to other tables. AI can use it directly for relationship inference, join SQL generation, and data consistency review.", + "ai_chat.builtin_tools.database.get_foreign_keys.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.get_foreign_keys.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.get_foreign_keys.parameters.tableName.description": "Table name", + "ai_chat.builtin_tools.database.get_foreign_keys.params": "connectionId, dbName, tableName", + "ai_chat.builtin_tools.database.get_foreign_keys.tool_description": "Get foreign-key relationships for the specified table, including local fields, referenced table, referenced fields, and constraint names. Use it for join-path analysis, ER relationship mapping, and constraint checks.", + "ai_chat.builtin_tools.database.get_indexes.desc": "Get index definitions for a specified table", + "ai_chat.builtin_tools.database.get_indexes.detail": "Pass connectionId, dbName, and tableName, then return index name, index columns, uniqueness, and index type. AI should prefer this for slow SQL analysis, index optimization, and execution-plan inference.", + "ai_chat.builtin_tools.database.get_indexes.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.get_indexes.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.get_indexes.parameters.tableName.description": "Table name", + "ai_chat.builtin_tools.database.get_indexes.params": "connectionId, dbName, tableName", + "ai_chat.builtin_tools.database.get_indexes.tool_description": "Get index definitions for the specified table, including index name, column order, uniqueness, and index type. Use it for slow SQL analysis, index optimization suggestions, and confirming existing index coverage.", + "ai_chat.builtin_tools.database.get_table_ddl.desc": "Get the table creation statement (DDL)", + "ai_chat.builtin_tools.database.get_table_ddl.detail": "Pass connectionId, dbName, and tableName, then return the complete CREATE TABLE statement, including field definitions, indexes, constraints, and related structure details.", + "ai_chat.builtin_tools.database.get_table_ddl.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.get_table_ddl.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.get_table_ddl.parameters.tableName.description": "Table name", + "ai_chat.builtin_tools.database.get_table_ddl.params": "connectionId, dbName, tableName", + "ai_chat.builtin_tools.database.get_table_ddl.tool_description": "Get the complete table creation statement (CREATE TABLE DDL) for the specified table, including fields, indexes, constraints, and complete structure information.", + "ai_chat.builtin_tools.database.get_tables.desc": "Get all table names under a specified database", + "ai_chat.builtin_tools.database.get_tables.detail": "Pass connectionId and dbName, then return a table name list. AI uses it to locate the target table mentioned by the user.", + "ai_chat.builtin_tools.database.get_tables.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.get_tables.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.get_tables.params": "connectionId, dbName", + "ai_chat.builtin_tools.database.get_tables.tool_description": "After the target connection and database name are known, if the user asks about a table or implicitly mentions one but the exact table name is unknown, call this tool to get all table names in that database (table names only) and infer the target table.", + "ai_chat.builtin_tools.database.get_triggers.desc": "Get trigger definitions for a specified table", + "ai_chat.builtin_tools.database.get_triggers.detail": "Pass connectionId, dbName, and tableName, then return trigger name, timing, event type, and statement body. AI can inspect it directly when analyzing implicit writes, side effects, and audit logic.", + "ai_chat.builtin_tools.database.get_triggers.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.get_triggers.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.get_triggers.parameters.tableName.description": "Table name", + "ai_chat.builtin_tools.database.get_triggers.params": "connectionId, dbName, tableName", + "ai_chat.builtin_tools.database.get_triggers.tool_description": "Get trigger definitions for the specified table, including timing, event, and trigger statement. Use it to investigate implicit data changes, audit logic, and table-level side effects.", + "ai_chat.builtin_tools.database.inspect_database_bundle.desc": "Capture a structure overview for a specified database", + "ai_chat.builtin_tools.database.inspect_database_bundle.detail": "Pass connectionId and dbName, then return table list, table count, total field count, and per-table field summary preview. Useful for first-pass exploration of an unfamiliar database before drilling into target tables.", + "ai_chat.builtin_tools.database.inspect_database_bundle.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.inspect_database_bundle.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.inspect_database_bundle.parameters.includeColumns.description": "Optional. Whether to include per-table field summaries. Default true.", + "ai_chat.builtin_tools.database.inspect_database_bundle.parameters.perTableColumnLimit.description": "Optional. Maximum field summaries per table. Default 8, maximum 30.", + "ai_chat.builtin_tools.database.inspect_database_bundle.parameters.tableLimit.description": "Optional. Maximum tables to return. Default 80, maximum 200.", + "ai_chat.builtin_tools.database.inspect_database_bundle.params": "connectionId, dbName, includeColumns?, tableLimit?, perTableColumnLimit?", + "ai_chat.builtin_tools.database.inspect_database_bundle.tool_description": "Get a structure overview for the specified database, returning table name list, total field count, and per-table field summary preview. Use it for unfamiliar database exploration, data mapping, and quickly choosing the next table to analyze deeply.", + "ai_chat.builtin_tools.database.inspect_table_bundle.desc": "Capture a structure snapshot for a specified table", + "ai_chat.builtin_tools.database.inspect_table_bundle.detail": "Pass connectionId, dbName, and tableName, then return columns, indexes, foreign keys, triggers, and DDL; sample rows can also be included. Useful before writing SQL, reviewing table design, or investigating side effects.", + "ai_chat.builtin_tools.database.inspect_table_bundle.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.inspect_table_bundle.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.inspect_table_bundle.parameters.includeSampleRows.description": "Optional. Whether to include sample rows.", + "ai_chat.builtin_tools.database.inspect_table_bundle.parameters.sampleLimit.description": "Optional. Sample row count. Default 10, maximum 100.", + "ai_chat.builtin_tools.database.inspect_table_bundle.parameters.tableName.description": "Table name", + "ai_chat.builtin_tools.database.inspect_table_bundle.params": "connectionId, dbName, tableName, includeSampleRows?, sampleLimit?", + "ai_chat.builtin_tools.database.inspect_table_bundle.tool_description": "Get a complete structure snapshot for the specified table, returning columns, indexes, foreign keys, triggers, DDL, and optional sample rows. Use it for full table-design exploration, quickly understanding table relationships, and reducing repeated round trips.", + "ai_chat.builtin_tools.database.preview_table_rows.desc": "Preview the first rows of a specified table", + "ai_chat.builtin_tools.database.preview_table_rows.detail": "Pass connectionId, dbName, tableName, and optional limit, then return real sample rows from the table. Use it to inspect data shape, null distribution, and enum values before deciding how to write SQL.", + "ai_chat.builtin_tools.database.preview_table_rows.parameters.connectionId.description": "Connection ID", + "ai_chat.builtin_tools.database.preview_table_rows.parameters.dbName.description": "Database name", + "ai_chat.builtin_tools.database.preview_table_rows.parameters.limit.description": "Optional. Preview row count. Default 20, maximum 100.", + "ai_chat.builtin_tools.database.preview_table_rows.parameters.tableName.description": "Table name", + "ai_chat.builtin_tools.database.preview_table_rows.params": "connectionId, dbName, tableName, limit?", + "ai_chat.builtin_tools.database.preview_table_rows.tool_description": "Preview sample rows from the specified table. Use it to quickly understand field value shapes, nulls, time formats, and status enums, reducing blind SQL generation by the model.", + "ai_chat.builtin_tools.flows.active_tab.description": "Read the current editor SQL draft or table tab before field checks, index analysis, and read-only verification.", + "ai_chat.builtin_tools.flows.active_tab.title": "Read the current tab", + "ai_chat.builtin_tools.flows.ai_context.description": "Confirm which table structures are attached to the current conversation before field checks, table design review, or SQL generation.", + "ai_chat.builtin_tools.flows.ai_context.title": "Inspect current AI context", + "ai_chat.builtin_tools.flows.ai_runtime.description": "Confirm the current model, safety level, context level, Skills, and MCP tools before choosing a probe chain.", + "ai_chat.builtin_tools.flows.ai_runtime.title": "Inspect current AI capabilities", + "ai_chat.builtin_tools.flows.ai_sessions.description": "Locate previous AI sessions, first user questions, and recent replies before reusing the current tab or historical SQL context.", + "ai_chat.builtin_tools.flows.ai_sessions.title": "Review AI chat history", + "ai_chat.builtin_tools.flows.ai_setup_health.description": "Get an AI configuration health snapshot first, then decide whether to drill into providers, chat readiness, MCP, prompts, Skills, or context.", + "ai_chat.builtin_tools.flows.ai_setup_health.title": "One-shot AI setup health check", + "ai_chat.builtin_tools.flows.app_health_overview.description": "Use when AI instability, connection issues, MCP issues, or message rendering problems overlap and an overall health snapshot is needed first.", + "ai_chat.builtin_tools.flows.app_health_overview.title": "AI app health overview", + "ai_chat.builtin_tools.flows.app_logs.description": "Review ERROR/WARN lines from the gonavi.log tail, then combine MCP, connection, and current data source state for diagnosis.", + "ai_chat.builtin_tools.flows.app_logs.title": "Troubleshoot application logs", + "ai_chat.builtin_tools.flows.chat_readiness.description": "Check which chat input prerequisites are missing, such as active provider, key, endpoint, or selected model, instead of guessing from UI symptoms.", + "ai_chat.builtin_tools.flows.chat_readiness.title": "Troubleshoot chat send readiness", + "ai_chat.builtin_tools.flows.choose_tool_route.description": "Use keywords to decide which built-in probes to call, how to fill tool arguments, and whether external MCP tools are available.", + "ai_chat.builtin_tools.flows.choose_tool_route.title": "Choose an AI tool route", + "ai_chat.builtin_tools.flows.codebase_hotspots.description": "Use before splitting thousand-line components, choosing the next refactor slice, or changing UI/AI/MCP code to inspect split hotspots, risk, and validation scope.", + "ai_chat.builtin_tools.flows.codebase_hotspots.title": "Govern large frontend files", + "ai_chat.builtin_tools.flows.connection_capabilities.description": "Check whether the current connection supports database creation/deletion, result editing, SQL export, or approximate counts.", + "ai_chat.builtin_tools.flows.connection_capabilities.title": "Check data-source capability boundaries", + "ai_chat.builtin_tools.flows.connection_failures.description": "When connection failures, cooldown, or validation failures appear, get structured root cause, latest address, and next actions first.", + "ai_chat.builtin_tools.flows.connection_failures.title": "Troubleshoot connection failures and cooldown", + "ai_chat.builtin_tools.flows.context_budget.description": "When AI slows down, answers poorly, or context is too large, inspect messages, DDL, MCP schema, prompts, and Skills before narrowing context.", + "ai_chat.builtin_tools.flows.context_budget.title": "Diagnose AI context size", + "ai_chat.builtin_tools.flows.current_connection.description": "Confirm the active data source type, address, current database, and SSH/proxy status before database exploration or connection troubleshooting.", + "ai_chat.builtin_tools.flows.current_connection.title": "Inspect current connection", + "ai_chat.builtin_tools.flows.database_overview.description": "Start by seeing which tables exist and what fields they roughly contain, then drill into target tables with snapshots.", + "ai_chat.builtin_tools.flows.database_overview.title": "Quick database overview", + "ai_chat.builtin_tools.flows.deep_structure.description": "Use for index optimization, relationship mapping, implicit side-effect investigation, and DDL review.", + "ai_chat.builtin_tools.flows.deep_structure.title": "Deep-dive structure", + "ai_chat.builtin_tools.flows.docker_mcp.description": "Use when Docker README setup discovers 0 tools, containers exit immediately, or docker run arguments may be split incorrectly.", + "ai_chat.builtin_tools.flows.docker_mcp.title": "Troubleshoot Docker MCP startup", + "ai_chat.builtin_tools.flows.external_sql_dirs.description": "Confirm configured external SQL directories, their connection/database bindings, and where an opened SQL file comes from before analyzing scripts.", + "ai_chat.builtin_tools.flows.external_sql_dirs.title": "Inventory external SQL directories", + "ai_chat.builtin_tools.flows.external_sql_file.description": "Locate a script path, read SQL file content from the directory, and combine it with the active tab draft if already opened.", + "ai_chat.builtin_tools.flows.external_sql_file.title": "Read external SQL files", + "ai_chat.builtin_tools.flows.field_lookup_table.description": "Use when only a field name, business meaning, or comment keyword is known, but the exact table is still unclear.", + "ai_chat.builtin_tools.flows.field_lookup_table.title": "Find tables by field", + "ai_chat.builtin_tools.flows.locate_table_fields.description": "Find the connection, database, and table first, then confirm real field names before generating SQL.", + "ai_chat.builtin_tools.flows.locate_table_fields.title": "Locate tables and fields", + "ai_chat.builtin_tools.flows.mcp_authoring.description": "Read real field descriptions, templates, and full-command splitting rules before validating pasted commands or drafts.", + "ai_chat.builtin_tools.flows.mcp_authoring.title": "New MCP authoring guide", + "ai_chat.builtin_tools.flows.mcp_setup.description": "Confirm configured and enabled MCP services and external client write status, then use MCP runtime failure logs to explain missing tools.", + "ai_chat.builtin_tools.flows.mcp_setup.title": "Troubleshoot MCP access status", + "ai_chat.builtin_tools.flows.mcp_tool_parameters.description": "Find the real discovered MCP tool alias first, then read inputSchema, required fields, enums, and nested parameter paths.", + "ai_chat.builtin_tools.flows.mcp_tool_parameters.title": "Inspect MCP tool parameters", + "ai_chat.builtin_tools.flows.message_flow.description": "Read the real current-session message structure and anomaly signals when replies split into bubbles, tool calls do not close, or flow state looks wrong.", + "ai_chat.builtin_tools.flows.message_flow.title": "Diagnose AI message flow", + "ai_chat.builtin_tools.flows.prompts_skills.description": "Confirm current custom prompts, enabled Skills, dependency tools, and effective scope before explaining current AI behavior.", + "ai_chat.builtin_tools.flows.prompts_skills.title": "Inspect current prompts and Skills", + "ai_chat.builtin_tools.flows.providers_models.description": "Confirm which providers are configured and active, whether keys or models are missing, and why chat cannot send or model lists are empty.", + "ai_chat.builtin_tools.flows.providers_models.title": "Troubleshoot providers and models", + "ai_chat.builtin_tools.flows.readonly_validation.description": "After generating SQL, validate results on a small scope while still respecting the AI safety level.", + "ai_chat.builtin_tools.flows.readonly_validation.title": "Read-only validation", + "ai_chat.builtin_tools.flows.recent_sql_activity.description": "Check whether recent activity is mostly read or write, whether DDL or deletes occurred, and which database has the most recent errors.", + "ai_chat.builtin_tools.flows.recent_sql_activity.title": "Summarize recent SQL activity", + "ai_chat.builtin_tools.flows.recent_sql_logs.description": "Trace recently failed SQL, slow query duration, or let AI explain and optimize based on real execution history.", + "ai_chat.builtin_tools.flows.recent_sql_logs.title": "Review recent execution records", + "ai_chat.builtin_tools.flows.redis_topology.description": "Use for Redis Sentinel, Cluster, multi-node, DB switch failures, or SSH tunnel issues to get status, redacted URI, adapter, DB semantics, and next actions.", + "ai_chat.builtin_tools.flows.redis_topology.title": "Diagnose Redis topology", + "ai_chat.builtin_tools.flows.remote_agent_mcp.description": "Use when OpenClaw/Hermans run on cloud Linux while database connections and passwords stay on the Windows GoNavi machine.", + "ai_chat.builtin_tools.flows.remote_agent_mcp.title": "Connect remote Agents to GoNavi MCP", + "ai_chat.builtin_tools.flows.render_error.description": "Use when an AI message is blank or a bubble fails locally while the panel stays alive; read the isolated render-error snapshot first.", + "ai_chat.builtin_tools.flows.render_error.title": "Troubleshoot AI bubble render errors", + "ai_chat.builtin_tools.flows.safety_boundary.description": "Check whether the current state is read-only, whether DDL/DML is allowed, and whether MCP writes require allowMutating.", + "ai_chat.builtin_tools.flows.safety_boundary.title": "Check write safety boundaries", + "ai_chat.builtin_tools.flows.sample_data.description": "Confirm fields first, then inspect the first real sample rows and null patterns.", + "ai_chat.builtin_tools.flows.sample_data.title": "Understand sample data", + "ai_chat.builtin_tools.flows.saved_connections.description": "Filter locally saved data sources by keyword or type, then inspect the chosen connection state or database structure.", + "ai_chat.builtin_tools.flows.saved_connections.title": "Inventory local connection assets", + "ai_chat.builtin_tools.flows.saved_queries.description": "Find locally saved query scripts first, then check fields and run read-only validation instead of rewriting old SQL manually.", + "ai_chat.builtin_tools.flows.saved_queries.title": "Reuse saved SQL", + "ai_chat.builtin_tools.flows.shortcuts.description": "Confirm current Win/Mac shortcuts, customizations, and how to trigger result panel, AI panel, query execution, and related actions.", + "ai_chat.builtin_tools.flows.shortcuts.title": "Inspect current shortcut configuration", + "ai_chat.builtin_tools.flows.sql_editor_transaction.description": "Confirm whether SQL editor DML enters a managed transaction, current commit mode, pending transactions, and commit semantics after update/insert/delete.", + "ai_chat.builtin_tools.flows.sql_editor_transaction.title": "Check SQL editor transactions", + "ai_chat.builtin_tools.flows.sql_risk.description": "Before execution, deletion, update, DDL, or batch SQL, check statement count, write/DDL risk, WHERE clauses, and current safety policy.", + "ai_chat.builtin_tools.flows.sql_risk.title": "Pre-check SQL risk", + "ai_chat.builtin_tools.flows.sql_snippets.description": "Find team SQL snippet templates, completion prefixes, and common skeletons before deciding whether to rewrite.", + "ai_chat.builtin_tools.flows.sql_snippets.title": "Find SQL snippet templates", + "ai_chat.builtin_tools.flows.support_bundle.description": "Use when troubleshooting evidence needs to be collected at once, without secrets or database passwords.", + "ai_chat.builtin_tools.flows.support_bundle.title": "Export AI troubleshooting support bundle", + "ai_chat.builtin_tools.flows.table_snapshot.description": "Return columns, indexes, foreign keys, triggers, and DDL in one call; sample rows can be included when needed to reduce round trips.", + "ai_chat.builtin_tools.flows.table_snapshot.title": "One-shot table snapshot", + "ai_chat.builtin_tools.flows.upstream_request.description": "Read redacted gonavi.log request records when the user needs upstream payloads, requestId, status codes, latency, or request body previews.", + "ai_chat.builtin_tools.flows.upstream_request.title": "Trace AI upstream requests", + "ai_chat.builtin_tools.flows.workspace_tabs.description": "See which SQL, table, or command tabs are open, then inspect the target tab for field checks, comparisons, and read-only validation.", + "ai_chat.builtin_tools.flows.workspace_tabs.title": "Inventory the current workspace", + "ai_chat.inspection.tool_info.inspect_mcp_setup.desc": "Aktuelle MCP-Konfiguration und externen Zugriff prüfen", + "ai_chat.inspection.tool_info.inspect_mcp_setup.detail": "Gibt lokale MCP-Dienste, Aktivierungsstatus, Startbefehle, Claude Code / Codex Schreibstatus, OpenClaw / Hermans Remote-Agent-Grenzen und Befehlserkennung zurück. Für konfigurierte MCP-Dienste, externe Clientfehler oder Schreibstatus der Client-Konfiguration.", + "ai_chat.inspection.tool_info.inspect_mcp_setup.params": "Keine Parameter", + "ai_chat.inspection.tool_info.inspect_mcp_setup.tool_description": "Liest den lokalen MCP-Konfigurations-Snapshot mit Dienstliste, Aktivierungsstatus, Startbefehlen, Umgebungsvariablenschlüsseln, entdeckten Tools, GoNavi MCP Client-Schreibstatus, lokaler CLI-Erkennung und Remote-Agent-Zugriffsgrenzen.", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.desc": "Remote-MCP-Zugriff für OpenClaw/Hermans prüfen", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.detail": "Gibt GoNavi Streamable HTTP MCP Startbefehle, Remote-URL, Authentifizierungshinweise, OpenClaw/Hermans Cloud-Agent-Grenzen, Bridge-Optionen und Sicherheitshinweise zurück. Für Cloud OpenClaw Zugriff auf Windows GoNavi, lokale Datenbankpasswörter oder HTTP MCP Bereitstellung.", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.params": "publicUrl?, localAddr?, path?, exposeStrategy?, tokenConfigured?", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.tool_description": "Liest den GoNavi MCP Remote-Agent-Zugriffssnapshot mit Streamable HTTP Startbefehlen, /mcp URL, Bearer Token Anforderungen, OpenClaw/Hermans Cloud-Zugriffsschritten, Passwortgrenze auf dem Windows-Host und Risiken für tunnel, reverse proxy, Tailscale und ähnliche Strategien.", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.param.publicUrl": "Optional. HTTPS- oder privates Netzwerk-URL, das der Remote-Agent erreichen kann. Fehlt /mcp, hängt das Tool den konfigurierten path an.", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.param.localAddr": "Optional. Lokale Windows-HTTP-MCP-Listen-Adresse. Standard 127.0.0.1:8765. Direktes Binden an 0.0.0.0 wird nicht empfohlen.", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.param.path": "Optional. Streamable HTTP MCP path. Standard /mcp.", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.param.exposeStrategy": "Optional. Geplante Remote-Bereitstellungsstrategie, um passende Risikohinweise zurückzugeben.", + "ai_chat.inspection.tool_info.inspect_mcp_remote_access.param.tokenConfigured": "Optional. Ob bereits ein zufälliger Bearer Token konfiguriert ist. Bei false wird eine Authentifizierungswarnung zurückgegeben.", + "ai_chat.inspection.tool_info.inspect_mcp_runtime_failures.desc": "MCP-Start- und Tool-Aufruffehler diagnostizieren", + "ai_chat.inspection.tool_info.inspect_mcp_runtime_failures.detail": "Liest aktuelle MCP-Start-, Tool-Erkennungs-, Tool-Aufruf- und HTTP-MCP-Subprozessfehler aus gonavi.log und kombiniert gespeicherte Dienste sowie entdeckte Tools zu Fehlertypen, wahrscheinlichen Ursachen, betroffenen Diensten und nächsten Reparaturschritten.", + "ai_chat.inspection.tool_info.inspect_mcp_runtime_failures.params": "serverName?, keyword?, lineLimit?(Standard 160), includeLines?(Standard false)", + "ai_chat.inspection.tool_info.inspect_mcp_runtime_failures.tool_description": "Liest MCP-Laufzeitfehlersignale aus GoNavi-Anwendungslogs, klassifiziert Dienststart, Tool-Erkennung, Tool-Aufruf und HTTP-MCP-Subprozessbeendigungen und kombiniert aktuelle Konfiguration sowie Tool-Anzahlen zu wahrscheinlichen Ursachen und nextActions.", + "ai_chat.inspection.tool_info.inspect_mcp_runtime_failures.param.serverName": "Optional. Nur einen MCP-Dienstnamen oder server= Namen aus Logs prüfen, etwa GitHub, Browser oder DockerFetch.", + "ai_chat.inspection.tool_info.inspect_mcp_runtime_failures.param.keyword": "Optional. MCP-bezogene Logs nach keyword filtern, etwa timeout, stdio, permission, 401 oder docker.", + "ai_chat.inspection.tool_info.inspect_mcp_runtime_failures.param.lineLimit": "Optional. Maximale Anzahl zu lesender Tail-Logzeilen. Standard 160, Maximum 200.", + "ai_chat.inspection.tool_info.inspect_mcp_runtime_failures.param.includeLines": "Optional. Ob redigierte rohe MCP-Logzeilen enthalten sein sollen. Standard false; nur aktivieren, wenn Originalzeilen zitiert werden müssen.", + "ai_chat.inspection.tool_info.inspect_mcp_authoring_guide.desc": "add-MCP Erstellungsleitfaden prüfen", + "ai_chat.inspection.tool_info.inspect_mcp_authoring_guide.detail": "Gibt Zweck der add-MCP Felder, empfohlene Ausfüllreihenfolge, automatische Aufteilung vollständiger Befehle und npx / Node / uvx / Python / Docker / EXE Vorlagen zurück. Vor Antworten zu command, args, env, Vorlagen oder vollständigen Startbefehlen verwenden.", + "ai_chat.inspection.tool_info.inspect_mcp_authoring_guide.params": "Keine Parameter", + "ai_chat.inspection.tool_info.inspect_mcp_authoring_guide.tool_description": "Liest den integrierten MCP-Erstellungsleitfaden von GoNavi mit empfohlener Feldreihenfolge, Feldzweck, gängigen Befehlsbeispielen, automatischer Aufteilung vollständiger Befehle und npx / Node / uvx / Python / Docker / EXE Vorlagenbeispielen.", + "ai_chat.inspection.tool_info.inspect_mcp_docker_setup.desc": "Docker MCP Startkonfiguration prüfen", + "ai_chat.inspection.tool_info.inspect_mcp_docker_setup.detail": "Liest gespeicherte Docker MCP-Dienste, prüft, ob command und args korrekt in docker, run, --rm, -i, Imagenamen und Containerargumente aufgeteilt sind, und gibt fehlende Argumente, Tool-Anzahl, timeout Hinweise und nächste Reparaturschritte zurück.", + "ai_chat.inspection.tool_info.inspect_mcp_docker_setup.params": "serverId?, includeDisabled?(Standard true)", + "ai_chat.inspection.tool_info.inspect_mcp_docker_setup.tool_description": "Prüft Startargumente gespeicherter Docker MCP-Dienste und gibt docker run/-i/image/--rm/env/timeout Status, entdeckte Tool-Anzahlen, Konfigurationswarnungen und nextActions zurück.", + "ai_chat.inspection.tool_info.inspect_mcp_docker_setup.param.serverId": "Optional. Nur eine MCP serverId prüfen. Wenn ausgelassen, werden alle Docker MCP-Dienste geprüft.", + "ai_chat.inspection.tool_info.inspect_mcp_docker_setup.param.includeDisabled": "Optional. Ob deaktivierte Docker MCP-Dienste enthalten sein sollen. Standard true.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.desc": "add-MCP Entwurf validieren", + "ai_chat.inspection.tool_info.inspect_mcp_draft.detail": "Simuliert GoNavi add-MCP Konfiguration aus einem vollständigen Startbefehl oder feldweisem Entwurf und gibt automatische Aufteilung, Startvorschau, anwendbaren Entwurf, Argument- und Umgebungshinweise, Validierungsprobleme, empfohlene Vorlagen und nächste Reparaturvorschläge zurück. Sensible Argumente werden redigiert.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.params": "fullCommand?, command?, args?, envText?, timeoutSeconds?, templateKey?, name?", + "ai_chat.inspection.tool_info.inspect_mcp_draft.tool_description": "Validiert einen ausstehenden MCP-Dienstentwurf. Unterstützt automatische Aufteilung für fullCommand/rawCommand/commandLine oder feldweise Validierung für command, args, envText, timeoutSeconds und templateKey. Gibt geparste Felder, redigierte Startvorschau, suggestedServerSeed, Argumenthinweise, Umgebungsrisiken, errors, warnings, empfohlene Vorlagen und nextActions zurück, ohne api-key/token/password Werte auszugeben.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.param.fullCommand": "Optional. Vollständiger MCP-Startbefehl aus README oder vom Benutzer, etwa $env:GITHUB_TOKEN=...; uvx mcp-server-github --stdio.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.param.command": "Optional. Start-command in einem feldweisen Entwurf. Sollte nur npx, node, uvx, python oder ein exe path sein.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.param.args": "Optional. Befehlsargumente in einem feldweisen Entwurf. Arrays sind genauer, kommagetrennte oder zeilengetrennte Zeichenfolgen werden aber ebenfalls akzeptiert.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.param.envText": "Optional. Umgebungsvariablenentwurf, ein KEY=VALUE pro Zeile. Keine export, set oder $env: Präfixe übergeben.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.param.timeoutSeconds": "Optional. Timeout-Sekunden für eine Tool-Erkennung oder einen Aufruf. Empfohlen 20; langsam startende Dienste können 45 oder 60 verwenden.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.param.templateKey": "Optional. Zuerst eine integrierte Vorlage anwenden und dann mit benutzerseitigen Feldern überschreiben.", + "ai_chat.inspection.tool_info.inspect_mcp_draft.param.name": "Optional. MCP-Dienstname, etwa GitHub, Filesystem oder Browser.", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.desc": "Argument-schema eines MCP-Tools prüfen", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.detail": "Liest inputSchema entdeckter MCP-Tools nach alias, serverId oder keyword und gibt Pflichtparameter, Feldtypen, enum Werte, verschachtelte Objektpfade und Hinweise vor dem Aufruf zurück. Nach erfolgreicher MCP-Erkennung zum Prüfen akzeptierter Argumente verwenden.", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.params": "alias?, serverId?, keyword?, includeSchema?(Standard false), limit?(Standard 8)", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.tool_description": "Liest Parameter-schema-Zusammenfassungen entdeckter MCP-Tools, filterbar nach alias, serverId oder keyword, und gibt Pflichtfelder, Typen, enum Werte, verschachtelte Parameterpfade und Hinweise vor dem Aufruf zurück. Vor dem Schreiben von arguments JSON für externe MCP-Tool-Aufrufe oder nach Parameterfehlern verwenden.", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.param.alias": "Optional. Nach exaktem MCP tool alias abfragen, etwa github_create_issue. Bevorzugt zuerst den echten alias aus inspect_mcp_setup lesen.", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.param.serverId": "Optional. Nur Tools prüfen, die unter einer MCP serverId entdeckt wurden.", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.param.keyword": "Optional. Nach Tool-alias, Originalname, title, description oder Dienstname filtern.", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.param.includeSchema": "Optional. Ob das vollständige raw inputSchema enthalten sein soll. Standard false; nur für komplexe verschachtelte schema-Prüfung aktivieren.", + "ai_chat.inspection.tool_info.inspect_mcp_tool_schema.param.limit": "Optional. Maximale Anzahl zurückzugebender passender Tools. Standard 8, Maximum 30.", + "ai_chat.inspection.tool_info.inspect_app_logs.desc": "GoNavi-Anwendungslog-Ende prüfen", + "ai_chat.inspection.tool_info.inspect_app_logs.detail": "Filtert aktuelle INFO/WARN/ERROR-Zeilen im GoNavi-Anwendungslog optional per keyword und gibt Level-Verteilung, Logdateipfad und Kürzungsstatus zurück. Zuerst verwenden, wenn gonavi.log, Startfehler, MCP-Startfehler oder Datenbankverbindungsfehler erwähnt werden.", + "ai_chat.inspection.tool_info.inspect_app_logs.params": "keyword?, lineLimit?(Standard 80)", + "ai_chat.inspection.tool_info.inspect_app_logs.tool_description": "Liest das Ende des GoNavi-Anwendungslogs, optional nach keyword gefiltert, und gibt aktuelle Logzeilen, Level-Verteilung, Logpfad und Kürzungsstatus zurück.", + "ai_chat.inspection.tool_info.inspect_app_logs.param.keyword": "Optional. Loginhalt nach keyword filtern, z. B. mcp, mysql, timeout oder error.", + "ai_chat.inspection.tool_info.inspect_app_logs.param.lineLimit": "Optional. Maximale Anzahl zurückzugebender Logzeilen. Standard 80, Maximum 200.", + "ai_chat.inspection.tool_info.inspect_recent_connection_failures.desc": "Aktuelle Datenbankverbindungsfehler und Cooldowns zusammenfassen", + "ai_chat.inspection.tool_info.inspect_recent_connection_failures.detail": "Extrahiert Datenbankverbindungsfehler, Validierungsfehler, SSH-Tunnel-Fehler und Cooldown-Treffer aus aktuellen gonavi.log-Zeilen und klassifiziert Hauptproblem, letzte Adresse, letzte Ursache und nächste Schritte.", + "ai_chat.inspection.tool_info.inspect_recent_connection_failures.params": "keyword?, lineLimit?(Standard 120)", + "ai_chat.inspection.tool_info.inspect_recent_connection_failures.tool_description": "Fasst aktuelle Datenbankverbindungsfehler, Validierungsfehler, SSH-Tunnel-Fehler und Cooldown-Treffer aus GoNavi-Anwendungslogs zusammen.", + "ai_chat.inspection.tool_info.inspect_recent_connection_failures.param.keyword": "Optional. Nach Verbindungstyp, Adresse oder Fehler-keyword filtern, z. B. mysql, ssh, timeout oder 127.0.0.1.", + "ai_chat.inspection.tool_info.inspect_recent_connection_failures.param.lineLimit": "Optional. Maximale Anzahl zu analysierender Logzeilen. Standard 120, Maximum 240.", + "ai_chat.inspection.tool_info.inspect_ai_last_render_error.desc": "Letzten AI-Nachrichten-Renderfehler prüfen", + "ai_chat.inspection.tool_info.inspect_ai_last_render_error.detail": "Gibt den letzten isolierten AI-Nachrichten-Renderfehler mit Nachrichtenkennung, Inhaltsvorschau, Fehlerzusammenfassung und Komponentenstack-Zusammenfassung zurück.", + "ai_chat.inspection.tool_info.inspect_ai_last_render_error.params": "Keine Parameter", + "ai_chat.inspection.tool_info.inspect_ai_last_render_error.tool_description": "Liest den letzten lokalen AI-Nachrichten-Renderfehler-Snapshot mit message ID, Rolle, Inhaltsvorschau, Fehlerzusammenfassung, Komponentenstack und nächsten Diagnosehinweisen.", + "ai_chat.inspection.tool_info.inspect_saved_queries.desc": "Lokal gespeicherte SQL-Abfragen prüfen", + "ai_chat.inspection.tool_info.inspect_saved_queries.detail": "Filtert lokal gespeicherte Abfragen nach keyword, Verbindung oder Datenbank und gibt Abfragename, Verbindung, Datenbank und SQL-Vorschau zurück.", + "ai_chat.inspection.tool_info.inspect_saved_queries.params": "keyword?, connectionId?, dbName?, limit?, includeSql?(Standard true)", + "ai_chat.inspection.tool_info.inspect_saved_queries.tool_description": "Liest lokal gespeicherte SQL-Abfragen, optional nach keyword, Verbindung und Datenbank gefiltert, und gibt Name, Verbindung, Datenbank und SQL-Vorschau zurück.", + "ai_chat.inspection.tool_info.inspect_saved_queries.param.keyword": "Optional. Nach Abfragename, SQL-Text, Verbindungsname oder Datenbankname filtern.", + "ai_chat.inspection.tool_info.inspect_saved_queries.param.connectionId": "Optional. Nur gespeicherte Abfragen unter einer Verbindung prüfen.", + "ai_chat.inspection.tool_info.inspect_saved_queries.param.dbName": "Optional. Nur gespeicherte Abfragen unter einer Datenbank prüfen.", + "ai_chat.inspection.tool_info.inspect_saved_queries.param.limit": "Optional. Maximale Anzahl zurückzugebender Abfragen. Standard 12, Maximum 50.", + "ai_chat.inspection.tool_info.inspect_saved_queries.param.includeSql": "Optional. Ob eine SQL-Vorschau enthalten ist. Standard true.", + "ai_chat.inspection.tool_info.inspect_ai_sessions.desc": "Lokalen AI-Unterhaltungsverlauf prüfen", + "ai_chat.inspection.tool_info.inspect_ai_sessions.detail": "Filtert lokale AI-Sitzungen nach keyword und gibt Sitzungstitel, Aktualisierungszeit, Nachrichtenanzahl, aktuellen Status, erste Nutzerfrage und letzte Nachrichtenvorschau zurück.", + "ai_chat.inspection.tool_info.inspect_ai_sessions.params": "keyword?, limit?, includePreview?(Standard true)", + "ai_chat.inspection.tool_info.inspect_ai_sessions.tool_description": "Liest lokalen AI-Unterhaltungsverlauf, optional nach keyword gefiltert, und gibt Sitzungstitel, Aktualisierungszeit, Nachrichtenanzahl, aktuellen Sitzungsstatus, erste Nutzerfrage und letzte Nachrichtenvorschau zurück.", + "ai_chat.inspection.tool_info.inspect_ai_sessions.param.keyword": "Optional. Nach Sitzungstitel, session ID, erster Nutzerfrage oder letztem Nachrichteninhalt filtern.", + "ai_chat.inspection.tool_info.inspect_ai_sessions.param.limit": "Optional. Maximale Anzahl zurückzugebender Sitzungen. Standard 10, Maximum 50.", + "ai_chat.inspection.tool_info.inspect_ai_sessions.param.includePreview": "Optional. Ob erste Nutzerfrage und letzte Nachrichtenvorschau enthalten sind. Standard true.", + "ai_chat.inspection.tool_info.inspect_ai_message_flow.desc": "Aktuellen AI-Nachrichtenfluss diagnostizieren", + "ai_chat.inspection.tool_info.inspect_ai_message_flow.detail": "Liest aktuelle Nachrichten der aktuellen oder angegebenen AI-Sitzung, zählt user/assistant/tool-Nachrichten, prüft Tool-Ergebnisse und erkennt aufeinanderfolgende assistant-Blasen, leere assistant-Platzhalter oder verbliebenes loading.", + "ai_chat.inspection.tool_info.inspect_ai_message_flow.params": "sessionId?(Standard aktuelle Sitzung), limit?(Standard 24), includeContent?(Standard true), previewLimit?(Standard 180)", + "ai_chat.inspection.tool_info.inspect_ai_message_flow.tool_description": "Liest Nachrichtenflussdiagnosen für die aktuelle oder angegebene AI-Sitzung, einschließlich Rollenfolge, assistant/tool-Zahlen, Tool-Call-zu-Result-Matching, aufeinanderfolgende assistant-Nachrichten, leere assistant-Nachrichten und loading-Reste.", + "ai_chat.inspection.tool_info.inspect_ai_message_flow.param.sessionId": "Optional. Zu diagnostizierende AI session ID. Ohne Wert wird die aktuelle aktive Sitzung verwendet.", + "ai_chat.inspection.tool_info.inspect_ai_message_flow.param.limit": "Optional. Maximale Anzahl aktueller Nachrichten. Standard 24, Maximum 80.", + "ai_chat.inspection.tool_info.inspect_ai_message_flow.param.includeContent": "Optional. Ob Nachrichtenvorschauen enthalten sind. Standard true.", + "ai_chat.inspection.tool_info.inspect_ai_message_flow.param.previewLimit": "Optional. Zeichenlimit je Nachrichtenvorschau. Standard 180, Maximum 1000.", + "ai_chat.inspection.tool_info.inspect_ai_context_budget.desc": "AI-Kontextgröße und Stabilitätsrisiko diagnostizieren", + "ai_chat.inspection.tool_info.inspect_ai_context_budget.detail": "Schätzt aktuelle Nachrichten, Tool-Ergebnisse, angehängte Tabellen-DDL, MCP tool schemas, Nutzerprompts und Skills in der aktuellen oder angegebenen AI-Sitzung und gibt low/medium/high/critical-Risiko, Hauptquellen und Eingrenzungsvorschläge zurück.", + "ai_chat.inspection.tool_info.inspect_ai_context_budget.params": "sessionId?(Standard aktuelle Sitzung), messageLimit?(Standard 40), includeDetails?(Standard true)", + "ai_chat.inspection.tool_info.inspect_ai_context_budget.tool_description": "Liest einen Snapshot zu AI-Kontextgröße und Stabilitätsrisiko mit Nachrichtenfenster, Tool-Ergebnislänge, angehängter Tabellen-DDL, MCP tool schemas, Nutzerprompts und aktivierten Skills.", + "ai_chat.inspection.tool_info.inspect_ai_context_budget.param.sessionId": "Optional. Zu diagnostizierende AI session ID. Ohne Wert wird die aktuelle aktive Sitzung verwendet.", + "ai_chat.inspection.tool_info.inspect_ai_context_budget.param.messageLimit": "Optional. Maximale Anzahl aktueller Nachrichten zum Zählen. Standard 40, Maximum 120.", + "ai_chat.inspection.tool_info.inspect_ai_context_budget.param.includeDetails": "Optional. Ob größte Nachricht, größte DDL-Tabelle und größtes MCP schema enthalten sind. Standard true.", + "ai_chat.inspection.tool_info.inspect_codebase_hotspots.desc": "Große Frontend-Dateien und Split-Hotspots prüfen", + "ai_chat.inspection.tool_info.inspect_codebase_hotspots.detail": "Gibt große Frontend-Datei-Hotspots in GoNavi zurück, einschließlich Zeilenzahl, Risikostufe, Split-Reife, Sicherheitsgrenzen, vorgeschlagene Split-Slices und auszuführende Regressionstests.", + "ai_chat.inspection.tool_info.inspect_codebase_hotspots.params": "keyword?, minLines?(Standard 1000), limit?(Standard 8), includeRecommendations?(Standard true)", + "ai_chat.inspection.tool_info.inspect_codebase_hotspots.tool_description": "Liest den GoNavi-Frontend-Snapshot für große Dateien und Split-Hotspots und gibt Dateipfad, Zeilenzahl, Risikostufe, Split-Reife, bevorzugten Slice, sichere Split-Grenze, vorgeschlagene Slices, Testziele und Prüfplan zurück.", + "ai_chat.inspection.tool_info.inspect_codebase_hotspots.param.keyword": "Optional. Nach Pfad, Modul, Risiko, Split-Slice oder Testziel filtern, z. B. Sidebar, DataGrid, Redis, transaction oder connection.", + "ai_chat.inspection.tool_info.inspect_codebase_hotspots.param.minLines": "Optional. Nur Hotspot-Dateien mit mindestens dieser Zeilenzahl zurückgeben. Standard 1000, Maximum 20000.", + "ai_chat.inspection.tool_info.inspect_codebase_hotspots.param.limit": "Optional. Maximale Anzahl zurückzugebender Hotspots. Standard 8, Maximum 30.", + "ai_chat.inspection.tool_info.inspect_codebase_hotspots.param.includeRecommendations": "Optional. Ob suggestedSlices, testTargets und nextActions enthalten sind. Standard true.", + "ai_chat.inspection.tool_info.inspect_sql_snippets.desc": "SQL-Snippet-Vorlagen prüfen", + "ai_chat.inspection.tool_info.inspect_sql_snippets.detail": "Gibt lokale SQL-Snippet-prefix, Name, Beschreibung und Vorlagenvorschau zurück, optional nach keyword gefiltert.", + "ai_chat.inspection.tool_info.inspect_sql_snippets.params": "keyword?, limit?, includeBody?(Standard true)", + "ai_chat.inspection.tool_info.inspect_sql_snippets.tool_description": "Liest lokale SQL-Snippet-Vorlagen, optional nach keyword gefiltert, und gibt prefix, Name, Beschreibung und Vorlagenvorschau zurück.", + "ai_chat.inspection.tool_info.inspect_sql_snippets.param.keyword": "Optional. Nach prefix, Name, Beschreibung oder Vorlageninhalt filtern.", + "ai_chat.inspection.tool_info.inspect_sql_snippets.param.limit": "Optional. Maximale Anzahl zurückzugebender Snippets. Standard 20, Maximum 80.", + "ai_chat.inspection.tool_info.inspect_sql_snippets.param.includeBody": "Optional. Ob eine Vorschau des Vorlageninhalts enthalten ist. Standard true.", + "ai_chat.inspection.tool_info.inspect_shortcuts.desc": "Aktuelle Shortcut-Konfiguration und Plattformunterschiede prüfen", + "ai_chat.inspection.tool_info.inspect_shortcuts.detail": "Gibt Shortcut-Aktionen, aktuelle Plattformbindung, Windows/macOS-Kombinationen, Nutzeränderungen und Vergleich mit Standardwerten zurück.", + "ai_chat.inspection.tool_info.inspect_shortcuts.params": "action?, keyword?, includeDisabled?(Standard true), includeAllPlatforms?(Standard true)", + "ai_chat.inspection.tool_info.inspect_shortcuts.tool_description": "Liest den aktuellen GoNavi-Shortcut-Konfigurationssnapshot, optional nach Aktionsname oder keyword gefiltert, und gibt aktuelle Plattformbindung, Windows/macOS-Bindungen, Defaults und Anpassungsstatus zurück.", + "ai_chat.inspection.tool_info.inspect_shortcuts.param.action": "Optional. Nach exaktem action key filtern, z. B. toggleQueryResultsPanel, sendAIChatMessage oder toggleAIPanel.", + "ai_chat.inspection.tool_info.inspect_shortcuts.param.keyword": "Optional. Nach Aktionsname, Beschreibung, Bereich, Tastenkombination oder Default filtern.", + "ai_chat.inspection.tool_info.inspect_shortcuts.param.includeDisabled": "Optional. Ob aktuell deaktivierte Shortcuts enthalten sind. Standard true.", + "ai_chat.inspection.tool_info.inspect_shortcuts.param.includeAllPlatforms": "Optional. Ob sowohl Windows- als auch macOS-Bindungen enthalten sind. Standard true.", + "ai_chat.inspection.tool_info.inspect_app_health.desc": "Inspect the overall AI application health", + "ai_chat.inspection.tool_info.inspect_app_health.detail": "Summarizes AI configuration, provider send prerequisites, MCP access, application log ERROR/WARN signals, recent connection failures and cooldowns, AI reply bubble render errors, and current workspace tabs. Use it first when users report AI instability, ask for an overall check, or need connection and MCP issues diagnosed together.", + "ai_chat.inspection.tool_info.inspect_app_health.params": "keyword?, connectionKeyword?, lineLimit?(default 120), includeLogLines?(default false)", + "ai_chat.inspection.tool_info.inspect_app_health.tool_description": "Read the GoNavi AI application health overview, including AI provider and send prerequisites, MCP access, application log ERROR/WARN signals, recent connection failures and cooldowns, AI reply bubble render errors, and current workspace tabs, then return blockers, runtime anomaly signals, and suggested next probes.", + "ai_chat.inspection.tool_info.inspect_app_health.param.keyword": "Optional. Filter application logs by keyword, such as ai, mcp, mysql, or error. If omitted, the recent log window is read.", + "ai_chat.inspection.tool_info.inspect_app_health.param.connectionKeyword": "Optional. Keyword used when analyzing connection failure logs by type, address, or error. If omitted, keyword is reused.", + "ai_chat.inspection.tool_info.inspect_app_health.param.lineLimit": "Optional. Maximum number of log lines to analyze per probe. Default 120, maximum 240.", + "ai_chat.inspection.tool_info.inspect_app_health.param.includeLogLines": "Optional. Whether to include original log lines in the result. Default false; enable only when lines need to be quoted.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.desc": "Export an AI troubleshooting support bundle", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.detail": "Aggregates AI application health, provider and MCP status, application log summary, connection failure summary, message flow structure, context size, remote MCP access, and tool catalog index. Use it when users report AI instability, need MCP, connection, and logs reviewed together, or need development troubleshooting material without secrets or database passwords.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.params": "keyword?, sessionId?, lineLimit?(default 120), includeLogLines?(default false), includeMessageContent?(default false), publicUrl?, tokenConfigured?", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.tool_description": "Generate a GoNavi AI troubleshooting support bundle that summarizes AI application health, provider and send prerequisites, MCP configuration and remote access, application log summary, database connection failure summary, current AI message flow, context-size risk, and tool catalog index. By default it does not include database passwords, provider keys, MCP environment variable values, original log lines, or full message content.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.keyword": "Optional. Filter logs and tool catalog entries by keyword, such as ai, mcp, mysql, error, or openclaw.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.connectionKeyword": "Optional. Keyword used to analyze connection failure logs. If omitted, keyword is reused.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.sessionId": "Optional. AI session ID to diagnose. If omitted, the current active session is used.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.lineLimit": "Optional. Maximum number of application log lines to analyze. Default 120, maximum 240.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.includeLogLines": "Optional. Whether to include original log lines. Default false; enable only when lines need to be quoted.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.includeMessageContent": "Optional. Whether to include message content previews. Default false; enable only when troubleshooting bubble content.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.includeDetails": "Optional. Whether to include context-size details. Default false.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.publicUrl": "Optional. Public or tunnel URL used by a cloud Agent to access GoNavi MCP for the remote MCP support bundle.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.localAddr": "Optional. Windows local HTTP MCP listen address. Default 127.0.0.1:8765.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.path": "Optional. Streamable HTTP MCP path. Default /mcp.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.exposeStrategy": "Optional. Remote exposure strategy used to generate matching safety reminders.", + "ai_chat.inspection.tool_info.inspect_ai_support_bundle.param.tokenConfigured": "Optional. Whether a random Bearer Token is already prepared. Passing false returns an authentication warning.", + "ai_chat.inspection.tool_info.inspect_ai_setup_health.desc": "Run a one-shot health check for the current AI setup", + "ai_chat.inspection.tool_info.inspect_ai_setup_health.detail": "Summarizes the current AI provider, chat send prerequisites, MCP services and external client access, prompts and Skills, and attached context, then returns blockers, warnings, and next actions. Use it when users ask why AI is hard to use, whether the current AI setup has problems, or what is still missing.", + "ai_chat.inspection.tool_info.inspect_ai_setup_health.params": "No parameters", + "ai_chat.inspection.tool_info.inspect_ai_setup_health.tool_description": "Inspect current AI setup health, returning provider, model, chat send prerequisites, MCP access, prompts and Skills, attached table context, blockers, suggestions, and next actions.", + "ai_chat.inspection.tool_info.inspect_ai_runtime.desc": "Inspect current AI runtime status", + "ai_chat.inspection.tool_info.inspect_ai_runtime.detail": "Returns the active model provider, model name, safety level, context level, enabled Skills, and currently exposed built-in and MCP tools. Use it before answering questions about available tools, the active model, or why write operations are unavailable.", + "ai_chat.inspection.tool_info.inspect_ai_runtime.params": "No parameters", + "ai_chat.inspection.tool_info.inspect_ai_runtime.tool_description": "Read the current AI runtime snapshot, including provider, model, safety level, context level, enabled Skills, available built-in tools, and MCP tools. Use it before answering AI capability-boundary questions.", + "ai_chat.inspection.tool_info.inspect_ai_safety.desc": "Inspect current AI write safety boundaries", + "ai_chat.inspection.tool_info.inspect_ai_safety.detail": "Returns the SQL scope allowed by the current AI safety level, whether non-read-only statements still require confirmation or allowMutating, and whether the active connection, tab, or JVM diagnostic permission adds read-only restrictions. Use it when users ask why writes are blocked, whether DDL can run, or whether allowMutating is required.", + "ai_chat.inspection.tool_info.inspect_ai_safety.params": "No parameters", + "ai_chat.inspection.tool_info.inspect_ai_safety.tool_description": "Read the current AI safety-boundary snapshot, including SQL scope allowed by the active safety level, confirmation requirements for non-query statements, MCP execute_sql allowMutating requirements, and any additional read-only restrictions from the active connection, result tab, or JVM diagnostic permissions.", + "ai_chat.inspection.tool_info.inspect_ai_providers.desc": "Inspect current AI providers and model configuration", + "ai_chat.inspection.tool_info.inspect_ai_providers.detail": "Returns configured AI providers, the active provider, baseUrl values, selected models, declared model lists, whether keys exist, custom request header keys, and missing key, model, or endpoint checks. Use it when users ask why there are no models, whether an API Key is configured, or which providers are currently configured.", + "ai_chat.inspection.tool_info.inspect_ai_providers.params": "No parameters", + "ai_chat.inspection.tool_info.inspect_ai_providers.tool_description": "Read the current AI provider configuration snapshot, including provider list, active provider, endpoint, selected model, declared model list, key presence, custom request header keys, and missing key, model, or endpoint checks.", + "ai_chat.inspection.tool_info.inspect_ai_chat_readiness.desc": "Inspect whether current AI chat can send", + "ai_chat.inspection.tool_info.inspect_ai_chat_readiness.detail": "Returns whether the current chat input has all prerequisites to send, including active provider, missing key or endpoint on the current provider, selected model, current connection context, attached table context, and next actions. Use it when users ask why sending is disabled or what the chat input is missing.", + "ai_chat.inspection.tool_info.inspect_ai_chat_readiness.params": "No parameters", + "ai_chat.inspection.tool_info.inspect_ai_chat_readiness.tool_description": "Read the send-prerequisite state of the current AI chat input, including active provider, key and endpoint completeness, selected model, current connection context, attached table schema count, and suggested next actions.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.desc": "Inspect AI upstream request payloads and status", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.detail": "Reads recent AI upstream request start, completion, and failure records from gonavi.log, filtered by provider, requestId, or keyword, then returns request body preview, payload structure summary, endpoint, status code, latency, and error summary. Use it when users need to verify the real payload sent upstream, diagnose request parameter compatibility, confirm whether tools were sent, or inspect redacted request logs.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.params": "provider?, requestId?, keyword?, lineLimit?(default 160), requestLimit?(default 12), includeBody?(default true), includePayloadSummary?(default true), includeLines?(default false)", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.tool_description": "Read AI upstream request records from GoNavi application logs and return requestId, provider, method, endpoint, request body preview, redacted payload structure summary, status code, latency, and error summary. Use it when users mention upstream request payloads, requestId, provider parameters, missing tool calls, model API errors, or need to verify the real payload just sent upstream.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.provider": "Optional. Inspect only one provider, such as openai, anthropic, or gemini. Case-insensitive.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.requestId": "Optional. Filter by the exact requestId in logs, useful when continuing from an error log.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.keyword": "Optional. Further filter by requestId, provider, endpoint, bodyPreview, or error, such as model name, API path, or parameter name.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.lineLimit": "Optional. Maximum number of tail log lines to read. Default 160, maximum 300.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.requestLimit": "Optional. Maximum number of request summaries to return. Default 12, maximum 40.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.includeBody": "Optional. Whether to return the redacted request body preview. Default true; set false when only status is needed.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.includePayloadSummary": "Optional. Whether to parse the request body and return model, message role distribution, tool count/name list, stream, and tool_choice summary. Default true; message bodies and keys are not returned.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.includeLines": "Optional. Whether to include redacted raw log lines. Default false; enable only when original lines need to be quoted.", + "ai_chat.inspection.tool_info.inspect_ai_upstream_logs.param.bodyPreviewLimit": "Optional. Maximum characters for one body preview. Default 6000, maximum 12000.", + "ai_chat.inspection.tool_info.inspect_ai_tool_catalog.desc": "Inspect AI built-in tool catalog and argument hints", + "ai_chat.inspection.tool_info.inspect_ai_tool_catalog.detail": "Returns GoNavi AI built-in tools, recommended probe flows, argument descriptions, and current MCP tool summaries by keyword or tool name. Use it when users ask which tool should be used, how to fill arguments, which built-in tools exist, or when AI needs to choose a probe route first.", + "ai_chat.inspection.tool_info.inspect_ai_tool_catalog.params": "keyword?, toolName?, includeMCPTools?(default true), limit?(default 12)", + "ai_chat.inspection.tool_info.inspect_ai_tool_catalog.tool_description": "Read the GoNavi AI tool catalog snapshot, filterable by keyword or tool name, and return recommended tool-call flows, built-in tool descriptions, argument hints, and currently discovered MCP tool summaries.", + "ai_chat.inspection.tool_info.inspect_ai_tool_catalog.param.keyword": "Optional. Filter tools and flows by problem keyword, such as mcp, connection failure, transaction, shortcut, schema, or log.", + "ai_chat.inspection.tool_info.inspect_ai_tool_catalog.param.toolName": "Optional. Query by exact built-in tool name, such as inspect_mcp_draft or inspect_sql_risk.", + "ai_chat.inspection.tool_info.inspect_ai_tool_catalog.param.includeMCPTools": "Optional. Whether to include currently discovered MCP tool summaries. Default true.", + "ai_chat.inspection.tool_info.inspect_ai_tool_catalog.param.limit": "Optional. Maximum number of flows, built-in tools, and MCP tools to return. Default 12, maximum 40.", + "ai_chat.inspection.tool_info.inspect_ai_guidance.desc": "Inspect current AI prompts and Skills configuration", + "ai_chat.inspection.tool_info.inspect_ai_guidance.detail": "Returns current user-defined global, database, and JVM prompts, plus enabled Skills, scopes, dependency tools, and skill prompt content. Use it when users ask which prompts are currently attached, why AI answers this way, or which Skills are active.", + "ai_chat.inspection.tool_info.inspect_ai_guidance.params": "No parameters", + "ai_chat.inspection.tool_info.inspect_ai_guidance.tool_description": "Read the current AI prompt and skill configuration snapshot, including user-defined prompts, enabled Skills, scopes, dependency tools, and each system prompt.", + "ai_chat.inspection.tool_info.inspect_ai_context.desc": "Inspect currently attached AI table-schema context", + "ai_chat.inspection.tool_info.inspect_ai_context.detail": "Returns the tables currently attached to the AI conversation context, their connection and database, and optional DDL previews. Use it when users ask which table structures are attached or what the current AI context contains.", + "ai_chat.inspection.tool_info.inspect_ai_context.params": "includeDDL?(default false), ddlLimit?(default 4000)", + "ai_chat.inspection.tool_info.inspect_ai_context.tool_description": "Read the table-schema snapshot currently attached to the AI conversation context, including connection, database, table name, and optional DDL content.", + "ai_chat.inspection.tool_info.inspect_ai_context.param.includeDDL": "Optional. Whether to include each table's DDL content. Default false.", + "ai_chat.inspection.tool_info.inspect_ai_context.param.ddlLimit": "Optional. DDL truncation length. Default 4000, maximum 12000.", + "ai_chat.inspection.tool_info.inspect_current_connection.desc": "Inspect the current active connection or data source summary", + "ai_chat.inspection.tool_info.inspect_current_connection.detail": "Returns current active connection type, address, port, current database, SSH/proxy/HTTP tunnel state, and table information bound to the active tab. Use it when users ask which database is connected, whether SSH is used, or what type the current data source is.", + "ai_chat.inspection.tool_info.inspect_current_connection.params": "No parameters", + "ai_chat.inspection.tool_info.inspect_current_connection.tool_description": "Read the real summary of the current active connection or active-tab data source, including connection type, address, port, current database, SSH/proxy/HTTP tunnel state, and table context bound to the active tab.", + "ai_chat.inspection.tool_info.inspect_connection_capabilities.desc": "Inspect frontend capabilities supported by the current connection", + "ai_chat.inspection.tool_info.inspect_connection_capabilities.detail": "Returns the data-source capability matrix for the current or specified connection, including query editor support, SQL export, copy INSERT, create/rename/delete database support, forced read-only result state, and whether manual or approximate counts are preferred. Use it when users ask why database creation, deletion, result editing, or other actions are unavailable.", + "ai_chat.inspection.tool_info.inspect_connection_capabilities.params": "connectionId?(default current active connection)", + "ai_chat.inspection.tool_info.inspect_connection_capabilities.tool_description": "Read the frontend capability matrix for the current active connection or specified saved connection, including query editor support, SQL export, copy INSERT, create/rename/delete database support, forced read-only result state, and count strategy preferences.", + "ai_chat.inspection.tool_info.inspect_connection_capabilities.param.connectionId": "Optional. Connection ID to inspect. If omitted, the current active connection is used.", + "ai_chat.inspection.tool_info.inspect_saved_connections.desc": "Inspect locally saved connections", + "ai_chat.inspection.tool_info.inspect_saved_connections.detail": "Filters local saved data sources by keyword or database type and returns the data-source list, type distribution, address, current database, and SSH/proxy/HTTP tunnel state. Use it when users ask which connections are saved locally, want to find mysql or postgres connections, or need to know which connection has SSH configured.", + "ai_chat.inspection.tool_info.inspect_saved_connections.params": "keyword?, type?, limit?", + "ai_chat.inspection.tool_info.inspect_saved_connections.tool_description": "Read locally saved connections, optionally filtered by keyword and database type, and return each connection's type, address, current database, SSH/proxy/HTTP tunnel summary, and related metadata.", + "ai_chat.inspection.tool_info.inspect_saved_connections.param.keyword": "Optional. Filter by connection name, ID, type, host, database name, SSH address, or proxy address.", + "ai_chat.inspection.tool_info.inspect_saved_connections.param.type": "Optional. Only inspect one database type, such as mysql, postgres, redis, or mongodb.", + "ai_chat.inspection.tool_info.inspect_saved_connections.param.limit": "Optional. Maximum number of connections to return. Default 20, maximum 100.", + "ai_chat.inspection.tool_info.inspect_redis_topology.desc": "Diagnose Redis standalone, Sentinel, and Cluster configuration", + "ai_chat.inspection.tool_info.inspect_redis_topology.detail": "Reads local Redis connection topology summaries and returns standalone, Sentinel, and Cluster nodes, master, authentication state, DB range, redacted URI examples, status level, and next actions. Use it when users ask how to configure Redis Sentinel or Cluster, why DB switching fails, or how Cluster multi-DB behavior works.", + "ai_chat.inspection.tool_info.inspect_redis_topology.params": "connectionId?, keyword?, limit?, includeRecommendations?(default true)", + "ai_chat.inspection.tool_info.inspect_redis_topology.tool_description": "Read local Redis standalone, Sentinel, and Cluster topology summaries, returning nodes, Sentinel master, authentication state, DB selection, TLS/SSH/proxy state, backend adapter, redacted URI examples, status level, blockers, potential configuration risks, and recommendations. Results do not echo Redis or Sentinel passwords.", + "ai_chat.inspection.tool_info.inspect_redis_topology.param.connectionId": "Optional. Diagnose only one Redis connection ID.", + "ai_chat.inspection.tool_info.inspect_redis_topology.param.keyword": "Optional. Filter by connection name, address, topology, Sentinel master, or node address.", + "ai_chat.inspection.tool_info.inspect_redis_topology.param.limit": "Optional. Maximum number of Redis connections to return. Default 20, maximum 100.", + "ai_chat.inspection.tool_info.inspect_redis_topology.param.includeRecommendations": "Optional. Whether to return repair recommendations. Default true.", + "ai_chat.inspection.tool_info.inspect_external_sql_directories.desc": "Inspect local external SQL directory assets", + "ai_chat.inspection.tool_info.inspect_external_sql_directories.detail": "Filters local external SQL directories by keyword, connection, or database, and returns directory path, bound connection/database, and whether SQL files from those directories are currently open. Use it when users mention external SQL directories, ask which directory contains a script, or need to identify the external directory for an open SQL file.", + "ai_chat.inspection.tool_info.inspect_external_sql_directories.params": "keyword?, connectionId?, dbName?, limit?", + "ai_chat.inspection.tool_info.inspect_external_sql_directories.tool_description": "Read locally configured external SQL directories, optionally filtered by keyword, connection, and database, and return directory path, bound connection/database, and summaries of currently open external SQL file tabs.", + "ai_chat.inspection.tool_info.inspect_external_sql_directories.param.keyword": "Optional. Filter by directory name, path, connection name, or database name.", + "ai_chat.inspection.tool_info.inspect_external_sql_directories.param.connectionId": "Optional. Only inspect external SQL directories bound to one connection.", + "ai_chat.inspection.tool_info.inspect_external_sql_directories.param.dbName": "Optional. Only inspect external SQL directories bound to one database.", + "ai_chat.inspection.tool_info.inspect_external_sql_directories.param.limit": "Optional. Maximum number of directories to return. Default 20, maximum 100.", + "ai_chat.inspection.tool_info.inspect_external_sql_file.desc": "Read external SQL file content", + "ai_chat.inspection.tool_info.inspect_external_sql_file.detail": "Reads a specific SQL file inside a configured external SQL directory and returns its directory, bound connection/database, whether it already has an open tab, and a truncated content preview. Use it when users ask to inspect a script in a directory or explain what report.sql does.", + "ai_chat.inspection.tool_info.inspect_external_sql_file.params": "filePath, previewCharLimit?", + "ai_chat.inspection.tool_info.inspect_external_sql_file.tool_description": "Read the content preview of a specified external SQL file, only for SQL files inside configured external SQL directories. Return file path, owning directory, bound connection/database, whether it is already open in the workspace, and truncated body content.", + "ai_chat.inspection.tool_info.inspect_external_sql_file.param.filePath": "Required. Absolute path of the SQL file to read, usually found with inspect_external_sql_directories first.", + "ai_chat.inspection.tool_info.inspect_external_sql_file.param.previewCharLimit": "Optional. Maximum characters returned in the content preview. Default 12000, maximum 40000.", + "ai_chat.inspection.tool_info.inspect_active_tab.desc": "Inspect the current active tab context", + "ai_chat.inspection.tool_info.inspect_active_tab.detail": "Returns the current active tab type, connection, database, table name, and draft content in the current SQL or command tab, truncated when long. Use it when users mention the current SQL, ask to optimize the editor statement, or refer to the current tab.", + "ai_chat.inspection.tool_info.inspect_active_tab.params": "includeContent?(default true)", + "ai_chat.inspection.tool_info.inspect_active_tab.tool_description": "Get the current active tab context snapshot, including tab type, connection, database, table name, and draft content from the current SQL or command tab.", + "ai_chat.inspection.tool_info.inspect_active_tab.param.includeContent": "Optional. Whether to include SQL or command draft content from the tab. Default true.", + "ai_chat.inspection.tool_info.inspect_workspace_tabs.desc": "Inspect currently open workspace tabs", + "ai_chat.inspection.tool_info.inspect_workspace_tabs.detail": "Returns the list of tabs open in the current workspace, which one is active, and each tab's connection, database, table name, and related context. Use it when users ask which SQL tabs are open, what exists in the workspace, or want to compare several query tabs.", + "ai_chat.inspection.tool_info.inspect_workspace_tabs.params": "limit?(default 12), includeContent?(default false)", + "ai_chat.inspection.tool_info.inspect_workspace_tabs.tool_description": "Get an overview of currently open workspace tabs, including active tab, tab type, connection, database, table name, and optional SQL or command draft content.", + "ai_chat.inspection.tool_info.inspect_workspace_tabs.param.limit": "Optional. Maximum number of tabs to return. Default 12, maximum 30.", + "ai_chat.inspection.tool_info.inspect_workspace_tabs.param.includeContent": "Optional. Whether to include SQL or command draft content from tabs. Default false." } diff --git a/shared/i18n/en-US.json b/shared/i18n/en-US.json index 6ba5b5e..6636691 100644 --- a/shared/i18n/en-US.json +++ b/shared/i18n/en-US.json @@ -15,6 +15,19 @@ "common.success": "Success", "common.unknown": "Unknown", "common.warning": "Warning", + "dev.perf_data_grid.title": "DataGrid performance reproduction page", + "dev.perf_data_grid.ui_version.legacy": "Legacy UI", + "dev.perf_data_grid.ui_version.v2": "New UI", + "dev.perf_data_grid.ui_version.legacy_short": "Legacy", + "dev.perf_data_grid.ui_version.v2_short": "New", + "dev.perf_data_grid.rows": "Rows", + "dev.perf_data_grid.columns": "Columns", + "dev.perf_data_grid.density.comfortable": "Comfortable", + "dev.perf_data_grid.density.standard": "Compact", + "dev.perf_data_grid.density.compact": "Ultra compact", + "dev.perf_data_grid.trigger_layout": "Trigger layout recalculation", + "dev.perf_data_grid.notice.message": "This page is only for development scroll performance sampling", + "dev.perf_data_grid.notice.description": "Current {{uiVersion}} UI, {{rows}} rows / {{columns}} columns. Sample vertical, horizontal, and Shift+wheel scrolling directly in the grid area.", "connection.sidebar.group.untitled": "Untitled group", "connection.sidebar.group.meta": "{{count}} connections · Connection group", "connection.sidebar.group.badge": "GROUP", @@ -104,6 +117,9 @@ "table_overview.message.unpinned": "Table unpinned", "table_overview.message.copy_structure_success": "Table structure copied to clipboard", "table_overview.message.copy_structure_failed": "Failed to copy table structure: {{detail}}", + "table_overview.message.copy_table_name_empty": "Table name is empty and cannot be copied", + "table_overview.message.copy_table_name_success": "Table name copied to clipboard", + "table_overview.message.copy_table_name_failed": "Failed to copy table name: {{detail}}", "table_overview.message.exporting_table_format": "Exporting {{table}} as {{format}}...", "table_overview.message.export_success": "Export succeeded", "table_overview.message.export_failed": "Export failed: {{detail}}", @@ -119,6 +135,8 @@ "table_overview.message.rename_table_success": "Table renamed", "table_overview.message.rename_table_failed": "Failed to rename table: {{detail}}", "table_overview.message.unknown_error": "Unknown error", + "table_overview.tab.design_table_title": "Design Table ({{table}})", + "table_overview.tab.table_structure_title": "Table Structure ({{table}})", "table_overview.modal.delete_table.title": "Delete Table", "table_overview.modal.delete_table.content": "Delete table \"{{table}}\"? This action cannot be undone.", "table_overview.modal.rename_table.title": "Rename Table", @@ -150,6 +168,8 @@ "table_overview.action.show_more": "Show more tables ({{count}} remaining)", "table_overview.menu.new_query": "New Query", "table_overview.menu.design_table": "Design Table", + "table_overview.menu.table_structure": "Table Structure", + "table_overview.menu.copy_table_name": "Copy Table Name", "table_overview.menu.copy_structure": "Copy Table Structure", "table_overview.menu.backup_table_sql": "Back Up Table (SQL)", "table_overview.menu.rename_table": "Rename Table", @@ -213,6 +233,18 @@ "app.data_root.backend.error.open_directory_failed": "Failed to open data directory: {{detail}}", "app.data_root.backend.error.open_directory_unsupported": "Opening directories is not supported on this platform: {{platform}}", "app.data_root.backend.error.read_source_failed": "Unable to read source data ({{entry}}): {{detail}}", + "app.data_root.backend.error.resolve_source_failed": "Failed to resolve source data directory: {{detail}}", + "app.data_root.backend.error.resolve_target_failed": "Failed to resolve target data directory: {{detail}}", + "app.data_root.backend.error.target_inside_source": "Target data directory cannot be inside the source directory", + "app.data_root.backend.error.read_source_root_failed": "Failed to read source data directory: {{detail}}", + "app.data_root.backend.error.read_migrated_security_update_state_failed": "Failed to read migrated security update state: {{detail}}", + "app.data_root.backend.error.write_migrated_security_update_state_failed": "Failed to write migrated security update state: {{detail}}", + "app.data_root.backend.error.read_migrated_security_update_manifest_failed": "Failed to read migrated security update manifest: {{detail}}", + "app.data_root.backend.error.parse_migrated_security_update_manifest_failed": "Failed to parse migrated security update manifest: {{detail}}", + "app.data_root.backend.error.write_migrated_security_update_manifest_failed": "Failed to write migrated security update manifest: {{detail}}", + "app.data_root.backend.error.read_migrated_security_update_result_failed": "Failed to read migrated security update result: {{detail}}", + "app.data_root.backend.error.parse_migrated_security_update_result_failed": "Failed to parse migrated security update result: {{detail}}", + "app.data_root.backend.error.write_migrated_security_update_result_failed": "Failed to write migrated security update result: {{detail}}", "app.data_root.backend.message.migrated_restart": "Data migrated and switched to the new directory. Restart the app to finish switching all modules.", "app.data_root.backend.message.opened": "Data directory opened", "app.data_root.backend.message.unchanged": "Data directory is unchanged", @@ -235,6 +267,7 @@ "app.proxy.enable": "Enable Global Proxy", "app.proxy.host": "Proxy Host", "app.proxy.host_placeholder": "Example: 127.0.0.1", + "app.proxy.message.config_applied": "Global proxy configuration has been applied", "app.proxy.message.invalid_enabled": "Global proxy is enabled, but the host or port is invalid. It is currently treated as disabled.", "app.proxy.message.save_failed": "Global proxy configuration failed: {{error}}", "app.proxy.password_optional": "Password (optional)", @@ -371,6 +404,8 @@ "app.shortcuts.action.saveQuery.label": "Save Query", "app.shortcuts.action.selectCurrentStatement.description": "Select the SQL statement at the cursor in the query editor", "app.shortcuts.action.selectCurrentStatement.label": "Select Current Statement", + "app.shortcuts.action.toggleQueryResultsPanel.description": "Show or hide the results area below the query editor", + "app.shortcuts.action.toggleQueryResultsPanel.label": "Toggle Results Panel", "app.shortcuts.action.sendAIChatMessage.description": "Send the current message from the AI input; Shift+Enter always inserts a new line", "app.shortcuts.action.sendAIChatMessage.label": "Send AI Chat", "app.shortcuts.action.switchToNextTab.description": "Move right among open tabs", @@ -570,8 +605,35 @@ "app.browser_mock.mcp_client.claude_code.not_detected": "No Claude Code user-level GoNavi MCP configuration was detected", "app.browser_mock.mcp_client.codex.installed": "Codex user-level MCP configuration has been written. Restart Codex CLI or the desktop app to see GoNavi.", "app.browser_mock.mcp_client.codex.path_mismatch": "A GoNavi MCP record was detected in Codex, but it does not match the current GoNavi installation path. Updating is recommended.", + "ai.service.mcp_client.claude_code.install_success": "Claude Code user-level MCP configuration has been written. Restart Claude CLI, then GoNavi will appear under User MCPs in /mcp.", + "ai.service.mcp_client.codex.install_success": "Codex user-level MCP configuration has been written. Restart Codex CLI or the desktop app to see GoNavi.", + "ai.service.mcp_client.claude_code.config_path_failed": "Failed to locate Claude Code configuration: {{detail}}", + "ai.service.mcp_client.codex.config_path_failed": "Failed to locate Codex configuration: {{detail}}", + "ai.service.mcp_client.executable_path_failed": "Failed to locate the current GoNavi executable: {{detail}}", + "ai.service.mcp_client.user_home_dir_unavailable": "Unable to determine the current user home directory", + "ai.service.mcp_client.executable_path_empty": "Current GoNavi executable path is empty", + "ai.service.mcp_client.claude_code.config_format_invalid": "Claude Code configuration format is invalid: {{path}} should be {{expected}}", + "ai.service.mcp_client.codex.config_format_invalid": "Codex configuration format is invalid: {{path}} could not be parsed as {{expected}}", + "ai.service.mcp_client.claude_code.config_read_failed": "Failed to read Claude Code configuration: {{detail}}", + "ai.service.mcp_client.claude_code.config_parse_failed": "Failed to parse Claude Code configuration: {{detail}}", + "ai.service.mcp_client.claude_code.config_serialize_failed": "Failed to serialize Claude Code configuration: {{detail}}", + "ai.service.mcp_client.claude_code.config_dir_create_failed": "Failed to create Claude Code configuration directory: {{detail}}", + "ai.service.mcp_client.claude_code.config_write_failed": "Failed to write Claude Code configuration: {{detail}}", + "ai.service.mcp_client.codex.config_read_failed": "Failed to read Codex configuration: {{detail}}", + "ai.service.mcp_client.codex.config_dir_create_failed": "Failed to create Codex configuration directory: {{detail}}", + "ai.service.mcp_client.codex.config_write_failed": "Failed to write Codex configuration: {{detail}}", + "ai.service.mcp_client.claude_code.status.missing": "No Claude Code user-level GoNavi MCP configuration was detected", + "ai.service.mcp_client.codex.status.missing": "No Codex user-level GoNavi MCP configuration was detected", + "ai.service.mcp_client.claude_code.status.path_check_failed": "A GoNavi MCP record was detected in Claude Code, but the current GoNavi installation path check failed: {{detail}}", + "ai.service.mcp_client.codex.status.path_check_failed": "A GoNavi MCP record was detected in Codex, but the current GoNavi installation path check failed: {{detail}}", + "ai.service.mcp_client.claude_code.status.connected": "Claude Code user-level GoNavi MCP configuration was detected and matches the current GoNavi installation path", + "ai.service.mcp_client.codex.status.connected": "Codex user-level GoNavi MCP configuration was detected and matches the current GoNavi installation path", + "ai.service.mcp_client.claude_code.status.path_mismatch": "A GoNavi MCP record was detected in Claude Code, but it does not match the current GoNavi installation path. Updating is recommended.", + "ai.service.mcp_client.codex.status.path_mismatch": "A GoNavi MCP record was detected in Codex, but it does not match the current GoNavi installation path. Updating is recommended.", + "ai.service.mcp_client.remote.status.message": "{{label}} usually runs in the cloud or a remote environment. Connect it to Windows GoNavi through a remote MCP bridge; database passwords remain on this GoNavi machine.", "app.browser_mock.provider.test_failed_detail": "Connection test failed: {{detail}}", "app.browser_mock.provider.test_success": "Endpoint connectivity test succeeded", + "app.backend.error.reset_webview_zoom_failed": "Failed to reset WebView2 zoom: {{detail}}", "app.update.action.hide_to_background": "Hide to Background", "app.update.action.install_update": "Install Update", "app.update.action.open_install_directory": "Open Install Directory", @@ -642,6 +704,7 @@ "query.run": "Run", "query.save": "Save Query", "saved_query.default_name": "Query {{index}}", + "saved_query.error.missing_context": "Saved query is missing SQL, connection, or database context", "query.stop": "Stop", "message_publish_modal.title": "Test message publish", "message_publish_modal.title_with_connection": "Test message publish · {{connectionName}}", @@ -739,6 +802,7 @@ "connection_modal.field.oceanBaseProtocol.label": "OceanBase protocol", "connection_modal.field.oceanBaseProtocol.help.primary": "Choose MySQL for MySQL tenants and Oracle for Oracle tenants. GoNavi selects automatically by port: OB MySQL wire ports use OBClient capability injection (the same path as Navicat), while OBProxy Oracle listener ports use standard TNS.", "connection_modal.field.oceanBaseProtocol.help.connectionAttributes": "If an Oracle tenant connection reports \"Error 1235\" or an OBClient handshake failure, use {{attributes}} in the \"Connection parameters\" field to override the OBClient capability injected by GoNavi by default.", + "connection.oceanbase.error.unsupported_protocol": "OceanBase only supports MySQL/Oracle tenant protocols; \"{{value}}\" is not supported. Switch to MySQL or Oracle.", "connection_modal.field.ssh_host": "SSH host", "connection_modal.field.ssh_password": "SSH password", "connection_modal.field.ssh_user": "SSH user", @@ -980,6 +1044,7 @@ "connection_modal.secret.clear_saved_replica_password": "Clear saved replica password", "connection_modal.secret.clear_saved_ssh_password": "Clear saved SSH password", "connection_modal.secret.clear_saved_tunnel_password": "Clear saved HTTP Tunnel password", + "connection_modal.secret.error.saved_connection_deleted": "The saved connection was not found. It may have been deleted. Refresh and try again.", "connection_modal.secret.error.saved_connection_missing": "The saved secret for the current connection was not found. Re-enter the password, save, and try again.", "connection_modal.secret.error.store_unavailable": "Secure secret storage is currently unavailable. Check the system keychain or credential manager, then try again.", "connection_modal.secret.saved_mongo_replica_password": "Saved MongoDB replica password", @@ -1105,7 +1170,9 @@ "connection_modal.step1.group.nosql": "NoSQL databases", "connection_modal.step1.group.relational": "Relational databases", "connection_modal.step1.group.domestic": "Domestic databases", + "connection_modal.step1.group.vector": "Vector databases", "connection_modal.step1.group.timeseries": "Time-series databases", + "connection_modal.step1.group.message_queue": "Message queues", "connection_modal.step1.group.other": "Other", "connection_modal.group.time_series": "Time-series databases", "connection_modal.group.other": "Other", @@ -1119,6 +1186,18 @@ "connection_modal.layout.custom": "Custom driver connection", "connection_modal.layout.jvm": "JVM runtime connection", "connection_modal.layout.generic_sql": "Generic SQL connection", + "connection_modal.layoutKind.mysqlCompatible": "MySQL-compatible", + "connection_modal.layoutKind.mongodb": "MongoDB", + "connection_modal.layoutKind.redis": "Redis", + "connection_modal.layoutKind.postgresCompatible": "PostgreSQL-compatible", + "connection_modal.layoutKind.oracle": "Oracle", + "connection_modal.layoutKind.file": "File databases", + "connection_modal.layoutKind.search": "Search engines", + "connection_modal.layoutKind.vector": "Vector databases", + "connection_modal.layoutKind.timeseries": "Time-series databases", + "connection_modal.layoutKind.custom": "Custom driver", + "connection_modal.layoutKind.jvm": "JVM runtime", + "connection_modal.layoutKind.genericSql": "Standard SQL", "connection_modal.db_type_hint.custom": "Connect with a custom driver and DSN.", "connection_modal.db_type_hint.redis": "Connect to Redis standalone or Redis Cluster.", "connection_modal.db_type_hint.mongodb": "Connect to MongoDB standalone, Replica Set, or SRV addresses.", @@ -1128,7 +1207,11 @@ "connection_modal.step1.hint.custom": "Custom driver and DSN", "connection_modal.step1.hint.redis": "Single node / cluster", "connection_modal.step1.hint.mongodb": "Single node / replica set", + "connection_modal.step1.hint.elasticsearch": "Index browsing, Mapping inspection, JSON DSL, and query_string queries", + "connection_modal.step1.hint.chroma": "Collection browsing, vector retrieval, and metadata filtering", + "connection_modal.step1.hint.qdrant": "Collection browsing, vector search, and Payload filtering", "connection_modal.step1.hint.oceanBase": "MySQL / Oracle tenant", + "connection_modal.step1.hint.goldendb": "MySQL compatible / distributed transactions", "connection_modal.step1.hint.file": "Local file connection", "connection_modal.step1.hint.standard": "Standard connection configuration", "connection_modal.step.select_source": "Select data source", @@ -1248,12 +1331,19 @@ "sidebar.message.database_export_success": "Exported {{database}}.", "sidebar.message.database_export_failed": "Failed to export {{database}}: {{error}}", "sidebar.message.connection_config_not_found": "Connection configuration was not found.", + "sidebar.message.ai_table_context_missing": "The current table is missing connection context and cannot be sent to AI.", "sidebar.sql_file_exec.title": "Run external SQL file", "sidebar.message.read_file_failed": "Failed to read file: {{error}}", "sidebar.message.select_connection_or_database_first": "Select a connection or database first.", "sidebar.message.schema_create_unsupported": "Schema creation is not supported for this database.", "sidebar.message.schema_target_missing": "Select a database for schema creation.", "sidebar.message.schema_created": "Schema created.", + "sidebar.message.schema_edit_unsupported": "This node does not support schema editing from this entry.", + "sidebar.message.schema_target_edit_missing": "Target schema was not found, so it cannot be edited.", + "sidebar.message.schema_name_unchanged": "Schema name is unchanged.", + "sidebar.message.schema_renamed": "Schema renamed.", + "sidebar.message.schema_target_delete_missing": "Target schema was not found, so it cannot be deleted.", + "sidebar.message.schema_deleted": "Schema deleted.", "sidebar.message.operation_create_failed": "Create failed: {{error}}", "sidebar.sql_file.default_name": "SQL file", "sidebar.message.sql_file_context_incomplete": "SQL file context is incomplete.", @@ -1294,6 +1384,8 @@ "sidebar.message.create_failed": "Create failed: {{error}}", "sidebar.modal.confirm_delete_database.title": "Delete database", "sidebar.modal.confirm_delete_database.content": "Delete {{name}}? This cannot be undone.", + "sidebar.modal.confirm_delete_schema.title": "Delete schema", + "sidebar.modal.confirm_delete_schema.content": "Delete schema {{name}}? The schema and all objects inside it will be deleted. This cannot be undone.", "sidebar.modal.confirm_delete_sql_file.title": "Delete SQL file", "sidebar.modal.confirm_delete_sql_file.content": "Delete \"{{name}}\"? This will delete the local file from disk and cannot be undone.", "sidebar.modal.confirm_delete_sql_directory.title": "Delete SQL directory", @@ -1315,7 +1407,7 @@ "sidebar.search.multi_select_supported": "Multiple scopes supported", "sidebar.search.scope_hint": "Smart mode searches names, hosts, databases, and objects according to context.", "sidebar.modal.confirm_delete.title": "Confirm delete", - "sidebar.modal.confirm_delete_tag.content": "Delete {{name}}?", + "sidebar.modal.confirm_delete_tag.content": "Delete group \"{{name}}\"? This will not delete the connections inside it.", "sidebar.menu.edit_connection": "Edit connection", "sidebar.menu.delete_connection": "Delete connection", "sidebar.modal.confirm_delete_connection.content": "Delete {{name}}?", @@ -1332,6 +1424,30 @@ "sidebar.command_search.reset_filter": "Reset sidebar filter", "sidebar.command_search.no_synced_filter": "No synced sidebar filter", "sidebar.command_search.no_filter_content": "No filter text", + "sidebar.command_search.recent_sql_fallback": "SQL record", + "sidebar.command_search.action.ask_ai.title": "Ask AI", + "sidebar.command_search.action.new_query.meta": "Open a new SQL editor tab", + "sidebar.command_search.action.new_connection.title": "New data source", + "sidebar.command_search.action.new_connection.meta": "Create a database, runtime, or other data source connection", + "sidebar.command_search.action.open_ai.title": "Open AI data insights", + "sidebar.command_search.action.open_ai.meta": "Let AI analyze the current database context", + "sidebar.command_search.action.open_sql_log.title": "View SQL execution log", + "sidebar.command_search.action.open_sql_log.meta": "Open the recent execution history panel", + "sidebar.command_search.empty.ai": "Type a question after \"?\" and press Enter to send it to the AI panel.", + "sidebar.command_search.empty.object": "No matching tables, views, or materialized views.", + "sidebar.command_search.empty.default": "No matches. Type @table to search table objects only, or type ?question to ask AI.", + "sidebar.command_search.section.goto": "Go to", + "sidebar.command_search.section.ai": "AI · Ask", + "sidebar.command_search.section.actions": "Actions", + "sidebar.command_search.section.recent": "Recent queries", + "sidebar.command_search.footer.navigate": "Navigate", + "sidebar.command_search.footer.select": "Select", + "sidebar.command_search.footer.object_only": "Table objects only", + "sidebar.command_search.footer.ask_ai": "Send to AI", + "sidebar.ai_prompt.explain.intro": "Explain the structure and business meaning of table {{table}}.", + "sidebar.ai_prompt.explain.detail": "Focus on field meanings, primary keys/indexes, potential relationships, typical query scenarios, and risks.", + "sidebar.ai_prompt.query.intro": "Generate 3 common SQL queries for table {{table}}.", + "sidebar.ai_prompt.query.detail": "Include: a data preview query, a query filtered by key fields, and one aggregation or statistics query.", "sidebar.command_search.object_kind.all": "All", "sidebar.command_search.object_kind.tables": "Tables", "sidebar.command_search.object_kind.views": "Views", @@ -1361,6 +1477,7 @@ "sidebar.action.batch_tables": "Batch tables", "sidebar.action.batch_databases": "Batch databases", "sidebar.action.locate_current_table": "Locate current open table", + "sidebar.action.locate_current_tab": "Locate current tab", "sidebar.action.pin_table": "Pin table", "sidebar.action.unpin_table": "Unpin table", "sidebar.status.pinned": "Pinned", @@ -1407,7 +1524,13 @@ "sidebar.v2_database_menu.export_backup_section": "Export and backup", "sidebar.v2_database_menu.export_all_table_schema_sql": "Export all table schemas · SQL", "sidebar.v2_database_menu.backup_all_tables_sql": "Back up all tables · schema + data SQL", + "sidebar.v2_schema_menu.meta": "{{database}} · Schema actions", + "sidebar.v2_schema_menu.edit_schema": "Edit schema", + "sidebar.v2_schema_menu.export_current_schema_sql": "Export current schema table structures · SQL", + "sidebar.v2_schema_menu.backup_current_schema_sql": "Back up all current schema tables · schema + data", + "sidebar.v2_schema_menu.delete_schema_cascade": "Delete schema · DROP CASCADE", "sidebar.message.locate_current_table_unavailable": "The current tab has no table to locate", + "sidebar.message.locate_current_tab_unavailable": "The current tab has no locatable content", "sidebar.locate.object.table": "Table", "sidebar.locate.object.view": "View", "sidebar.locate.object.materialized_view": "Materialized view", @@ -1429,6 +1552,7 @@ "sidebar.field.database_name": "Database name", "sidebar.validation.name_required": "Enter a name.", "sidebar.modal.rename_database.title": "Rename database: {{name}}", + "sidebar.modal.rename_schema.title": "Edit schema: {{name}}", "sidebar.field.new_database_name": "Replacement database name", "sidebar.validation.new_database_name_required": "Enter the replacement database name.", "sidebar.field.schema_name": "Schema name", @@ -1543,6 +1667,7 @@ "sidebar.tab.trigger": "Trigger: {{name}}", "sidebar.tab.event": "Event: {{name}}", "sidebar.tab.edit_event": "Edit event: {{name}}", + "sidebar.tab.new_event": "New event", "sidebar.tab.materialized_view_definition": "Materialized view: {{name}}", "sidebar.tab.view_definition": "View: {{name}}", "sidebar.tab.edit_view": "Edit view: {{name}}", @@ -1553,12 +1678,17 @@ "sidebar.tab.create_procedure": "New procedure", "sidebar.tab.new_query": "New query", "sidebar.tab.new_query_database": "New query ({{database}})", + "store.fallback.connection_name": "Connection {{index}}", + "store.fallback.connection_tag_name": "Tag {{index}}", + "store.fallback.sql_snippet_name": "Snippet {{index}}", "sidebar.tab.redis_command": "Command - {{database}}", "sidebar.tab.redis_monitor": "Monitor - {{database}}", + "sidebar.tab.recent_query": "Recent query", "tab_manager.menu.close_all": "Close all tabs", "tab_manager.menu.close_left": "Close tabs to the left", "tab_manager.menu.close_other": "Close other tabs", "tab_manager.menu.close_right": "Close tabs to the right", + "tab_manager.menu.tab_display_settings": "Tab settings", "tab_manager.close_aria": "Close {{title}}", "tab_manager.kind_badge.query": "SQL", "tab_manager.kind_badge.table": "Table", @@ -1611,6 +1741,22 @@ "tab_manager.hover.label.database": "Database", "tab_manager.hover.label.object": "Object", "tab_manager.hover.label.type": "Type", + "tab_manager.sql_file_close.read_failed_cancel_close": "Failed to read SQL file, close was cancelled: {{detail}}", + "tab_manager.sql_file_close.dirty_single_label": "\"{{title}}\"", + "tab_manager.sql_file_close.dirty_multiple_label": "{{count}} SQL files", + "tab_manager.sql_file_close.save_confirm_title": "Save SQL file changes?", + "tab_manager.sql_file_close.save_confirm_content": "{{label}} has unsaved changes. Save before closing?", + "tab_manager.sql_file_close.save_and_close": "Save and close", + "tab_manager.sql_file_close.discard": "Don't save", + "tab_manager.sql_file_close.save_failed": "Failed to save {{title}}: {{detail}}", + "tab_manager.sql_file_close.unknown_error": "Unknown error", + "tab_manager.sql_file_close.saved": "SQL file saved", + "tab_manager.sql_file_close.missing_single_label": "\"{{title}}\"", + "tab_manager.sql_file_close.missing_multiple_label": "{{count}} SQL file tabs", + "tab_manager.sql_file_close.missing_confirm_title": "Close missing SQL file tabs?", + "tab_manager.sql_file_close.missing_confirm_content": "The external SQL file for {{label}} no longer exists or was moved. Closing will discard the local draft in the tab.", + "tab_manager.sql_file_close.continue_close": "Continue closing", + "tab_manager.sql_file_close.close_tabs": "Close tabs", "sidebar.message.no_visible_databases": "No visible databases or schemas were returned. Check account permissions or refresh from the context menu.", "sidebar.message.visual_new_table_unsupported": "This data source does not support visual table creation yet.", "sidebar.message.jvm_resources_backend_unavailable": "JVM resource browsing is not available in this build.", @@ -1644,8 +1790,26 @@ "sidebar.message.saved_query_deleted": "Query deleted.", "sidebar.message.saved_query_name_unchanged": "Query name is unchanged.", "sidebar.message.saved_query_renamed": "Query renamed.", + "sidebar.message.saved_query_rename_failed": "Failed to rename query: {{error}}", + "sidebar.message.saved_query_rebind_success": "Query bound to {{name}}", + "sidebar.message.saved_query_rebind_failed": "Failed to bind query: {{error}}", + "sidebar.message.saved_query_delete_failed": "Failed to delete query: {{error}}", + "sidebar.message.message_publish_unsupported": "The current object does not support test message sending", + "sidebar.message.message_publish_success": "Test message sent to {{destination}}", + "sidebar.message.message_publish_success_with_count": "Test message sent to {{destination}} (submitted {{count}})", + "sidebar.message.message_publish_target_fallback": "target", + "sidebar.message.connection_release_failed": "Failed to release connection", + "sidebar.message.connection_release_failed_from_sidebar": "The connection was disconnected from the sidebar, but backend connection release failed", "sidebar.menu.sort_by_name": "Sort by name", "sidebar.menu.sort_by_frequency": "Sort by usage frequency", + "sidebar.menu.new_table": "New table", + "sidebar.menu.create_event": "New event", + "sidebar.menu.bind_to_connection": "Bind to connection", + "sidebar.aria.switch_connection": "Switch to connection {{name}}", + "sidebar.menu.edit_schema": "Edit schema", + "sidebar.menu.export_current_schema_sql": "Export current schema table structures (SQL)", + "sidebar.menu.backup_current_schema_sql": "Back up all tables in current schema (schema and data SQL)", + "sidebar.menu.delete_schema": "Delete schema", "sidebar.menu.create_view": "New view", "sidebar.menu.create_function": "New function", "sidebar.menu.create_procedure": "New procedure", @@ -1678,7 +1842,10 @@ "sidebar.menu.view_routine_definition": "View definition", "sidebar.menu.edit_definition": "Edit definition", "sidebar.menu.delete_routine": "Delete {{type}}", + "sidebar.menu.copy_object_name": "Copy name", + "sidebar.menu.table_structure": "Table structure", "sidebar.menu.design_table": "Design table", + "sidebar.menu.copy_table_name": "Copy table name", "sidebar.menu.copy_table_structure": "Copy table structure", "sidebar.menu.backup_table_sql": "Back up table (SQL)", "sidebar.menu.rename_table": "Rename table", @@ -1789,8 +1956,8 @@ "jvm.backend.connection_error.agent.base_url_invalid.help": "Enter a full http:// or https:// URL, for example http://127.0.0.1:19090/gonavi/agent/jvm.", "jvm.backend.connection_error.agent.scheme_unsupported.summary": "Agent connection failed: only HTTP or HTTPS is supported.", "jvm.backend.connection_error.agent.scheme_unsupported.help": "Change Agent Base URL to an address that starts with http:// or https://.", - "jvm.backend.connection_error.agent.connection_refused.summary": "Agent connection failed: Target Agent management port is not listening, or the address is unreachable.", - "jvm.backend.connection_error.agent.connection_refused.help": "Confirm the Java service started the GoNavi Agent with `-javaagent`, and check Base URL, port mapping, and firewall rules.", + "jvm.backend.connection_error.agent.connection_refused.summary": "Agent connection failed: the target Agent management port is not listening, or the address is unreachable.", + "jvm.backend.connection_error.agent.connection_refused.help": "Confirm the Java service started GoNavi Agent with `-javaagent`, and check Base URL, port mapping, and firewall rules.", "jvm.backend.connection_error.agent.unauthorized.summary": "Agent connection failed: Agent responded, but the API Key is missing or invalid.", "jvm.backend.connection_error.agent.unauthorized.help": "Check whether the Agent API Key in the connection matches the target service startup parameters.", "jvm.backend.connection_error.agent.forbidden.summary": "Agent connection failed: Agent rejected this request.", @@ -2048,6 +2215,7 @@ "jvm_diagnostic.session.default_reason": "Session started from the console", "jvm_diagnostic.ai_plan.error.transport_mismatch": "The AI plan diagnostic transport is {{planTransport}}, which does not match the current console {{currentTransport}}. Regenerate the plan before applying it.", "jvm_diagnostic.ai_plan.message.filled": "AI diagnostic plan filled into the console", + "jvm_diagnostic.ai_plan.default_reason": "AI diagnostic plan: {{intent}}", "jvm_diagnostic.session_capability.title": "Session and capabilities", "jvm_diagnostic.session_capability.description": "Current transport, permissions, and quick maintenance", "jvm_diagnostic.session_capability.status.session_established": "Session established", @@ -2100,11 +2268,23 @@ "jvm_diagnostic.presentation.phase.running": "Running", "jvm_diagnostic.presentation.phase.completed": "Completed", "jvm_diagnostic.presentation.phase.failed": "Failed", + "jvm_diagnostic.presentation.phase.canceled": "Canceled", "jvm_diagnostic.presentation.phase.canceling": "Canceling", "jvm_diagnostic.presentation.phase.diagnostic": "Diagnostic event", "jvm_diagnostic.presentation.event.diagnostic": "Diagnostic output", "jvm_diagnostic.presentation.event.chunk": "Output chunk", "jvm_diagnostic.presentation.event.done": "Execution finished", + "jvm_diagnostic.presentation.transport.agent_bridge": "Agent Bridge", + "jvm_diagnostic.presentation.transport.arthas_tunnel": "Arthas Tunnel", + "jvm_diagnostic.presentation.risk.low": "Low risk", + "jvm_diagnostic.presentation.risk.medium": "Medium risk", + "jvm_diagnostic.presentation.risk.high": "High risk", + "jvm_diagnostic.presentation.command_type.observe": "Observe", + "jvm_diagnostic.presentation.command_type.trace": "Trace", + "jvm_diagnostic.presentation.command_type.mutating": "High risk", + "jvm_diagnostic.presentation.source.manual": "Manual input", + "jvm_diagnostic.presentation.source.ai_plan": "AI plan", + "jvm_diagnostic.presentation.fallback.unknown": "Unknown", "jvm_diagnostic.presentation.chunk.empty_event": "Empty event", "jvm_diagnostic.history.title": "Audit history", "jvm_diagnostic.history.description": "Recent commands and execution status", @@ -2197,6 +2377,80 @@ "query_editor.format.restore_last_format": "Restore last format", "query_editor.format.snippet_settings": "Snippet settings...", "query_editor.format.shortcut_settings": "Shortcut settings...", + "snippet_settings.list.title": "Snippet List", + "snippet_settings.action.new": "New Snippet", + "snippet_settings.action.reset": "Reset to Default", + "snippet_settings.action.delete": "Delete", + "snippet_settings.action.save": "Save", + "snippet_settings.action.close": "Close", + "snippet_settings.tag.builtin": "Built-in", + "snippet_settings.field.prefix.label": "Prefix", + "snippet_settings.field.prefix.placeholder": "e.g. sel, ins", + "snippet_settings.field.name.label": "Name", + "snippet_settings.field.name.placeholder": "Snippet display name", + "snippet_settings.field.description.label": "Description (optional)", + "snippet_settings.field.description.placeholder": "Description shown in completion details", + "snippet_settings.field.body.label": "Snippet Body", + "snippet_settings.empty_state": "Select a snippet on the left to edit, or click \"{{action}}\"", + "snippet_settings.syntax_help.label": "Snippet syntax notes (editable)", + "snippet_settings.syntax_help.placeholder": "Shown in completion details, for example placeholder meanings, parameter conventions, or notes", + "snippet_settings.syntax_reference.label": "Placeholder syntax reference", + "snippet_settings.syntax_reference.first_tabstop": "${1:placeholder} First Tab stop; placeholder is the hint text", + "snippet_settings.syntax_reference.second_tabstop": "${2:default_value} Second Tab stop; accept the default directly if needed", + "snippet_settings.syntax_reference.final_cursor": "$0 Final cursor position", + "snippet_settings.syntax_reference.linked_tabstop": "${1:table_name} Reusing the same number keeps edits synchronized", + "snippet_settings.syntax_reference.builtin_variables": "Built-in variables (auto-replaced when expanded):", + "snippet_settings.syntax_reference.current_date": "${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} Current date", + "snippet_settings.syntax_reference.current_time": "${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND} Current time", + "snippet_settings.syntax_reference.unix_seconds": "${CURRENT_SECONDS_UNIX} Unix timestamp", + "snippet_settings.syntax_reference.uuid": "${UUID} Random UUID", + "snippet_settings.syntax_reference.random": "${RANDOM} 6-digit random number", + "snippet_settings.syntax_reference.example": "Example: SELECT ${1:column_name} FROM ${2:table_name} WHERE date >= '${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}';$0", + "snippet_settings.confirm.reset.title": "Reset to Default", + "snippet_settings.confirm.reset.description": "Restore the original content of this built-in snippet", + "snippet_settings.confirm.delete.title": "Delete Snippet", + "snippet_settings.confirm.delete.description": "Are you sure you want to delete this snippet?", + "snippet_settings.message.prefix_required": "Prefix is required", + "snippet_settings.message.name_required": "Name is required", + "snippet_settings.message.body_required": "Snippet body is required", + "snippet_settings.message.prefix_duplicate": "Prefix \"{{prefix}}\" is already used by another snippet", + "snippet_settings.message.saved": "Snippet saved", + "snippet_settings.message.deleted": "Snippet deleted", + "snippet_settings.message.reset_default": "Reset to default", + "sql_snippets.builtin.sel.name": "SELECT Basic Query", + "sql_snippets.builtin.sel.description": "Basic SELECT query template", + "sql_snippets.builtin.selw.name": "SELECT WHERE", + "sql_snippets.builtin.selw.description": "SELECT query with a WHERE condition", + "sql_snippets.builtin.selj.name": "SELECT JOIN", + "sql_snippets.builtin.selj.description": "SELECT query with an INNER JOIN", + "sql_snippets.builtin.ins.name": "INSERT", + "sql_snippets.builtin.ins.description": "INSERT data template", + "sql_snippets.builtin.upd.name": "UPDATE", + "sql_snippets.builtin.upd.description": "UPDATE data template", + "sql_snippets.builtin.del.name": "DELETE", + "sql_snippets.builtin.del.description": "DELETE data template", + "sql_snippets.builtin.ct.name": "CREATE TABLE", + "sql_snippets.builtin.ct.description": "CREATE TABLE template", + "sql_snippets.builtin.alt.name": "ALTER TABLE", + "sql_snippets.builtin.alt.description": "ALTER TABLE add-column template", + "sql_snippets.builtin.dro.name": "DROP TABLE", + "sql_snippets.builtin.dro.description": "DROP TABLE template", + "sql_snippets.builtin.grp.name": "GROUP BY", + "sql_snippets.builtin.grp.description": "Aggregate query template with GROUP BY", + "sql_snippets.builtin.ljo.name": "LEFT JOIN", + "sql_snippets.builtin.ljo.description": "LEFT JOIN template", + "sql_snippets.builtin.sub.name": "Subquery", + "sql_snippets.builtin.sub.description": "IN subquery template", + "sql_snippets.builtin.lim.name": "LIMIT Query", + "sql_snippets.builtin.lim.description": "Pagination query template with LIMIT", + "sql_snippets.builtin.ord.name": "ORDER BY", + "sql_snippets.builtin.ord.description": "Query template with sorting", + "sql_snippets.builtin.seld.name": "SELECT by Date", + "sql_snippets.builtin.seld.description": "SELECT query filtered by date, prefilled with today's date", + "sql_snippets.builtin.ctt.name": "CREATE TABLE (with time columns)", + "sql_snippets.builtin.ctt.description": "Table creation template with created_at / updated_at time columns", + "sql_snippets.builtin.inst.name": "INSERT (with timestamp)", + "sql_snippets.builtin.inst.description": "INSERT template prefilled with the current timestamp", "query_editor.message.format_failed": "Format failed: SQL syntax may be invalid.", "query_editor.message.no_format_restore_snapshot": "No pre-format SQL snapshot is available to restore.", "query_editor.message.format_restore_success": "Restored the pre-format SQL snapshot.", @@ -2213,6 +2467,40 @@ "query_editor.message.execution_multi_success": "Executed {{statements}} statements and produced {{results}} result sets.", "query_editor.message.execution_result_sets_success": "Execution finished and produced {{results}} result sets.", "query_editor.message.execution_failed_with_error": "Query execution failed: {{error}}", + "query_editor.sql_error.unknown": "Unknown error", + "query_editor.sql_error.wrapper.semantic_line": "Semantic meaning: {{label}}. {{explanation}}", + "query_editor.sql_error.wrapper.suggestion_line": "Suggestion: {{suggestion}}", + "query_editor.sql_error.wrapper.raw_line": "Raw error: {{error}}", + "query_editor.sql_error.rule.syntax.label": "SQL syntax error", + "query_editor.sql_error.rule.syntax.explanation": "Usually caused by keywords, commas, parentheses, quotes, statement order, or SQL dialect mismatch.", + "query_editor.sql_error.rule.syntax.suggestion": "Check the SQL fragment near the reported position and confirm the current data source type matches the SQL dialect.", + "query_editor.sql_error.rule.object_missing.label": "Table or object does not exist", + "query_editor.sql_error.rule.object_missing.explanation": "The SQL references a table, view, sequence, or other database object that cannot be found in the current database or schema.", + "query_editor.sql_error.rule.object_missing.suggestion": "Check the object name, casing, schema/database prefix, and whether the selected database for this query is correct.", + "query_editor.sql_error.rule.column_missing.label": "Column does not exist", + "query_editor.sql_error.rule.column_missing.explanation": "The SQL references a column that is not in the result set, is spelled differently, or does not exist on the current table.", + "query_editor.sql_error.rule.column_missing.suggestion": "Check column names, aliases, casing, table aliases, and whether the column belongs to the current FROM/JOIN object.", + "query_editor.sql_error.rule.unique_conflict.label": "Unique constraint or primary key conflict", + "query_editor.sql_error.rule.unique_conflict.explanation": "The inserted or updated data duplicates an existing value in a unique index, primary key, or unique constraint.", + "query_editor.sql_error.rule.unique_conflict.suggestion": "Check the duplicate key value and use UPDATE or UPSERT if appropriate, or adjust the unique-key field value.", + "query_editor.sql_error.rule.permission_denied.label": "Insufficient permissions", + "query_editor.sql_error.rule.permission_denied.explanation": "The current database account does not have permission to execute this SQL or access the related objects.", + "query_editor.sql_error.rule.permission_denied.suggestion": "Check account privileges, schema grants, read-only connection limits, and whether an administrator needs to grant access.", + "query_editor.sql_error.rule.type_mismatch.label": "Data type or format mismatch", + "query_editor.sql_error.rule.type_mismatch.explanation": "The value being written, compared, or converted does not match the target column or expression format.", + "query_editor.sql_error.rule.type_mismatch.suggestion": "Check dates, numbers, booleans, enum values, implicit casts, and column types; use an explicit CAST if needed.", + "query_editor.sql_error.rule.constraint_failed.label": "Constraint validation failed", + "query_editor.sql_error.rule.constraint_failed.explanation": "The data violates a foreign key, non-null, check constraint, or referential integrity rule.", + "query_editor.sql_error.rule.constraint_failed.suggestion": "Check related parent records, required fields, CHECK conditions, and whether the write order is correct.", + "query_editor.sql_error.rule.timeout_or_canceled.label": "Query timed out or was canceled", + "query_editor.sql_error.rule.timeout_or_canceled.explanation": "The SQL ran longer than the timeout limit, or execution was manually canceled.", + "query_editor.sql_error.rule.timeout_or_canceled.suggestion": "Check the SQL execution plan, filters, and indexes; narrow the query range or adjust the timeout if needed.", + "query_editor.sql_error.rule.connection_or_auth.label": "Database connection or authentication failed", + "query_editor.sql_error.rule.connection_or_auth.explanation": "The client could not connect to the database, or credentials, network, or instance state may be wrong.", + "query_editor.sql_error.rule.connection_or_auth.suggestion": "Check host, port, username, password, network reachability, proxy/SSH tunnel, and database service status.", + "query_editor.sql_error.rule.generic.label": "Database execution error", + "query_editor.sql_error.rule.generic.explanation": "The database returned an execution failure, and no more specific error type was matched.", + "query_editor.sql_error.rule.generic.suggestion": "Continue troubleshooting with the raw error, SQL fragment, and current database dialect.", "query_editor.message.cancel_no_running": "No running query to cancel.", "query_editor.message.cancel_success": "Query canceled.", "query_editor.message.cancel_failed": "Failed to cancel query: {{error}}", @@ -2481,6 +2769,7 @@ "data_grid.column_settings.show_all": "Show all", "data_grid.column_settings.show_comments": "Show column comments in header", "data_grid.column_settings.show_types": "Show column types in header", + "data_grid.aria.row_number": "Row number", "data_grid.context_menu.auto_fit_column": "Auto-fit column width to content", "data_grid.context_menu.clear_column_sort": "Clear sort for this field", "data_grid.context_menu.column_display_section": "Field display", @@ -2616,6 +2905,7 @@ "data_grid.message.auto_commit_failed": "Auto commit failed: {{detail}}", "data_grid.message.auto_commit_success": "Auto commit succeeded", "data_grid.message.commit_failed": "Commit failed: {{detail}}", + "data_grid.message.rollback_failed": "Rollback failed: {{detail}}", "data_grid.message.undo_added_row_hint": "For new rows, use Delete selected or full-table rollback to undo.", "data_grid.message.undo_cell_original_missing": "The original data for this cell was not found, so it cannot be undone.", "data_grid.message.undo_cell_success": "Cell change undone", @@ -2690,6 +2980,7 @@ "data_grid.message.target_rows_cannot_only_source": "Target rows cannot be only the source row. Select another row.", "data_grid.message.target_rows_no_update": "Target rows do not need updates", "data_grid.message.transaction_committed": "Transaction committed", + "data_grid.message.transaction_rolled_back": "Transaction rolled back", "data_viewer.message.result_not_ready": "Current result set is not ready. Load data once first.", "data_viewer.message.query_failed": "Query failed", "data_viewer.message.query_timeout": "Query exceeded the connection timeout and was interrupted. Increase the connection timeout, or reduce the query scope and retry.", @@ -2707,6 +2998,7 @@ "data_viewer.read_only.reason.index_metadata_unavailable": "Unique index metadata could not be loaded, so changes cannot be submitted safely.", "data_viewer.read_only.reason.no_safe_locator": "No primary key or usable unique index was found, so changes cannot be submitted safely.", "data_viewer.read_only.reason.oracle_rowid_missing": "No primary key or usable unique index was found, and Oracle ROWID is missing from the result set, so changes cannot be submitted safely.", + "data_viewer.read_only.reason.duckdb_rowid_missing": "No primary key, usable unique index, or DuckDB rowid was found, so changes cannot be submitted safely.", "data_viewer.read_only.reason.primary_key_column_missing": "The result set is missing primary key column {{columns}}, so changes cannot be submitted safely.", "data_viewer.read_only.warning.table": "Table {{target}} remains read-only: {{reason}}", "data_viewer.read_only.warning.collection": "Collection {{target}} remains read-only: {{reason}}", @@ -2729,6 +3021,12 @@ "definition_viewer.error.query_failed_detail": "Failed to query definition: {{detail}}", "definition_viewer.field.database": "Database", "definition_viewer.field.type": "Type", + "definition_viewer.action.edit_object": "Edit object", + "definition_viewer.warning.refresh_latest_failed": "Failed to refresh the latest definition", + "definition_viewer.edit.tab_title": "Edit {{object}}: {{name}}", + "definition_viewer.edit.comment_title": "Edit {{object}}: {{name}}", + "definition_viewer.edit.comment_compatibility": "Confirm the syntax is compatible with the current database before running it", + "definition_viewer.edit.comment_empty_definition": "The current object definition is empty. Complete the DDL for {{name}} before running it", "definition_viewer.editor.unsupported_view_definition": "This database type does not support viewing view definitions", "definition_viewer.editor.unsupported_sqlite_routine_definition": "SQLite does not support function/procedure definition management", "definition_viewer.editor.unsupported_routine_definition": "This database type does not support viewing function/procedure definitions", @@ -2767,6 +3065,14 @@ "trigger_viewer.editor.sphinx.unsupported_query": "Current Sphinx instance{{version}} does not support querying trigger definitions.", "trigger_viewer.editor.sphinx.failed_message_label": "Returned failure message", "trigger_viewer.editor.sphinx.failed_message_unknown": "Returned failure message: Unknown error", + "trigger_viewer.action.edit_object": "Edit object", + "trigger_viewer.warning.refresh_latest_failed": "Failed to refresh the latest definition", + "trigger_viewer.tab.edit_trigger_title": "Edit trigger: {{name}}", + "trigger_viewer.edit_sql.header": "Edit trigger: {{name}}", + "trigger_viewer.edit_sql.replace_hint": "The table design change will drop the original trigger before creating a new one. Confirm before running.", + "trigger_viewer.edit_sql.compatibility_hint": "Review compatibility with the current database before running.", + "trigger_viewer.edit_sql.empty_definition": "The trigger definition is empty. Complete the CREATE TRIGGER statement before running.", + "trigger_viewer.edit_sql.fragment_definition": "The current data source returned only a trigger definition fragment. Complete the CREATE TRIGGER statement before running.", "data_grid.modal.export_options.all_data": "Export all data", "data_grid.modal.export_options.current_page": "Export current page ({{count}} rows)", "data_grid.modal.export_options.filtered_results": "Filtered results", @@ -2960,6 +3266,7 @@ "table_designer.message.table_comment_updated": "Table comment updated", "table_designer.message.table_name_required": "Enter a table name", "table_designer.message.target_table_required": "Enter a target table name", + "table_designer.message.duckdb_primary_key_change_unsupported": "DuckDB currently only supports adding a primary key to tables without one. Modifying or dropping an existing primary key requires rebuilding the table.", "table_designer.message.trigger_created": "Trigger created", "table_designer.message.trigger_deleted": "Trigger deleted", "table_designer.message.trigger_updated": "Trigger updated", @@ -3005,7 +3312,9 @@ "table_designer.placeholder.table_comment": "Enter a table comment", "table_designer.placeholder.table_name": "Enter a table name", "table_designer.placeholder.target_table_name": "Enter a target table name", + "table_designer.schema_sql.doris.primary_key_hint": "-- Doris primary key/Key model changes require manual migration according to the table model. MySQL-only DROP/ADD PRIMARY KEY clauses were skipped.", "table_designer.schema_sql.duckdb.comment_hint": "-- DuckDB cannot persist column comments through COMMENT ON COLUMN. The comment for column {{column}} remains only in the designer preview.", + "table_designer.schema_sql.duckdb.primary_key_hint": "-- DuckDB currently only supports adding PRIMARY KEY to tables without an existing primary key. Changing or dropping an existing primary key requires rebuilding the table.", "table_designer.schema_sql.limited_column_hint": "-- {{dialect}} column constraint, default, and comment syntax differs from MySQL. MySQL-only clauses were skipped; add dialect-specific SQL before running.", "table_designer.schema_sql.sqlite.modify_column_hint": "-- SQLite cannot alter column properties directly. Rebuild the table, migrate data, and replace the old table for column {{column}}.", "table_designer.schema_sql.sqlserver.drop_primary_key_hint": "-- SQL Server requires the original constraint name to drop the old primary key. Confirm it on the indexes tab before deleting.", @@ -3033,6 +3342,7 @@ "table_designer.sql_preview.change.comment": "Comment change", "table_designer.sql_preview.change.constraint": "Constraint change", "table_designer.sql_preview.change.create": "New table structure", + "table_designer.sql_preview.change.create_index": "Create index", "table_designer.sql_preview.change.drop": "Drop change", "table_designer.sql_preview.change.modify": "Column property change", "table_designer.sql_preview.change.rename": "Rename change", @@ -3043,6 +3353,7 @@ "table_designer.title.default_database": "Default database", "table_designer.title.schema_designer": "Schema designer", "table_designer.tab.columns": "Columns", + "table_designer.tab.edit_trigger_title": "Edit trigger: {{name}}", "table_designer.tab.foreign_keys": "Foreign keys", "table_designer.tab.indexes": "Indexes", "table_designer.tab.triggers": "Triggers", @@ -3065,6 +3376,122 @@ "redis_command.output.title": "Execution output", "redis_command.state.connection_not_found": "Connection not found", "redis_command.title.console": "Redis Console", + "db.backend.error.data_source_type_required": "Select a data source type first", + "db.backend.error.unsupported_database_type": "Unsupported database type: {{dbType}}", + "db.backend.error.clickhouse_address_required": "Enter a ClickHouse host address or connection URI", + "db.backend.error.clickhouse_http_client_protocol_version_unsupported": "Current ClickHouse HTTP port does not support client_protocol_version (common on ClickHouse 22.8); retrying with HTTP compatibility mode. If it still fails, confirm the connection protocol and port", + "db.backend.error.clickhouse_native_protocol_mismatch": "Server response does not look like a Native handshake; the current port looks more like an HTTP/HTTPS port. Select HTTP protocol, or confirm the ClickHouse Native port", + "db.backend.error.clickhouse_http_protocol_mismatch": "Server response does not look like an HTTP response; the current port looks more like a Native port. Select Native protocol, or confirm the ClickHouse HTTP port", + "db.backend.error.clickhouse_unknown_error": "Unknown error", + "db.backend.error.clickhouse_driver_detail_missing": "No driver error details were returned", + "db.backend.error.clickhouse_attempt_tls_config_failed": "Attempt {{attempt}} TLS configuration failed (protocol={{protocol}}): {{detail}}", + "db.backend.error.clickhouse_attempt_validation_failed": "Attempt {{attempt}} connection validation failed (protocol={{protocol}}): {{detail}}", + "db.backend.error.clickhouse_validation_failed_manual": "ClickHouse connection validation failed: used user-selected {{protocol}} protocol for {{host}}:{{port}}. {{detail}}", + "db.backend.error.clickhouse_validation_failed_auto": "ClickHouse connection validation failed: Automatic protocol detection failed (Native common ports 9000/9440, HTTP common ports {{httpPorts}}; for non-standard ports, specify the connection protocol manually). {{detail}}", + "db.backend.error.duckdb_driver_unavailable": "DuckDB driver is unavailable: {{detail}}", + "db.backend.error.duckdb_build_unavailable": "The current build does not include the DuckDB driver (platform={{platform}}). Enable CGO and use a supported platform (darwin/linux amd64|arm64, windows/amd64), or provide a custom library via -tags duckdb_use_lib / duckdb_use_static_lib", + "db.backend.error.connection_open_failed_prefix": "Failed to open database connection: ", + "db.backend.error.custom_driver_system_odbc_unsupported_prefix": "Failed to open database connection: custom connections do not support entering the system ODBC/JDBC driver name \"{{driver}}\" directly. Enter a Go database/sql driver name already registered by GoNavi. The current build does not register a generic ODBC driver, so connecting to InterSystems IRIS through \"{{driver}}\" is not supported yet: ", + "db.backend.error.custom_driver_unregistered_prefix": "Failed to open database connection: the custom connection driver \"{{driver}}\" is not registered in GoNavi. Enter a registered Go database/sql driver name instead of a system ODBC/JDBC driver name: ", + "db.backend.error.connection_verify_failed_prefix": "Failed to verify the established connection: ", + "db.backend.error.sqlite_file_path_required": "SQLite requires a local database file path (for example /path/to/demo.sqlite)", + "db.backend.error.sqlite_host_port_not_file_path": "SQLite requires a local database file path; the current input looks like a host address: {{dsn}}", + "db.backend.error.connection_not_open": "Connection is not open", + "db.backend.error.transaction_not_open": "Transaction is not open", + "db.backend.error.transaction_already_finished": "Transaction has already finished", + "db.backend.error.mqtt_connect_timeout": "MQTT connection timed out", + "db.backend.error.mqtt_subscribe_timeout": "MQTT subscription timed out", + "db.backend.error.mqtt_publish_timeout": "MQTT publish timed out", + "db.backend.action.delete": "Delete", + "db.backend.action.update": "Update", + "db.backend.error.row_delete_failed": "Delete failed: {{detail}}", + "db.backend.error.row_action_not_effective_rows_affected_unknown": "{{action}} did not take effect: could not determine affected rows: {{detail}}", + "db.backend.error.row_action_not_effective_no_rows_matched": "{{action}} did not take effect: no rows matched", + "db.backend.error.row_action_not_effective_multiple_rows": "{{action}} did not take effect: affected {{count}} rows; expected exactly 1", + "db.backend.error.row_update_key_conditions_required": "Update operation requires key conditions", + "db.backend.error.row_update_failed": "Update failed: {{detail}}", + "db.backend.error.batch_insert_quote_column_required": "Column quoting function is required", + "db.backend.error.batch_insert_placeholder_required": "Placeholder function is required", + "db.backend.error.batch_insert_exec_required": "Execution function is required", + "db.backend.error.batch_insert_literal_required": "Literal function is required", + "db.backend.error.batch_insert_failed": "Insert failed: {{detail}}", + "db.backend.error.batch_insert_failed_with_sql": "Insert failed: {{detail}}; SQL={{sql}}", + "db.backend.error.batch_insert_no_rows_affected": "Insert did not take effect: no rows were affected", + "db.backend.error.mongo_member_discovery_unsupported": "The current MongoDB driver does not support member discovery", + "db.backend.error.http_tunnel_proxy_conflict": "HTTP Tunnel cannot be enabled together with a regular proxy", + "db.backend.error.http_tunnel_host_required": "HTTP Tunnel host is required", + "db.backend.error.http_tunnel_port_invalid": "HTTP Tunnel port is invalid: {{port}}", + "db.backend.error.proxy_ssh_gateway_connect_failed": "Failed to connect to the SSH gateway through the proxy: {{detail}}", + "db.backend.error.proxy_target_port_invalid": "Target port is invalid: {{port}}", + "db.backend.error.proxy_local_forward_addr_parse_failed": "Failed to parse the local proxy forward address: {{address}}", + "db.backend.message.connect_timeout_detail": "Database connection timed out: {{dbType}} {{host}}:{{port}}/{{database}}: {{detail}}", + "db.backend.message.connect_failure_cooldown": "The connection failed recently and is in cooldown. Retry after {{remaining}}; last error: {{detail}}", + "db.backend.message.connect_success": "Connection succeeded", + "db.backend.message.mongo_primary_credentials_label": "primary credentials", + "db.backend.message.mongo_replica_credentials_label": "replica credentials", + "db.backend.message.release_success": "Connection released", + "db.backend.message.mongo_members_discovered": "Discovered {{count}} members", + "db.backend.error.schema_name_required": "Schema name is required", + "db.backend.error.schema_create_unsupported": "The current data source ({{dbType}}) does not support creating schemas from this entry point", + "db.backend.error.schema_same_name": "The old and new schema names must be different", + "db.backend.error.schema_rename_unsupported": "The current data source ({{dbType}}) does not support renaming schemas from this entry point", + "db.backend.error.schema_drop_unsupported": "The current data source ({{dbType}}) does not support dropping schemas from this entry point", + "db.backend.error.target_database_required": "Target database is required", + "db.backend.message.schema_created": "Schema created", + "db.backend.message.schema_renamed": "Schema renamed", + "db.backend.message.schema_dropped": "Schema dropped", + "db.backend.error.database_name_required": "Database name is required", + "db.backend.error.database_create_sphinx_unsupported": "Sphinx does not support creating databases", + "db.backend.error.database_create_user_schema_unsupported": "The current data source ({{dbType}}) treats databases as users/schemas and does not support creating them from this entry point. Use the SQL editor to run a CREATE USER statement", + "db.backend.error.database_same_name": "The old and new database names must be different", + "db.backend.error.database_rename_direct_unsupported": "MySQL/MariaDB/OceanBase/StarRocks/Sphinx does not support direct database renaming. Create a new database and migrate the data instead", + "db.backend.error.database_rename_unsupported": "The current data source ({{dbType}}) does not support renaming databases", + "db.backend.error.database_drop_unsupported": "The current data source ({{dbType}}) does not support dropping databases", + "db.backend.message.database_created": "Database created", + "db.backend.message.database_renamed": "Database renamed", + "db.backend.message.database_dropped": "Database dropped", + "db.backend.error.multi_statement_execution_failed": "Statement {{index}} failed: {{detail}}", + "db.backend.error.multi_statement_previous_success": " ({{count}} previous statements succeeded)", + "db.backend.message.multi_statement_sequential_fallback": "The current data source ({{dbType}}) does not support native multi-statement execution. It was automatically split into {{count}} statements and executed sequentially.", + "db.backend.error.managed_transaction_unsupported": "The current data source ({{dbType}}) does not support SQL editor managed transactions", + "db.backend.error.transaction_query_unsupported": "The current transaction session does not support query statements", + "db.backend.error.transaction_id_required": "Transaction ID is required", + "db.backend.error.transaction_not_found": "Transaction not found or already finished", + "db.backend.error.transaction_commit_failed": "Transaction commit failed: {{detail}}", + "db.backend.error.transaction_rollback_failed": "Transaction rollback failed: {{detail}}", + "db.backend.error.transaction_commit_close_failed": "Transaction committed, but closing the session failed: {{detail}}", + "db.backend.error.transaction_rollback_close_failed": "Transaction rolled back, but closing the session failed: {{detail}}", + "db.backend.message.transaction_committed": "Transaction committed", + "db.backend.message.transaction_rolled_back": "Transaction rolled back", + "db.backend.error.table_name_required": "Table name is required", + "db.backend.error.clickhouse_delete_failed_with_sql": "Failed to delete ClickHouse rows: {{detail}}; SQL={{sql}}", + "db.backend.error.clickhouse_update_failed_with_sql": "Failed to update ClickHouse rows: {{detail}}; SQL={{sql}}", + "db.backend.error.tdengine_apply_changes_insert_only": "TDengine targets currently support only INSERT writes; UPDATE/DELETE differences are not supported by ApplyChanges", + "db.backend.error.oracle_column_metadata_load_failed": "Failed to load column metadata (table={{table}}): {{detail}}; check ALL_TAB_COLUMNS query permission and whether the table exists", + "db.backend.error.create_table_statement_not_found": "The CREATE TABLE statement was not found", + "db.backend.error.oceanbase_oracle_show_create_table_fallback_failed": "{{metadataDetail}}; OceanBase Oracle SHOW CREATE TABLE fallback failed: {{showDetail}}", + "db.backend.error.table_columns_missing_for_ddl": "No column definitions were retrieved, so the CREATE TABLE statement could not be generated", + "db.backend.error.table_columns_empty_for_ddl": "The retrieved column definitions were empty, so the CREATE TABLE statement could not be generated", + "db.backend.error.table_same_name": "The old and new table names must be different", + "db.backend.error.table_new_name_no_qualifier": "The new table name must not include a schema or database prefix", + "db.backend.error.table_rename_unsupported": "The current data source ({{dbType}}) does not support renaming tables", + "db.backend.error.old_table_name_required": "The old table name is required", + "db.backend.error.table_drop_unsupported": "The current data source ({{dbType}}) does not support dropping tables", + "db.backend.message.table_renamed": "Table renamed", + "db.backend.message.table_dropped": "Table dropped", + "db.backend.error.view_name_required": "View name is required", + "db.backend.error.view_same_name": "The old and new view names must be different", + "db.backend.error.view_new_name_no_qualifier": "The new view name must not include a schema or database prefix", + "db.backend.error.view_rename_unsupported": "The current data source ({{dbType}}) does not support renaming views", + "db.backend.error.old_view_name_required": "Old view name is required", + "db.backend.error.view_drop_unsupported": "The current data source ({{dbType}}) does not support dropping views", + "db.backend.message.view_renamed": "View renamed", + "db.backend.message.view_dropped": "View dropped", + "db.backend.error.routine_name_required": "Function or procedure name is required", + "db.backend.error.routine_drop_unsupported": "The current data source ({{dbType}}) does not support dropping functions or procedures", + "db.backend.error.duckdb_procedure_drop_unsupported": "DuckDB does not support stored procedures yet", + "db.backend.message.function_dropped": "Function dropped", + "db.backend.message.procedure_dropped": "Stored procedure dropped", "redis.backend.message.connect_success": "Connection succeeded", "redis.backend.message.set_success": "Set succeeded", "redis.backend.message.select_db_success": "Database switched", @@ -3079,6 +3506,20 @@ "redis.backend.error.node_address_required": "Redis node address cannot be empty", "redis.backend.error.invalid_node_address": "Invalid Redis node address: {{address}}", "redis.backend.error.invalid_port": "Invalid Redis port: {{address}}", + "redis.backend.label.topology_sentinel": "Sentinel", + "redis.backend.label.topology_cluster": "Cluster", + "redis.backend.label.topology_multi_node": "multi-node", + "redis.backend.error.topology_ssh_tunnel_unsupported": "Redis {{topology}} mode does not support SSH tunnels yet. Disable SSH and try again.", + "redis.backend.error.sentinel_master_required": "Redis Sentinel mode requires a master name", + "redis.backend.error.connect_tls_setup_failed": "Attempt {{attempt}} TLS setup failed: {{detail}}", + "redis.backend.error.connect_attempt_failed": "Attempt {{attempt}} connection failed: {{detail}}", + "redis.backend.error.sentinel_connect_failed": "Redis Sentinel connection failed: {{detail}}", + "redis.backend.error.cluster_connect_failed": "Redis Cluster connection failed: {{detail}}", + "redis.backend.error.connect_failed": "Redis connection failed: {{detail}}", + "redis.backend.error.ssh_tunnel_create_failed": "Failed to create the SSH tunnel: {{detail}}", + "redis.backend.error.select_db_index_required": "SELECT command requires a database index", + "redis.backend.error.select_db_index_invalid": "Invalid database index: {{value}}", + "redis.backend.error.select_db_index_out_of_range": "Database index must be between {{min}} and {{max}}", "redis_monitor.action.pause_refresh": "Pause refresh", "redis_monitor.action.refresh_now": "Refresh now", "redis_monitor.action.resume_refresh": "Resume refresh", @@ -3253,6 +3694,7 @@ "ai_chat.header.action.export": "Export", "ai_chat.history.action.new_chat": "Start new chat", "ai_chat.history.default_session_title": "New chat", + "ai_chat.history.empty.no_history": "No history yet", "ai_chat.history.empty.no_matches": "No matching chats", "ai_chat.history.search.placeholder": "Search history...", "ai_chat.history.title": "Chat history", @@ -3297,9 +3739,21 @@ "ai_chat.welcome.suggestion.low_rows.with_context": "Why does {{table}} have only a few rows?", "ai_chat.input.action.send": "Send", "ai_chat.input.action.stop": "Stop generating", + "ai_chat.input.context.add": "Add", + "ai_chat.input.context.current_count": "Current context · {{count}}", "ai_chat.input.context.connection_tooltip": "Current data query context", + "ai_chat.input.context.label": "Attached context", "ai_chat.input.context.memory_tooltip": "Current session memory usage. Auto-compression starts when it reaches the {{limit}} limit.", "ai_chat.input.context.tag_label": "Linked context ({{count}})", + "ai_chat.input.context.selector.cancel": "Cancel", + "ai_chat.input.context.selector.confirm": "Sync selected tables to context", + "ai_chat.input.context.selector.database_placeholder": "Switch database", + "ai_chat.input.context.selector.empty_no_match": "No tables matching '{{searchText}}' were found", + "ai_chat.input.context.selector.empty_no_tables": "No tables available to attach in the current database", + "ai_chat.input.context.selector.invert_selection": "Invert matching selection", + "ai_chat.input.context.selector.search_placeholder": "Search table names in the current database...", + "ai_chat.input.context.selector.select_all": "Select all matching tables ({{count}})", + "ai_chat.input.context.selector.title": "Attach table schemas as context", "ai_chat.input.message.context_added": "Added {{count}} table structures to the context", "ai_chat.input.message.context_load_failed": "Failed to load table context: {{detail}}", "ai_chat.input.message.context_removed": "Removed {{count}} table structures from the context", @@ -3308,6 +3762,7 @@ "ai_chat.input.message.fetch_tables_failed": "Failed to load tables: {{detail}}", "ai_chat.input.message.select_database_context_first": "Select a database on the left before attaching chat context", "ai_chat.input.message.selection_unchanged": "Selected tables did not change", + "ai_chat.input.message.context_sync_failed": "Failed to sync AI context: {{detail}}", "ai_chat.input.modal.empty_tables": "No tables match '{{query}}'", "ai_chat.input.modal.invert_matching": "Invert matching results", "ai_chat.input.modal.ok": "Sync selected tables to context", @@ -3316,7 +3771,90 @@ "ai_chat.input.modal.switch_database.placeholder": "Switch database", "ai_chat.input.modal.title": "Attach database table schema context", "ai_chat.input.model.placeholder": "Select model", - "ai_chat.input.placeholder": "Type a message... (Enter to send, Shift+Enter for newline, / for commands)", + "ai_chat.input.placeholder": "Type a message... ({{shortcut}}, Shift+Enter for newline, / for commands)", + "ai_chat.input.placeholder_compact": "Type a message... {{shortcut}} · / commands", + "ai_chat.input.shortcut.disabled": "Shortcut sending disabled", + "ai_chat.input.shortcut.send_with_combo": "{{shortcut}} to send", + "ai_chat.mcp_client.install.status_tone.connected": "Connected", + "ai_chat.mcp_client.install.status_tone.update_required": "Update needed", + "ai_chat.mcp_client.install.status_tone.remote_bridge": "Remote bridge", + "ai_chat.mcp_client.install.status_tone.status_error": "Status error", + "ai_chat.mcp_client.install.status_tone.not_connected": "Not connected", + "ai_chat.mcp_client.install.state.connected": "External tool connection status: connected to this GoNavi", + "ai_chat.mcp_client.install.state.stale": "External tool connection status: old config found, update needed", + "ai_chat.mcp_client.install.state.error": "External tool connection status: failed to read", + "ai_chat.mcp_client.install.state.remote": "External tool connection status: remote MCP bridge required", + "ai_chat.mcp_client.install.state.missing": "External tool connection status: not connected", + "ai_chat.mcp_client.install.summary.connected": "{{label}} is connected to this GoNavi MCP and can call it directly.", + "ai_chat.mcp_client.install.summary.stale": "{{label}} already has an old GoNavi entry. Updating will point it to this GoNavi.", + "ai_chat.mcp_client.install.summary.error": "Failed to read the connection status for {{label}}. Refresh detection first.", + "ai_chat.mcp_client.install.summary.remote": "{{label}} usually runs in the cloud or on another machine and needs a remote MCP bridge to call this GoNavi.", + "ai_chat.mcp_client.install.summary.missing": "This GoNavi MCP is not connected to {{label}} yet.", + "ai_chat.mcp_client.install.option.connected": "This GoNavi MCP is already connected to this client.", + "ai_chat.mcp_client.install.option.stale": "An old GoNavi entry was detected. Update it to the current install path.", + "ai_chat.mcp_client.install.option.error": "Connection status looks abnormal. Refresh before changing it.", + "ai_chat.mcp_client.install.option.remote": "For cloud Agents: schema-only reads GoNavi structure by default, without copying database passwords or exposing execute_sql.", + "ai_chat.mcp_client.install.option.missing": "Current GoNavi MCP is not connected here yet.", + "ai_chat.mcp_client.install.detection.remote": "{{label}} usually does not run on this Windows machine. No local {{command}} command detection is needed; configure a remote MCP bridge URL in the cloud.", + "ai_chat.mcp_client.install.detection.detected": "Detected local {{command}} command. After connecting or updating, restart {{label}} to verify.", + "ai_chat.mcp_client.install.detection.not_detected": "Local {{command}} command was not detected. If the CLI is not in PATH yet, you can still write {{label}} config first and restart later.", + "ai_chat.mcp_client.install.selected.connected": "Connected to current GoNavi; no repeated action needed", + "ai_chat.mcp_client.install.selected.stale": "Old connection record exists; update it to the current GoNavi path", + "ai_chat.mcp_client.install.selected.error": "Status read is abnormal; refresh detection first", + "ai_chat.mcp_client.install.selected.remote": "Configure a remote MCP bridge; database passwords stay on the GoNavi machine", + "ai_chat.mcp_client.install.selected.missing": "GoNavi MCP is not connected yet", + "ai_chat.mcp_client.install.action.connected": "{{label}} is connected; no reinstall needed", + "ai_chat.mcp_client.install.action.update": "Update {{label}} connection config", + "ai_chat.mcp_client.install.action.copy_remote": "Copy {{label}} remote connection guide", + "ai_chat.mcp_client.install.action.install": "Install to {{label}} (external tool)", + "ai_chat.mcp_client.install.intro.title": "This connects GoNavi MCP to Claude Code / Codex / OpenClaw / Hermans for external tool calls. It is not installing a plugin into GoNavi itself.", + "ai_chat.mcp_client.install.intro.description": "Claude Code and Codex write local user-level MCP config. Cloud Agents such as OpenClaw and Hermans use remote connection guidance so database passwords are not copied to the cloud.", + "ai_chat.mcp_client.install.repeat_avoidance": "When already connected to this GoNavi, the main button is disabled to avoid repeated writes.", + "ai_chat.mcp_client.install.selector.title": "Connect external client", + "ai_chat.mcp_client.install.selector.description": "Choose one target client first. Local CLIs can write or update config automatically; remote Agents must access current GoNavi through an MCP bridge or tunnel and should not store database passwords.", + "ai_chat.mcp_client.install.selector.aria_label": "Select the external client for GoNavi MCP", + "ai_chat.mcp_client.install.selector.choice_title": "Select external client", + "ai_chat.mcp_client.install.selector.step.target.title": "Choose target client", + "ai_chat.mcp_client.install.selector.step.target.detail": "Local Claude/Codex can be installed automatically. OpenClaw/Hermans use remote connection guidance.", + "ai_chat.mcp_client.install.selector.step.write.title": "Write or copy config", + "ai_chat.mcp_client.install.selector.step.write.detail": "Automatic install only changes user-level MCP config. Remote Agents copy bridge guidance.", + "ai_chat.mcp_client.install.selector.step.restart.title": "Restart or configure target", + "ai_chat.mcp_client.install.selector.step.restart.detail": "Restart the local CLI to verify. Cloud Agents verify after configuring the remote MCP URL.", + "ai_chat.mcp_client.install.selector.hint.active_remote": "Selected. The remote connection guide will be copied.", + "ai_chat.mcp_client.install.selector.hint.active_local": "Selected. Only this client will be written or updated.", + "ai_chat.mcp_client.install.selector.hint.inactive_remote": "Click to view the remote connection method.", + "ai_chat.mcp_client.install.selector.hint.inactive_local": "Click to switch to this client.", + "ai_chat.mcp_client.install.status.title": "Selected client status", + "ai_chat.mcp_client.install.status.current_target": "Current target client: {{label}}", + "ai_chat.mcp_client.install.status.no_client": "No client selected", + "ai_chat.mcp_client.install.status.current_state": "Current status: {{status}}", + "ai_chat.mcp_client.install.status.remote_boundary": "Remote connection boundary: database connection info and passwords stay in Windows GoNavi. Cloud Agents read connection summaries, tables, and DDL through schema-only MCP tools by default, and execute_sql is not registered. For cross-machine access, use GoNavi Streamable HTTP mode with a token, tunnel, or reverse proxy.", + "ai_chat.mcp_client.install.status.cli_prefix": "CLI detection: {{status}}", + "ai_chat.mcp_client.install.status.cli.remote": "Remote Agent does not need local {{command}} command detection", + "ai_chat.mcp_client.install.status.cli.detected": "Detected {{command}}", + "ai_chat.mcp_client.install.status.cli.not_detected": "{{command}} was not detected; config can still be written first", + "ai_chat.mcp_client.install.status.command_path": "Command path: {{path}}", + "ai_chat.mcp_client.install.status.detection_result": "Detection result: {{message}}", + "ai_chat.mcp_client.install.status.detection_missing": "No connection status detected", + "ai_chat.mcp_client.install.status.config_file": "Config file: {{path}}", + "ai_chat.mcp_client.install.status.launch_command": "Launch command: {{command}}", + "ai_chat.mcp_client.install.status.refresh": "Refresh status", + "ai_chat.mcp_client.install.status.copy_config": "Copy config path", + "ai_chat.mcp_client.install.status.copy_command": "Copy launch command", + "ai_chat.mcp_client.install.message.refresh_failed": "Failed to refresh client installation status", + "ai_chat.mcp_client.install.message.remote_guide_copied": "{{label}} remote connection guide copied", + "ai_chat.mcp_client.install.message.remote_guide_copy_failed": "Failed to copy {{label}} remote connection guide", + "ai_chat.mcp_client.install.message.already_connected": "{{label}} is already connected to current GoNavi MCP. No repeated write is needed.", + "ai_chat.mcp_client.install.message.codex_not_supported": "This version does not support automatic Codex MCP installation yet", + "ai_chat.mcp_client.install.message.claude_not_supported": "This version does not support automatic Claude Code MCP installation yet", + "ai_chat.mcp_client.install.message.install_success": "Wrote {{label}} user-level MCP config", + "ai_chat.mcp_client.install.message.install_failed": "Failed to install {{label}} MCP", + "ai_chat.mcp_client.install.message.config_path_missing": "No config file path is available to copy", + "ai_chat.mcp_client.install.message.config_path_copied": "Config file path copied", + "ai_chat.mcp_client.install.message.config_path_copy_failed": "Failed to copy config file path", + "ai_chat.mcp_client.install.message.launch_command_missing": "No launch command is available to copy", + "ai_chat.mcp_client.install.message.launch_command_copied": "Launch command copied", + "ai_chat.mcp_client.install.message.launch_command_copy_failed": "Failed to copy launch command", "ai_chat.input.slash.diff.desc": "Compare two tables and generate changes", "ai_chat.input.slash.diff.label": "🔄 Table diff", "ai_chat.input.slash.diff.prompt": "Compare the structure differences between these two tables and generate ALTER statements to migrate from the old version to the new version:", @@ -3341,8 +3879,149 @@ "ai_chat.input.slash.sql.desc": "Describe requirements and generate statements", "ai_chat.input.slash.sql.label": "📝 Generate SQL", "ai_chat.input.slash.sql.prompt": "Generate SQL from the following requirements:", + "ai_chat.input.slash.category.generate.title": "SQL generation", + "ai_chat.input.slash.category.generate.description": "Generate SQL, test data, or migration drafts directly.", + "ai_chat.input.slash.category.review.title": "Structure review", + "ai_chat.input.slash.category.review.description": "Explain SQL and review table design and index strategies.", + "ai_chat.input.slash.category.diagnose.title": "Diagnostic probes", + "ai_chat.input.slash.category.diagnose.description": "Run built-in probes first to inspect the real state of AI, MCP, and recent SQL activity.", + "ai_chat.input.slash.empty.title": "No matching slash commands", + "ai_chat.input.slash.empty.description": "Try these common entries first to jump into SQL generation, AI health checks, or MCP diagnostics.", + "ai_chat.input.slash.empty.summary": "There are {{count}} slash commands available. Search by command name, description, or keyword.", + "ai_chat.input.slash.health.label": "🩺 AI health check", + "ai_chat.input.slash.health.desc": "Run health probes for the current AI setup", + "ai_chat.input.slash.health.prompt": "Call inspect_ai_setup_health first. Run a full health check of the current GoNavi AI setup, then summarize blockers, warnings, and nextActions.", + "ai_chat.input.slash.tools.label": "🧰 Tool catalog", + "ai_chat.input.slash.tools.desc": "Pick the right built-in probe by keyword", + "ai_chat.input.slash.tools.prompt": "Call inspect_ai_tool_catalog first. Filter recommended flows, built-in tool parameter hints, and the current MCP tool summary by my question keywords, then tell me which tool I should call next. Keywords:", + "ai_chat.input.slash.budget.label": "🧠 Context budget", + "ai_chat.input.slash.budget.desc": "Inspect message, DDL, MCP schema, and Skills size", + "ai_chat.input.slash.budget.prompt": "Call inspect_ai_context_budget first. Inspect the size risks of the current conversation messages, tool results, DDL, MCP schema, prompts, and Skills, then tell me which context I should narrow down.", + "ai_chat.input.slash.hotspots.label": "🧱 Code hotspots", + "ai_chat.input.slash.hotspots.desc": "Review large-file split candidates and test scope", + "ai_chat.input.slash.hotspots.prompt": "Call inspect_codebase_hotspots first. Read the current GoNavi frontend large-file hotspots, suggested split slices, and testing targets, then tell me which file is the best next split candidate, what boundary to use, and which verification to run. Keywords:", + "ai_chat.input.slash.mcp.label": "🪛 Troubleshoot MCP setup", + "ai_chat.input.slash.mcp.desc": "Check MCP services and external client status", + "ai_chat.input.slash.mcp.prompt": "Call inspect_mcp_setup first. Audit the current MCP services, tool discovery results, and the connection status of local Claude Code / Codex clients plus remote OpenClaw / Hermans agents.", + "ai_chat.input.slash.mcpfail.label": "🧯 MCP runtime failures", + "ai_chat.input.slash.mcpfail.desc": "Read recent MCP startup, discovery, and call failure logs", + "ai_chat.input.slash.mcpfail.prompt": "Call inspect_mcp_runtime_failures first. Read the recent MCP startup, tool discovery, tool call, stdio, Docker, or HTTP MCP failure logs, then combine them with the current MCP service configuration to judge the cause and nextActions. Keywords or service name:", + "ai_chat.input.slash.mcpadd.label": "🧭 Add MCP guidance", + "ai_chat.input.slash.mcpadd.desc": "See how command, args, env, and templates should be filled", + "ai_chat.input.slash.mcpadd.prompt": "Call inspect_mcp_authoring_guide first; if I pasted a full startup command or draft, also call inspect_mcp_draft to simulate the fields and validation issues. Then combine inspect_mcp_setup and tell me how command, args, env, and timeout should be filled when adding a GoNavi MCP service, plus which template is the closest fit.", + "ai_chat.input.slash.mcpdraft.label": "🧪 MCP draft validation", + "ai_chat.input.slash.mcpdraft.desc": "Validate how to split one MCP startup command", + "ai_chat.input.slash.mcpdraft.prompt": "Call inspect_mcp_draft first to validate the MCP fullCommand or command/args/env/timeout draft I provide. Return the auto-split result, startup preview, suggestedServerSeed, errors, warnings, and nextActions; if field guidance is still missing, then also call inspect_mcp_authoring_guide.", + "ai_chat.input.slash.mcptool.label": "🧩 MCP tool arguments", + "ai_chat.input.slash.mcptool.desc": "Review MCP tool schema and arguments format", + "ai_chat.input.slash.mcptool.prompt": "Call inspect_mcp_setup first to find the currently discovered MCP tool alias. If I already gave a tool name or keyword, also call inspect_mcp_tool_schema to read the matching inputSchema, then tell me the required parameters, field types, enum values, nested paths, and how the arguments JSON should be written.", + "ai_chat.input.slash.connfail.label": "🧯 Connection failure probe", + "ai_chat.input.slash.connfail.desc": "Summarize recent connection failures, cooldowns, and validation errors", + "ai_chat.input.slash.connfail.prompt": "Call inspect_recent_connection_failures first. Summarize the real log conclusions for recent database connection failures, connection cooldowns, validation failures, and SSH tunnel exceptions; if there is already a clear address or type, then use inspect_current_connection or inspect_saved_connections to narrow it further.", + "ai_chat.input.slash.shortcuts.label": "⌨️ Shortcut probe", + "ai_chat.input.slash.shortcuts.desc": "Read the current Win/Mac shortcut configuration", + "ai_chat.input.slash.shortcuts.prompt": "Call inspect_shortcuts first. Tell me the current GoNavi shortcut configuration, especially how to execute SQL, switch the results area, open the AI panel, and send AI messages on the current platform and the other platform, and whether any defaults were changed.", + "ai_chat.input.slash.applog.label": "🪵 App logs", + "ai_chat.input.slash.applog.desc": "Review recent GoNavi application logs", + "ai_chat.input.slash.applog.prompt": "Call inspect_app_logs first. Check the recent errors and warnings in the GoNavi application logs; if I mention connection failures, MCP startup failures, startup exceptions, or gonavi.log, prioritize filtering with those keywords.", + "ai_chat.input.slash.airender.label": "🧯 AI render failure", + "ai_chat.input.slash.airender.desc": "Read the most recent AI message render failure record", + "ai_chat.input.slash.airender.prompt": "Call inspect_ai_last_render_error first. Tell me which message failed in the most recent AI render failure record, what the error summary says, and what I should check next.", + "ai_chat.input.slash.safety.label": "🛡️ Write safety", + "ai_chat.input.slash.safety.desc": "Confirm read-only vs write boundaries and allowMutating", + "ai_chat.input.slash.safety.prompt": "Call inspect_ai_safety first. Tell me the current write boundaries for AI and GoNavi MCP, whether they are read-only, and whether execute_sql requires allowMutating.", + "ai_chat.input.slash.activity.label": "🕘 Recent SQL activity", + "ai_chat.input.slash.activity.desc": "Summarize recent executions, errors, and hotspots", + "ai_chat.input.slash.activity.prompt": "Call inspect_recent_sql_activity first. Summarize the recent SQL activity, error hotspots, and the main read/write patterns.", + "ai_chat.input.slash.tx.label": "🔁 SQL transaction status", + "ai_chat.input.slash.tx.desc": "Review SQL editor commit mode and pending transactions", + "ai_chat.input.slash.tx.prompt": "Call inspect_sql_editor_transaction first. Tell me the current SQL editor DML-managed transaction semantics, manual vs automatic commit settings, whether the active SQL tabs enter a transaction, whether there are pending transactions, and whether I should commit, roll back, or keep executing next.", + "ai_chat.input.slash.query.keywords": "query|natural language|data lookup", + "ai_chat.input.slash.sql.keywords": "sql|generate|query statement", + "ai_chat.input.slash.mock.keywords": "mock|test data|insert", + "ai_chat.input.slash.diff.keywords": "diff|migration|alter", + "ai_chat.input.slash.explain.keywords": "explain|sql|logic", + "ai_chat.input.slash.optimize.keywords": "optimize|index|performance", + "ai_chat.input.slash.schema.keywords": "schema|table structure|design", + "ai_chat.input.slash.index.keywords": "index|slow query|performance", + "ai_chat.input.slash.health.keywords": "health|ai config|probe", + "ai_chat.input.slash.tools.keywords": "tool catalog|built-in tools|toolcatalog|parameter hints|arguments|probe route", + "ai_chat.input.slash.budget.keywords": "context|budget|large context|slow response|schema too large|tool results", + "ai_chat.input.slash.hotspots.keywords": "large file|bloated|split|refactor|hotspots|code hotspots|thousands of lines", + "ai_chat.input.slash.mcp.keywords": "mcp|codex|claude|openclaw|hermans|external client", + "ai_chat.input.slash.mcpfail.keywords": "mcpfail|mcp failure|runtime failure|zero tools discovered|stdio|docker mcp|http mcp|startup failure|call failure", + "ai_chat.input.slash.mcpadd.keywords": "add mcp|command|args|env|template", + "ai_chat.input.slash.mcpdraft.keywords": "mcp draft|mcp validation|fullcommand|startup command|argument splitting|command|args|env", + "ai_chat.input.slash.mcptool.keywords": "mcp tool|mcp tool arguments|schema|arguments|parameters|tool call|inputschema", + "ai_chat.input.slash.connfail.keywords": "connection failure|cooldown|validation failure|ssh|mysql", + "ai_chat.input.slash.shortcuts.keywords": "shortcuts|result area|mac|windows", + "ai_chat.input.slash.applog.keywords": "logs|gonavi.log|mcp error|connection failure|startup exception", + "ai_chat.input.slash.airender.keywords": "render failure|blank bubble|ai message|render|white block", + "ai_chat.input.slash.safety.keywords": "safety|read-only|allowmutating|ddl|dml", + "ai_chat.input.slash.activity.keywords": "activity|sql logs|recent executions|error", + "ai_chat.input.slash.tx.keywords": "transaction|commit|auto commit|manual commit|pending transaction|dml", "ai_chat.input.tooltip.attach_table_context": "Attach database table context", + "ai_chat.input.tooltip.slash_command": "Slash commands", + "ai_chat.input.tooltip.upload_attachment": "Upload attachment (images, Markdown, Word, Excel, PDF, text)", "ai_chat.input.tooltip.upload_image": "Upload image or screenshot", + "ai_chat.input.attachment.remove_file": "Remove attachment", + "ai_chat.input.attachment.remove_image": "Remove image", + "ai_chat.input.attachment.kind.text": "Text", + "ai_chat.input.attachment.kind.image": "Image", + "ai_chat.input.attachment.kind.file": "File", + "ai_chat.input.attachment.warning.pdf_partial_text": "PDF used lightweight text extraction; scanned or compressed-font content may not be fully readable.", + "ai_chat.input.attachment.warning.pdf_no_text": "No readable text was extracted from the PDF; if it is scanned or uses complex encoding, copy the body before sending.", + "ai_chat.input.attachment.warning.legacy_office_partial_text": "Legacy Office binary files only use lightweight text snippet extraction; convert to docx/xlsx before uploading for more complete content.", + "ai_chat.input.attachment.warning.too_large": "File exceeds {{size}}; file metadata was attached but the body was not read.", + "ai_chat.input.attachment.warning.unsupported_type": "This file type was attached, but body text was not extracted yet; use markdown, txt, docx, xlsx, or pdf if the model needs the content.", + "ai_chat.input.attachment.warning.extract_failed": "Attachment body extraction failed: {{detail}}", + "ai_chat.input.attachment.excel.worksheet_header": "[Worksheet: {{sheetName}}]", + "ai_chat.input.attachment.kind.markdown": "Markdown", + "ai_chat.input.attachment.kind.pdf": "PDF", + "ai_chat.input.attachment.kind.word": "Word", + "ai_chat.input.attachment.kind.excel": "Excel", + "ai_chat.input.attachment.kind.document": "File", + "ai_chat.input.attachment.prompt.content_truncated": "[Attachment body truncated]", + "ai_chat.input.attachment.prompt.heading": "### Attachment {{index}}: {{name}}", + "ai_chat.input.attachment.prompt.kind": "- Type: {{kind}}", + "ai_chat.input.attachment.prompt.mime": "- MIME: {{mimeType}}", + "ai_chat.input.attachment.prompt.size": "- Size: {{size}}", + "ai_chat.input.attachment.prompt.extract_warning": "- Extraction note: {{message}}", + "ai_chat.input.attachment.prompt.text_truncated": "- Extraction note: Body text was truncated before sending.", + "ai_chat.input.attachment.prompt.no_text": "No readable attachment body was extracted.", + "ai_chat.input.attachment.prompt.default_user_content": "Continue based on the following attachment content.", + "ai_chat.input.attachment.prompt.wrapper_start": "", + "ai_chat.input.attachment.prompt.wrapper_end": "", + "ai_chat.input.attachment.message.warning": "{{name}}: {{message}}", + "ai_chat.input.attachment.message.read_failed": "Failed to read attachment {{name}}: {{detail}}", + "ai_chat.input.status.label.not_ready": "Not ready", + "ai_chat.input.status.label.needs_fix": "Needs fix", + "ai_chat.input.status.label.loading": "Loading", + "ai_chat.input.status.label.model_required": "Model required", + "ai_chat.input.status.label.ready": "Ready", + "ai_chat.input.status.action.open_settings": "Open AI settings", + "ai_chat.input.status.action.fix_provider": "Fix provider configuration", + "ai_chat.input.status.action.reload_models": "Reload models", + "ai_chat.input.status.dismiss_aria_label": "Dismiss AI status notice", + "ai_chat.input.status.provider_fallback_name": "Current provider", + "ai_chat.input.status.issue.missing_secret": "API key", + "ai_chat.input.status.issue.missing_base_url": "endpoint URL", + "ai_chat.input.status.issue.missing_selected_model": "model", + "ai_chat.input.status.issue.separator": ", ", + "ai_chat.input.status.missing_provider.title.none": "No provider available", + "ai_chat.input.status.missing_provider.description.none": "Add and enable a model provider in AI settings first.", + "ai_chat.input.status.missing_provider.title.unselected": "Providers are configured, but none is currently active", + "ai_chat.input.status.missing_provider.description.unselected": "Select an active provider in AI settings before sending.", + "ai_chat.input.status.provider_incomplete.title": "{{provider}} is missing {{issues}}", + "ai_chat.input.status.provider_incomplete.description": "Complete the provider configuration before sending to avoid immediate request failures.", + "ai_chat.input.status.missing_model.title.loading": "Loading models for {{provider}}", + "ai_chat.input.status.missing_model.title.select": "Select a model for {{provider}}", + "ai_chat.input.status.missing_model.description.available": "{{count}} models are available right now. Select one before sending.", + "ai_chat.input.status.missing_model.description.empty": "If the list is empty, check the provider endpoint, API key, and model access.", + "ai_chat.input.status.ready.title": "AI is ready: {{provider}} / {{model}}", + "ai_chat.input.status.ready.description.with_context": "{{count}} table schema contexts are attached. You can send now.", + "ai_chat.input.status.ready.description.with_connection": "The current connection is selected. Attach table schema context for more precise database guidance.", + "ai_chat.input.status.ready.description.no_context": "You can send now. Select a connection or attach table schema context for more precise database guidance.", "ai_chat.tools.mcp_fallback_description": "MCP tool {{toolName}} provided by {{serverName}}", "ai_chat.composer_notice.missing_model.description": "Open the model dropdown below and select a model. If the list is empty, check the provider endpoint and API Key.", "ai_chat.composer_notice.missing_model.title": "Select a model first", @@ -3358,6 +4037,11 @@ "ai_chat.message.action.retry": "Regenerate from the previous user message", "ai_chat.message.action.copy_error_raw": "Copy raw error", "ai_chat.message.action.copied_error_raw": "Raw error copied", + "ai_chat.message.render_error.title": "This AI message failed to render and has been isolated", + "ai_chat.message.render_error.body": "The rest of the conversation is still available. You can delete this broken message before continuing.", + "ai_chat.message.render_error.unknown": "Unknown render error", + "ai_chat.message.render_error.retry": "Retry render", + "ai_chat.message.render_error.delete": "Delete this message", "ai_chat.message.role.user": "You", "ai_chat.message.image_alt": "Attached image {{index}}", "ai_chat.message.code.copy": "Copy code", @@ -3386,11 +4070,552 @@ "ai_chat.message.tool_result.title": "Probe result ({{name}})", "ai_chat.message.tool_result.char_count": "{{count}} chars", "ai_chat.message.tool_result.no_data": "No data", + "ai_chat.message.tool_call.inspect_ai_runtime": "Read current AI runtime status", + "ai_chat.message.tool_call.inspect_ai_safety": "Read current AI safety boundary", + "ai_chat.message.tool_call.inspect_ai_providers": "Read current AI provider and model configuration", + "ai_chat.message.tool_call.inspect_ai_chat_readiness": "Read current AI chat preflight status", + "ai_chat.message.tool_call.inspect_ai_tool_catalog": "Read AI tool catalog and parameter hints", + "ai_chat.message.tool_call.inspect_ai_support_bundle": "Generate AI troubleshooting support bundle", + "ai_chat.message.tool_call.inspect_mcp_setup": "Read current MCP configuration status", + "ai_chat.message.tool_call.inspect_mcp_runtime_failures": "Diagnose MCP runtime failures", + "ai_chat.message.tool_call.inspect_mcp_authoring_guide": "Read MCP authoring guide", + "ai_chat.message.tool_call.inspect_mcp_draft": "Validate MCP draft", + "ai_chat.message.tool_call.inspect_mcp_tool_schema": "Read MCP tool parameter schema", + "ai_chat.message.tool_call.inspect_ai_guidance": "Read current AI prompt and skill configuration", "ai_chat.message.tool_call.get_connections": "Load available connection info", "ai_chat.message.tool_call.get_databases": "Scan database list", "ai_chat.message.tool_call.get_tables": "Analyze table structure info", + "ai_chat.message.tool_call.get_all_columns": "Summarize columns across tables", "ai_chat.message.tool_call.get_columns": "Read column list", + "ai_chat.message.tool_call.get_indexes": "Inspect index definitions", + "ai_chat.message.tool_call.get_foreign_keys": "Map foreign key relationships", + "ai_chat.message.tool_call.get_triggers": "Inspect trigger logic", "ai_chat.message.tool_call.get_table_ddl": "Read CREATE TABLE statement", + "ai_chat.message.tool_call.inspect_table_bundle": "Fetch full table structure snapshot", + "ai_chat.message.tool_call.inspect_database_bundle": "Fetch database structure overview", + "ai_chat.inspection.app_health.last_render_error.empty_summary": "No AI message render errors have been recorded yet.", + "ai_chat.inspection.last_render_error.empty_summary": "No AI message render errors have been recorded yet.", + "ai_chat.inspection.last_render_error.empty_next_action.reproduce": "If the user reports a blank AI message, white block, or localized render error, reproduce it and read this snapshot again.", + "ai_chat.inspection.last_render_error.empty_next_action.inspect_health": "If the entire AI panel is failing, combine this with inspect_ai_setup_health and inspect_app_logs.", + "ai_chat.inspection.last_render_error.recorded_summary": "A recent AI message render error was recorded, including the message, render path, and stack summary needed for diagnosis.", + "ai_chat.inspection.last_render_error.next_action.match_message": "Match messageId and contentPreview against the current conversation to identify which bubble triggered the render error.", + "ai_chat.inspection.last_render_error.next_action.narrow_scope": "If more narrowing is needed, compare the latest user input, tool results, and related component code.", + "ai_chat.inspection.app_health.log_reading_unavailable": "The current runtime does not provide log reading capability", + "ai_chat.inspection.app_health.app_log.unread": "GoNavi application logs are not readable: {{detail}}", + "ai_chat.inspection.app_health.connection_failures.unread": "Connection failure logs are not readable: {{detail}}", + "ai_chat.inspection.app_health.warning.app_log_unread": "GoNavi application logs cannot be read, so startup exceptions and MCP/connection errors lack log evidence", + "ai_chat.inspection.app_health.next_action.enable_app_log_reading": "Confirm the current runtime can read gonavi.log, then call inspect_app_logs for log details", + "ai_chat.inspection.app_health.warning.app_log_errors": "Recent application logs contain {{count}} ERROR entries; inspect_app_logs should be checked first", + "ai_chat.inspection.app_health.next_action.inspect_app_log_errors": "Call inspect_app_logs to review recent raw ERROR/WARN lines and confirm whether they affect AI, MCP, or database connections", + "ai_chat.inspection.app_health.warning.app_log_warnings": "Recent application logs contain {{count}} WARN entries; confirm whether they are known ignorable warnings", + "ai_chat.inspection.app_health.next_action.inspect_app_log_warnings": "If the user reports instability, call inspect_app_logs first to see whether WARN lines cluster around AI/MCP/connection paths", + "ai_chat.inspection.app_health.warning.connection_failures_unread": "Connection failure logs cannot be read, so database connection cooldown and validation failures lack structured evidence", + "ai_chat.inspection.app_health.warning.connection_failures_recent": "Recently detected {{count}} connection failure/cooldown records", + "ai_chat.inspection.app_health.next_action.inspect_recent_connection_failures": "Call inspect_recent_connection_failures to review the latest connection failure cause, then decide whether to inspect the current connection or saved connection config", + "ai_chat.inspection.app_health.warning.no_workspace_tabs": "No tabs are open in the current workspace, so AI has no active editor context to read directly", + "ai_chat.inspection.app_health.next_action.open_sql_tab": "To analyze the current SQL, open or select the target SQL tab first, then call inspect_active_tab", + "ai_chat.inspection.app_health.warning.last_render_error": "A recent AI message render error was recorded and may affect reply bubble display or Markdown rendering", + "ai_chat.inspection.app_health.next_action.inspect_last_render_error": "Call inspect_ai_last_render_error to review the latest bubble render error messageId, content preview, and component stack", + "ai_chat.inspection.app_health.message.ready": "The AI application health overview passed; AI configuration, logs, connection failures, and workspace context show no obvious issues", + "ai_chat.inspection.app_health.message.blocked": "AI application health has {{count}} blockers; fix provider and send prerequisites first", + "ai_chat.inspection.app_health.message.degraded": "AI application health has runtime anomaly signals; drill into logs or connection failure records first", + "ai_chat.inspection.app_health.message.needs_attention": "AI application health is usable overall, but still has {{count}} recommendations", + "ai_chat.inspection.connection_failures.category.cooldown": "Connection cooldown", + "ai_chat.inspection.connection_failures.category.parameter_compatibility": "Connection parameter/compatibility issue", + "ai_chat.inspection.connection_failures.category.validation": "Connection validation failed", + "ai_chat.inspection.connection_failures.category.authentication": "Authentication failed", + "ai_chat.inspection.connection_failures.category.timeout": "Connection timeout", + "ai_chat.inspection.connection_failures.category.network": "Network unreachable", + "ai_chat.inspection.connection_failures.category.ssh": "SSH tunnel failed", + "ai_chat.inspection.connection_failures.category.startup": "Driver/process startup failed", + "ai_chat.inspection.connection_failures.category.other": "Other connection anomaly", + "ai_chat.inspection.connection_failures.next_action.cooldown": "Fix the previous real connection error before retrying; repeated refreshes will keep hitting the connection cooldown.", + "ai_chat.inspection.connection_failures.next_action.parameter_compatibility": "Check connection parameters, DSN, and protocol compatibility first, especially multiStatements, charset, extra query parameters, and URL encoding.", + "ai_chat.inspection.connection_failures.next_action.validation": "Inspect the server validation failure details and confirm that database type, driver protocol, database name, or Service Name matches the target service.", + "ai_chat.inspection.connection_failures.next_action.authentication": "Verify the username, password, authentication database, tenant, or Service Name, and confirm the server allows this account to log in.", + "ai_chat.inspection.connection_failures.next_action.ssh": "Check the SSH jump host address, port, account, and tunnel target address; if needed, verify connectivity from the jump host to the database first.", + "ai_chat.inspection.connection_failures.next_action.network": "Check whether the target address, port, firewall, proxy, and tunnel path are reachable, and confirm the server is actually listening.", + "ai_chat.inspection.connection_failures.next_action.startup": "Check whether the driver process or external dependency can start normally; if needed, validate the startup command locally or on the target host first.", + "ai_chat.inspection.connection_failures.next_action.check_current_connection": "If you need to confirm the connection currently shown in the UI still targets the same endpoint, call inspect_current_connection to check whether it still points to {{address}}.", + "ai_chat.inspection.connection_failures.next_action.inspect_config": "First use inspect_current_connection and inspect_saved_connections to verify the current connection configuration, then widen the log window if more evidence is needed.", + "ai_chat.inspection.connection_failures.message.detected": "Recent logs identified {{count}} connection-related anomalies; latest category is {{categoryLabel}}", + "ai_chat.inspection.connection_failures.message.no_keyword_match": "No recent connection failure records matched keyword \"{{keyword}}\"", + "ai_chat.inspection.connection_failures.message.none": "No connection failures, validation failures, or connection cooldown records were identified in recent logs", + "ai_chat.inspection.context_budget.warning.missing_session": "The target AI session was not found, so message volume statistics only cover an empty window", + "ai_chat.inspection.context_budget.next_action.open_session": "Open or select the target AI session first, then call inspect_ai_context_budget again", + "ai_chat.inspection.context_budget.warning.critical_risk": "The current AI input context has reached critical volume and may cause slow replies, truncation, or ignored constraints", + "ai_chat.inspection.context_budget.warning.high_risk": "The current AI input context is large; narrow the context before complex questions", + "ai_chat.inspection.context_budget.warning.large_schema_context": "Many table schemas or long DDL are mounted and may crowd out the user question and tool results", + "ai_chat.inspection.context_budget.next_action.narrow_tables": "Keep only tables relevant to this turn; if needed, use inspect_table_bundle to read target tables on demand", + "ai_chat.inspection.context_budget.warning.large_messages": "Recent messages in this session are long and may affect the stability of later replies", + "ai_chat.inspection.context_budget.next_action.summarize_or_new_session": "Start a new session or ask AI to summarize the current conclusion before continuing the next complex task", + "ai_chat.inspection.context_budget.warning.large_tool_results": "Recent tool results are long and may dilute later answers with logs or large result sets", + "ai_chat.inspection.context_budget.next_action.reduce_tool_results": "Reduce the returned volume from inspect_app_logs / inspect_recent_sql_logs / includeDDL / includeLogLines", + "ai_chat.inspection.context_budget.warning.large_mcp_catalog": "Many MCP tools or schemas are exposed, which may make the model more likely to choose the wrong tool", + "ai_chat.inspection.context_budget.next_action.narrow_tools": "Temporarily disable unrelated MCP services, or call inspect_ai_tool_catalog by keyword first to narrow the tool route", + "ai_chat.inspection.context_budget.warning.large_skills": "Many Skills are enabled or prompts are long, which may stack conflicting constraints", + "ai_chat.inspection.context_budget.next_action.reduce_skills": "Keep only Skills relevant to this turn, then restore other Skills after finishing", + "ai_chat.inspection.context_budget.warning.unresolved_tool_calls": "The recent message window contains {{count}} unclosed tool calls", + "ai_chat.inspection.context_budget.next_action.inspect_message_flow": "Call inspect_ai_message_flow first to confirm whether tool calls are missing tool result messages", + "ai_chat.inspection.context_budget.next_action.continue_narrow_probe": "The current context volume is manageable; continue by calling narrower schema, log, or SQL risk probes for the specific question", + "ai_chat.inspection.tool_catalog.next_action.filter_by_keyword": "Filter by user-question keywords first, such as mcp, connection failure, transaction, shortcut, schema, or logs", + "ai_chat.inspection.tool_catalog.warning.no_mcp_tools": "No external MCP tools were discovered; if the user needs external capabilities, check MCP service configuration and tool discovery status first", + "ai_chat.inspection.tool_catalog.next_action.inspect_mcp_setup": "Call inspect_mcp_setup to inspect MCP services and external client access status", + "ai_chat.inspection.tool_catalog.warning.no_matches": "No matching tools or recommended flows were found", + "ai_chat.inspection.tool_catalog.next_action.broaden_keyword": "Use broader keywords, or call inspect_ai_runtime first to view the complete current tool list", + "ai_chat.inspection.tool_catalog.next_action.use_parameter_descriptions": "Before calling tools with parameters, build arguments from parameters.description first; confirm with the user when context is missing", + "ai_chat.inspection.tool_catalog.message.by_tool_name": "Returned catalog information for tool {{toolName}}", + "ai_chat.inspection.tool_catalog.message.by_keyword": "Returned tool catalog suggestions for keyword {{keyword}}", + "ai_chat.inspection.tool_catalog.message.summary": "Returned the GoNavi AI tool catalog summary", + "ai_chat.inspection.app_health.error.support_bundle_failed": "Failed to generate AI support bundle", + "ai_chat.inspection.support_bundle.message.ready": "Generated a GoNavi AI support bundle snapshot for diagnosing AI, MCP, logs, connections, and context size issues", + "ai_chat.inspection.support_bundle.privacy.note": "By default, only summaries and structured counts are returned; log lines or message previews are included only when includeLogLines/includeMessageContent is explicitly enabled.", + "ai_chat.inspection.codebase_hotspots.evidence.note": "Based on the current repository frontend file-line hotspot snapshot; before extraction, prefer slices that are readyToExtract and already covered by tests.", + "ai_chat.inspection.codebase_hotspots.next_action.pick_ready_slice": "Prefer a slice with readiness=readyToExtract and existing test coverage for small-step extraction; avoid rewriting an entire large component directly.", + "ai_chat.inspection.codebase_hotspots.next_action.confirm_safe_seam": "Confirm the safeSeam before every extraction, and do not cross SQL execution, transaction, connection secret, or database dialect boundaries.", + "ai_chat.inspection.codebase_hotspots.next_action.run_targeted_tests": "After extraction, run the corresponding component tests, related utils tests, and npm --prefix frontend run build at minimum.", + "ai_chat.inspection.codebase_hotspots.next_action.browser_smoke": "For visible UI extraction, open the real page in a browser for one smoke verification.", + "ai_chat.inspection.codebase_hotspots.sidebar.why": "The left tree, command palette, context menus, and connection actions are concentrated in one file, so change entry points are numerous and regression risk is high.", + "ai_chat.inspection.codebase_hotspots.sidebar.preferred_next_slice": "External SQL directory dialog", + "ai_chat.inspection.codebase_hotspots.sidebar.safe_seam": "Extract stateless dialog and menu configuration first, then handle action dispatch that depends on connection tree state.", + "ai_chat.inspection.codebase_hotspots.sidebar.suggested_slice.v2_command_palette": "V2 command palette", + "ai_chat.inspection.codebase_hotspots.sidebar.suggested_slice.external_sql_directory_dialog": "External SQL directory dialog", + "ai_chat.inspection.codebase_hotspots.sidebar.suggested_slice.connection_tree_actions": "Connection tree actions", + "ai_chat.inspection.codebase_hotspots.sidebar.suggested_slice.batch_operation_dialogs": "Batch operation dialogs", + "ai_chat.inspection.codebase_hotspots.sidebar.verification.browser_smoke": "Open the sidebar in a browser and verify the connection tree, context menus, and external SQL directory entry.", + "ai_chat.inspection.codebase_hotspots.data_grid.why": "Result display, editing, DDL, export, and column operations are coupled, so a focused fix can affect the query result area.", + "ai_chat.inspection.codebase_hotspots.data_grid.preferred_next_slice": "Result export toolbar", + "ai_chat.inspection.codebase_hotspots.data_grid.safe_seam": "Extract the pure display toolbar and menu item generation first; do not move data-editing transaction state yet.", + "ai_chat.inspection.codebase_hotspots.data_grid.suggested_slice.result_export_toolbar": "Result export toolbar", + "ai_chat.inspection.codebase_hotspots.data_grid.suggested_slice.column_header_menu": "Column header menu", + "ai_chat.inspection.codebase_hotspots.data_grid.suggested_slice.ddl_view": "DDL view", + "ai_chat.inspection.codebase_hotspots.data_grid.suggested_slice.cell_edit_transaction_hint": "Cell edit transaction hint", + "ai_chat.inspection.codebase_hotspots.data_grid.verification.browser_smoke": "Run a query in the browser and verify the result table, export, column menu, and table editing entry.", + "ai_chat.inspection.codebase_hotspots.connection_modal.why": "Multi-source connection forms are still concentrated in one component, so adding data sources or secret rules can affect each other.", + "ai_chat.inspection.codebase_hotspots.connection_modal.preferred_next_slice": "TLS configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.safe_seam": "The connection form already has presentation utilities; first extract configuration sections shown per data source.", + "ai_chat.inspection.codebase_hotspots.connection_modal.suggested_slice.ssh_proxy_section": "SSH/proxy configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.suggested_slice.tls_section": "TLS configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.suggested_slice.mongodb_section": "MongoDB configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.suggested_slice.jvm_section": "JVM configuration section", + "ai_chat.inspection.codebase_hotspots.connection_modal.verification.browser_smoke": "Open the add/edit connection dialog in a browser and switch MySQL, Oracle, MongoDB, and Redis forms.", + "ai_chat.inspection.codebase_hotspots.query_editor.why": "SQL editing, execution, transactions, result layout, and shortcut state are concentrated, so transaction and result-panel regressions are likely.", + "ai_chat.inspection.codebase_hotspots.query_editor.preferred_next_slice": "Editor toolbar", + "ai_chat.inspection.codebase_hotspots.query_editor.safe_seam": "The toolbar JSX can pass state and callbacks through props, avoiding SQL execution, transaction, and result pagination logic.", + "ai_chat.inspection.codebase_hotspots.query_editor.suggested_slice.result_toolbar": "Result area toolbar", + "ai_chat.inspection.codebase_hotspots.query_editor.suggested_slice.transaction_status_bar": "Transaction status bar", + "ai_chat.inspection.codebase_hotspots.query_editor.suggested_slice.execution_log_hint": "Execution log hint", + "ai_chat.inspection.codebase_hotspots.query_editor.suggested_slice.editor_shortcut_binding": "Editor shortcut binding", + "ai_chat.inspection.codebase_hotspots.query_editor.verification.browser_smoke": "Open the SQL editor in a browser and verify connection/database selection, run, save, format, result visibility, and the AI menu.", + "ai_chat.inspection.codebase_hotspots.table_designer.why": "Field editing, indexes, foreign keys, partitions, and DDL generation are concentrated, so database dialect differences can spread easily.", + "ai_chat.inspection.codebase_hotspots.table_designer.preferred_next_slice": "Field editing table", + "ai_chat.inspection.codebase_hotspots.table_designer.safe_seam": "Add field type, length, NULL, and default value snapshot tests first, then extract the field editing table.", + "ai_chat.inspection.codebase_hotspots.table_designer.suggested_slice.field_editing_table": "Field editing table", + "ai_chat.inspection.codebase_hotspots.table_designer.suggested_slice.index_panel": "Index configuration panel", + "ai_chat.inspection.codebase_hotspots.table_designer.suggested_slice.foreign_key_panel": "Foreign key configuration panel", + "ai_chat.inspection.codebase_hotspots.table_designer.suggested_slice.dialect_ddl_preview": "Dialect DDL preview", + "ai_chat.inspection.codebase_hotspots.table_designer.verification.browser_smoke": "Open object design in a browser and verify fields, indexes, foreign keys, and DDL preview.", + "ai_chat.inspection.codebase_hotspots.redis_viewer.why": "Key browsing, data-structure editing, TTL, encoding display, and the add dialog are concentrated, so Redis Cluster/Sentinel follow-up validation is broad.", + "ai_chat.inspection.codebase_hotspots.redis_viewer.preferred_next_slice": "Key search bar", + "ai_chat.inspection.codebase_hotspots.redis_viewer.safe_seam": "Extract the search bar and topology hint first, avoiding early changes to each data-structure editor.", + "ai_chat.inspection.codebase_hotspots.redis_viewer.suggested_slice.key_search_bar": "Key search bar", + "ai_chat.inspection.codebase_hotspots.redis_viewer.suggested_slice.structure_editors": "String/List/Set/ZSet/Hash/Stream editors", + "ai_chat.inspection.codebase_hotspots.redis_viewer.suggested_slice.add_key_dialog": "Add key dialog", + "ai_chat.inspection.codebase_hotspots.redis_viewer.verification.browser_smoke": "Open a Redis connection in a browser and verify key search, refresh, TTL, and the add entry.", + "ai_chat.inspection.codebase_hotspots.driver_manager.why": "Driver installation, status display, downloads, and optional proxy logic are substantial, so status cards and action areas are good next extraction targets.", + "ai_chat.inspection.codebase_hotspots.driver_manager.preferred_next_slice": "Driver status list", + "ai_chat.inspection.codebase_hotspots.driver_manager.safe_seam": "The status list is presentational; extract it first while keeping install and download actions in the parent component.", + "ai_chat.inspection.codebase_hotspots.driver_manager.suggested_slice.driver_status_list": "Driver status list", + "ai_chat.inspection.codebase_hotspots.driver_manager.suggested_slice.install_actions": "Install action area", + "ai_chat.inspection.codebase_hotspots.driver_manager.suggested_slice.download_logs": "Download log area", + "ai_chat.inspection.codebase_hotspots.driver_manager.verification.browser_smoke": "Open driver management in a browser and verify status display, install buttons, and the log area.", + "ai_chat.inspection.codebase_hotspots.data_sync.why": "Data sync connections, table mapping, prechecks, and execution results are concentrated, so database dialect issues can be hidden.", + "ai_chat.inspection.codebase_hotspots.data_sync.preferred_next_slice": "Sync precheck results", + "ai_chat.inspection.codebase_hotspots.data_sync.safe_seam": "Make precheck results a pure display component first, keeping connection selection and execution actions in the parent component.", + "ai_chat.inspection.codebase_hotspots.data_sync.suggested_slice.connection_selection": "Connection selection area", + "ai_chat.inspection.codebase_hotspots.data_sync.suggested_slice.table_mapping": "Table mapping area", + "ai_chat.inspection.codebase_hotspots.data_sync.suggested_slice.precheck_results": "Sync precheck results", + "ai_chat.inspection.codebase_hotspots.data_sync.suggested_slice.execution_logs": "Execution log area", + "ai_chat.inspection.codebase_hotspots.data_sync.verification.browser_smoke": "Open data sync in a browser and verify connection selection, table mapping, precheck, and execution logs.", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.why": "Diagnostic commands, output blocks, permission hints, and session state can continue to be split to lower JVM diagnostics regression risk.", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.preferred_next_slice": "Diagnostic output area", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.safe_seam": "The output area mainly depends on the command result array, so it can be extracted as a display component first.", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.suggested_slice.command_input": "Command input area", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.suggested_slice.diagnostic_output": "Diagnostic output area", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.suggested_slice.permission_hint": "Permission hint area", + "ai_chat.inspection.codebase_hotspots.jvm_diagnostic.verification.browser_smoke": "Open the JVM diagnostics panel in a browser and verify command input, output, and permission hints.", + "ai_chat.inspection.app_health.error.app_health_failed": "Failed to read AI application health overview", + "ai_chat.inspection.guidance.scope.global": "Global", + "ai_chat.inspection.guidance.scope.database": "Database session", + "ai_chat.inspection.guidance.scope.jvm": "JVM resource analysis", + "ai_chat.inspection.guidance.scope.jvmDiagnostic": "JVM diagnostics", + "ai_chat.inspection.guidance.message.configured": "{{promptCount}} custom prompts and {{skillCount}} Skills are enabled", + "ai_chat.inspection.guidance.message.empty": "No custom prompts or Skills are enabled", + "ai_chat.inspection.runtime.safety.readonly": "Read-only", + "ai_chat.inspection.runtime.safety.readwrite": "Read/write", + "ai_chat.inspection.runtime.safety.full": "Full access", + "ai_chat.inspection.runtime.context.schema_only": "Schema only", + "ai_chat.inspection.runtime.context.with_samples": "Schema + samples", + "ai_chat.inspection.runtime.context.with_results": "Schema + results", + "ai_chat.inspection.runtime.message.active": "AI is using {{provider}} with {{toolCount}} tools available", + "ai_chat.inspection.runtime.message.no_provider": "No AI provider is currently active", + "ai_chat.inspection.safety.rule.readonly": "Read-only mode only allows query statements.", + "ai_chat.inspection.safety.rule.readwrite": "Read/write mode allows queries and DML; DDL is still blocked.", + "ai_chat.inspection.safety.rule.full": "Full access mode allows all SQL operations; high-risk or unrecognized statements still require confirmation.", + "ai_chat.inspection.safety.restriction.readonly_blocks_mutating": "At the current safety level, all DML/DDL is blocked directly.", + "ai_chat.inspection.safety.restriction.non_query_confirmation": "Any allowed non-query statement still requires human confirmation.", + "ai_chat.inspection.safety.restriction.mcp_allow_mutating": "When executing non-query statements through GoNavi MCP execute_sql, allowMutating=true must also be passed explicitly.", + "ai_chat.inspection.safety.restriction.active_result_readonly": "The current active tab result set is read-only and cannot be treated as a directly writable data grid.", + "ai_chat.inspection.safety.restriction.jvm_readonly": "The current JVM connection is read-only, so diagnostic plans should default to observation and troubleshooting.", + "ai_chat.inspection.safety.restriction.jvm_mutating_disabled": "Current JVM diagnostics explicitly disallow mutating commands, even if the AI safety level allows writes.", + "ai_chat.inspection.safety.recommendation.enable_readwrite_for_dml": "Switch AI safety level to read/write mode before executing INSERT/UPDATE/DELETE.", + "ai_chat.inspection.safety.recommendation.enable_full_for_ddl": "Switch to full access mode before executing CREATE/ALTER/DROP/TRUNCATE schema changes.", + "ai_chat.inspection.safety.recommendation.full_required_for_schema": "DML is already allowed; schema changes still require full access mode.", + "ai_chat.inspection.safety.recommendation.open_editable_grid": "If the goal is editing a result grid, reopen an editable table or query result instead of the current read-only tab.", + "ai_chat.inspection.safety.recommendation.confirm_jvm_policy": "The current JVM connection should be treated as read-only; confirm whether its policy should change before mutating diagnostics.", + "ai_chat.inspection.safety.recommendation.enable_jvm_mutating": "JVM diagnostics currently disallow mutating commands; adjust diagnostic permissions before high-risk commands.", + "ai_chat.inspection.safety.message.active": "AI safety level is {{safety}}; active connection is {{connection}}", + "ai_chat.inspection.safety.message.no_connection": "AI safety level is {{safety}}; no active connection is selected", + "ai_chat.inspection.provider.message.empty": "No AI providers are configured", + "ai_chat.inspection.provider.message.active_needs_attention": "Using {{provider}}, but {{issueCount}} items still need checking", + "ai_chat.inspection.provider.message.active_ready": "{{count}} providers are configured; using {{provider}}", + "ai_chat.inspection.provider.message.unselected": "{{count}} providers are configured, but no active provider is selected", + "ai_chat.inspection.table_schema.warning.ddl_fallback": "Failed to fetch DDL for table {{tableName}}; fell back to a column metadata summary.", + "ai_chat.inspection.table_schema.warning.ddl_error": "DDL error: {{detail}}", + "ai_chat.inspection.table_schema.warning.fallback_limitation": "This result does not include complete index, constraint, trigger, or other DDL information; continue analysis from the column list and do not stop solely because DDL permissions failed.", + "ai_chat.inspection.table_schema.warning.columns_contract": "⚠️ The following is the real field list for table {{tableName}}. When generating SQL, use only these field values as column names exactly as shown; do not modify, abbreviate, or invent column names.", + "ai_chat.inspection.table_schema.warning.available_fields": "Available fields: {{fields}}", + "ai_chat.inspection.table_schema.warning.detail": "Details: {{detail}}", + "ai_chat.inspection.table_schema.value.none": "none", + "ai_chat.inspection.table_schema.error.unknown": "Unknown error", + "ai_chat.inspection.table_schema.error.ddl_failed": "Failed to fetch table DDL: {{detail}}", + "ai_chat.inspection.table_schema.error.ddl_and_columns_failed": "Failed to fetch table DDL: {{ddlDetail}}; fallback column metadata also failed: {{columnDetail}}", + "ai_chat.inspection.mcp.warning.config_errors": "{{count}} MCP server has launch configuration errors; testing and tool discovery may fail", + "ai_chat.inspection.mcp.warning.config_warnings": "{{count}} MCP server has launch configuration warnings; confirm them before diagnosing tool discovery failures", + "ai_chat.inspection.mcp.next_action.fix_config_errors": "Fix the MCP server configuration errors first, then test the server again", + "ai_chat.inspection.mcp.next_action.fix_config_warnings": "Open the affected MCP server and split launch command, arguments, and timeout according to the configuration check hints", + "ai_chat.inspection.mcp.message.with_issues": "{{serverCount}} MCP server is configured; {{enabledCount}} enabled; {{issueCount}} configuration checks need attention", + "ai_chat.inspection.mcp.message.configured": "{{serverCount}} MCP server is configured; {{enabledCount}} enabled", + "ai_chat.inspection.mcp.message.empty": "No MCP servers are configured yet", + "ai_chat.inspection.mcp_draft.default_name": "MCP draft", + "ai_chat.inspection.mcp_draft.redacted_parse_failed": "[Parse failed, original command hidden]", + "ai_chat.inspection.mcp_draft.parse.no_full_command": "No fullCommand was provided; validated the split-field draft instead.", + "ai_chat.inspection.mcp_draft.next_action.command_missing": "Paste the full startup command from README first, or at least fill node, npx, uvx, python, or exe as command.", + "ai_chat.inspection.mcp_draft.next_action.command_whole_line": "Put the whole command into the full command field for auto-splitting; keep only the executable in command, and put scripts, packages, and --stdio into args.", + "ai_chat.inspection.mcp_draft.next_action.args_missing_for_launcher": "Complete launcher arguments: npx usually needs -y and a package name, node needs server.js, python needs -m and a module name, uvx needs a package name, and docker needs run, -i, and an image name.", + "ai_chat.inspection.mcp_draft.next_action.docker_run": "For Docker MCP, set command to docker and add run separately in args.", + "ai_chat.inspection.mcp_draft.next_action.docker_interactive": "Add -i or --interactive to Docker MCP args so the stdio connection does not close immediately.", + "ai_chat.inspection.mcp_draft.next_action.docker_image": "Add the image name from README to Docker MCP args, for example mcp/server-fetch:latest.", + "ai_chat.inspection.mcp_draft.next_action.env_lines": "Write environment variables as one KEY=VALUE per line; do not put export, set, env, &&, or $env:KEY=VALUE; into args.", + "ai_chat.inspection.mcp_draft.next_action.timeout": "Set timeout to 20 seconds; slow-starting services can use 45 or 60 seconds.", + "ai_chat.inspection.mcp_draft.next_action.ready_to_save": "The current draft can be saved and tested for tool discovery; if it discovers 0 tools, check whether the service supports stdio.", + "ai_chat.inspection.mcp_draft.next_action.can_test_with_warnings": "The current draft can be tested, but handle warnings first to avoid tool discovery timeouts or discovering 0 tools.", + "ai_chat.inspection.mcp_draft.next_action.send_full_command": "If you are still unsure how to split it, pass the original full command to fullCommand and let GoNavi calculate it.", + "ai_chat.inspection.mcp_docker.next_action.add_run": "Add run to args, for example docker run --rm -i ", + "ai_chat.inspection.mcp_docker.next_action.add_interactive": "Add -i or --interactive to args so MCP stdio does not close immediately", + "ai_chat.inspection.mcp_docker.next_action.add_image": "Add the image name from README after docker run options", + "ai_chat.inspection.mcp_docker.next_action.timeout": "Docker may be slow on first startup; use timeoutSeconds 45 or 60", + "ai_chat.inspection.mcp_docker.next_action.no_tools": "The configuration structure looks complete but no tools were discovered; click \"Test tool discovery\" to confirm Docker, the image, and in-container dependencies are available", + "ai_chat.inspection.mcp_docker.next_action.disabled": "This Docker MCP is disabled; enable it and test tool discovery after confirming the configuration", + "ai_chat.inspection.mcp_docker.next_action.create_from_readme": "If README provides docker run -i --rm , create a server from the \"Docker image\" template in MCP settings", + "ai_chat.inspection.mcp_docker.warning.incomplete": "{{count}} Docker MCP server is missing key arguments such as run, -i, or image name", + "ai_chat.inspection.mcp_docker.next_action.fix_key_args": "Fix key Docker MCP arguments first, then test tool discovery again", + "ai_chat.inspection.mcp_docker.warning.config_warnings": "{{count}} Docker MCP server still has configuration warnings", + "ai_chat.inspection.mcp_docker.next_action.open_services": "Open the affected Docker MCP services and confirm arguments and timeout from the configuration hints", + "ai_chat.inspection.mcp_docker.warning.no_tools": "{{count}} enabled Docker MCP server has not discovered tools yet", + "ai_chat.inspection.mcp_docker.next_action.refresh_tools": "Confirm local Docker is available, the image is pulled, and click \"Test tool discovery\" to refresh the tool list", + "ai_chat.inspection.mcp_docker.message.with_incomplete": "There are {{total}} Docker MCP servers; {{count}} have incomplete key arguments", + "ai_chat.inspection.mcp_docker.message.with_enabled": "There are {{total}} Docker MCP servers; {{enabled}} are enabled", + "ai_chat.inspection.mcp_docker.message.empty": "No Docker MCP servers are configured", + "ai_chat.inspection.mcp_tool_schema.usage.no_input_schema": "This MCP tool does not declare inputSchema; check the service README first or probe with an empty object.", + "ai_chat.inspection.mcp_tool_schema.usage.required_params": "Before calling {{alias}}, provide: {{parameters}}", + "ai_chat.inspection.mcp_tool_schema.usage.enum_values": "{{path}} must be one of: {{values}}", + "ai_chat.inspection.mcp_tool_schema.usage.nested_json": "Nested object and array parameters must follow the JSON structure; do not pass the whole object as a string.", + "ai_chat.inspection.mcp_tool_schema.usage.schema_fields_only": "Only pass fields declared in the schema; if a field meaning is unclear, ask the user instead of guessing.", + "ai_chat.inspection.mcp_tool_schema.warning.no_tools": "No MCP tools were discovered; MCP services may not be configured, or service testing/discovery may have failed.", + "ai_chat.inspection.mcp_tool_schema.next_action.inspect_setup": "Call inspect_mcp_setup first to check whether MCP services are enabled and tools have been discovered.", + "ai_chat.inspection.mcp_tool_schema.warning.no_matches": "No matching MCP tool was found.", + "ai_chat.inspection.mcp_tool_schema.next_action.lookup_alias": "Call inspect_mcp_setup first to check the MCP tool aliases actually discovered, then query by exact alias.", + "ai_chat.inspection.mcp_tool_schema.warning.missing_schema": "Some MCP tools do not declare inputSchema, so parameter documentation may be incomplete.", + "ai_chat.inspection.mcp_tool_schema.next_action.read_readme": "For tools without schema, go back to the MCP service README or use tool errors to confirm parameters.", + "ai_chat.inspection.mcp_tool_schema.message.with_matches": "Found {{matched}} MCP tools and returned {{returned}} parameter schema summaries", + "ai_chat.inspection.mcp_tool_schema.message.no_matches": "No matching MCP tool was found", + "ai_chat.inspection.mcp_tool_schema.message.empty": "No MCP tool schema is available yet", + "ai_chat.inspection.mcp_runtime.next_action.command_not_found": "Check that command contains only the executable name, and confirm it is on PATH or uses an absolute path.", + "ai_chat.inspection.mcp_runtime.next_action.timeout": "Raise timeoutSeconds to 45 or 60, and confirm the service keeps the stdio connection open after startup.", + "ai_chat.inspection.mcp_runtime.next_action.permission": "Check executable permissions, antivirus/system blocking, and working directory access.", + "ai_chat.inspection.mcp_runtime.next_action.auth": "Check whether the Token/API Key in environment variables is configured, unexpired, and has enough scope.", + "ai_chat.inspection.mcp_runtime.next_action.network": "Check whether the remote endpoint, proxy, VPN, or local port required by MCP is reachable.", + "ai_chat.inspection.mcp_runtime.next_action.stdio_closed": "Confirm the --stdio/stdin argument required by README is set; for Docker, confirm args includes -i.", + "ai_chat.inspection.mcp_runtime.next_action.process_exit": "Run the launch command in a terminal separately and inspect why the process exits immediately after startup.", + "ai_chat.inspection.mcp_runtime.next_action.argument_error": "Call inspect_mcp_tool_schema first to read the real inputSchema, then fix the tool arguments JSON.", + "ai_chat.inspection.mcp_runtime.next_action.transport": "New MCP servers in GoNavi currently support stdio only; for HTTP MCP, use the GoNavi HTTP service or the matching remote access guide.", + "ai_chat.inspection.mcp_runtime.next_action.unknown": "Inspect the configuration with inspect_mcp_setup, then call inspect_app_logs with a larger log window to confirm the raw error.", + "ai_chat.inspection.mcp_runtime.next_action.enabled_without_tools": "Some enabled MCP servers have no discovered tools; click \"Test tool discovery\" first and confirm the launch command runs independently.", + "ai_chat.inspection.mcp_runtime.next_action.fix_discovery_first": "When the tool list is empty, fix startup/discovery failure before diagnosing individual tool arguments.", + "ai_chat.inspection.mcp_runtime.next_action.expand_logs": "No MCP failure signal was found in recent logs; if you just reproduced the issue, increase lineLimit or filter precisely with serverName.", + "ai_chat.inspection.mcp_runtime.warning.failure_events": "{{count}} MCP runtime failure signal was found in recent logs.", + "ai_chat.inspection.mcp_runtime.warning.enabled_without_tools": "{{count}} enabled MCP server currently has no discovered tools.", + "ai_chat.inspection.mcp_runtime.message.failure_events": "{{count}} MCP runtime failure signal was found in recent logs", + "ai_chat.inspection.mcp_runtime.message.no_failure_events": "No MCP startup, tool discovery, or tool call failure signal was found in recent logs.", + "ai_chat.inspection.mcp_runtime.error.read_logs_failed": "Failed to read MCP runtime failure logs: {{detail}}", + "ai_chat.inspection.mcp_runtime.error.read_logs_unsupported": "The current environment does not support reading GoNavi app logs", + "ai_chat.inspection.diagnostics.error.read_app_logs_failed": "Failed to read GoNavi app logs: {{detail}}", + "ai_chat.inspection.diagnostics.error.read_ai_upstream_logs_failed": "Failed to read AI upstream request logs: {{detail}}", + "ai_chat.inspection.diagnostics.error.read_recent_connection_failures_failed": "Failed to read recent connection failure records: {{detail}}", + "ai_chat.inspection.diagnostics.error.read_app_logs_unsupported": "The current environment does not support reading GoNavi app logs", + "ai_chat.inspection.diagnostics.error.unknown": "unknown error", + "ai_chat.inspection.snapshot.error.inspect_current_connection": "Failed to read current connection: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_connection_capabilities": "Failed to read current connection capability matrix: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_saved_connections": "Failed to read saved connection list: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_redis_topology": "Failed to read Redis topology configuration: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_external_sql_directories": "Failed to read external SQL directories: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_external_sql_file": "Failed to read external SQL file: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_sessions": "Failed to read local AI session list: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_active_tab": "Failed to read current active tab: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_workspace_tabs": "Failed to read current workspace tabs: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_context": "Failed to read current AI context: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_recent_sql_logs": "Failed to fetch recent SQL logs: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_recent_sql_activity": "Failed to summarize recent SQL activity: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_sql_editor_transaction": "Failed to read SQL editor transaction state: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_mcp_runtime_failures": "Failed to read MCP runtime failure diagnostics: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_last_render_error": "Failed to read the latest AI render error: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_message_flow": "Failed to read AI message flow diagnostics: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_ai_context_budget": "Failed to read AI context budget diagnostics: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_codebase_hotspots": "Failed to read code hotspot diagnostics: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_saved_queries": "Failed to read saved queries: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_sql_snippets": "Failed to read SQL snippets: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_shortcuts": "Failed to read shortcut configuration: {{detail}}", + "ai_chat.inspection.snapshot.error.inspect_app_health": "Failed to read AI app health overview: {{detail}}", + "ai_chat.inspection.snapshot.error.default": "Failed to read local inspection snapshot: {{detail}}", + "ai_chat.inspection.upstream_logs.warning.invalid_json": "The request body is not complete JSON. It may have been truncated by logs, so a structured summary cannot be generated.", + "ai_chat.inspection.upstream_logs.warning.not_json_object": "The request body is not a JSON object, so model, message, and tool fields cannot be identified.", + "ai_chat.inspection.upstream_logs.warning.missing_messages": "No messages, contents, system, or prompt field was found. Confirm whether the upstream protocol matches expectations.", + "ai_chat.inspection.upstream_logs.warning.missing_tools": "The payload does not include tools/functions, so the model cannot initiate tool calls.", + "ai_chat.inspection.upstream_logs.warning.large_input": "The input text is large. Narrow the context or reduce log/DDL content if needed.", + "ai_chat.inspection.upstream_logs.message.empty": "No AI upstream request records were found in recent logs. Send one AI message first, or retry with a larger lineLimit.", + "ai_chat.inspection.upstream_logs.next_action.filter_request_body": "To verify full request inputs, filter precisely by requestId first, then check whether bodyPreview was truncated.", + "ai_chat.inspection.upstream_logs.next_action.inspect_timeout": "If there is only a start event with no completion or failure, continue with inspect_app_logs or increase lineLimit to check for request timeout.", + "ai_chat.inspection.upstream_logs.next_action.confirm_logging": "Confirm that the current build includes AI upstream request logging.", + "ai_chat.inspection.upstream_logs.next_action.send_message": "Send one AI chat message, then call this tool again.", + "ai_chat.inspection.upstream_logs.next_action.read_warn_error": "If there are still no records, call inspect_app_logs to read recent WARN/ERROR raw logs.", + "ai_chat.inspection.sql_risk.warning.unrecognized_operation": "No valid SQL operation keyword was recognized.", + "ai_chat.inspection.sql_risk.warning.data_change": "This statement modifies data. Confirm the target database, conditions, and impact scope before execution.", + "ai_chat.inspection.sql_risk.warning.ddl_change": "This statement modifies database structures or objects. Back up first and confirm the rollback plan.", + "ai_chat.inspection.sql_risk.warning.routine_side_effect": "This statement calls a routine or procedure and may have implicit writes or side effects.", + "ai_chat.inspection.sql_risk.warning.delete_missing_where": "DELETE is missing a WHERE clause and may delete the entire table.", + "ai_chat.inspection.sql_risk.warning.update_missing_where": "UPDATE is missing a WHERE clause and may update the entire table.", + "ai_chat.inspection.sql_risk.warning.truncate": "TRUNCATE quickly clears table data and usually cannot be rolled back row by row.", + "ai_chat.inspection.sql_risk.warning.drop_object": "DROP deletes database objects. Confirm the object and backup before execution.", + "ai_chat.inspection.sql_risk.warning.permission_change": "GRANT / REVOKE changes permission boundaries. Confirm the grantee and scope.", + "ai_chat.inspection.sql_risk.warning.multi_statement": "{{count}} SQL statements were detected. Confirm the impact scope of each statement before batch execution.", + "ai_chat.inspection.sql_risk.warning.safety_blocked": "The current AI safety policy does not allow {{operationType}} SQL.", + "ai_chat.inspection.sql_risk.warning.safety_blocked_unknown": "The current AI safety policy does not allow this SQL operation type.", + "ai_chat.inspection.sql_risk.error.inspect_failed": "Failed to inspect SQL risk: {{detail}}", + "ai_chat.inspection.sql_risk.message.no_active_query_sql": "The current active tab is not a SQL query tab, or the editor has no SQL content.", + "ai_chat.inspection.sql_risk.message.no_sql": "No SQL was provided, and there is no readable active SQL query tab.", + "ai_chat.inspection.sql_risk.next_action.provide_sql": "Pass the sql argument first, or switch to a query tab that contains a SQL draft.", + "ai_chat.inspection.sql_risk.next_action.explain_and_confirm": "Explain the risk points to the user first, then ask the user to confirm whether to continue.", + "ai_chat.inspection.sql_risk.next_action.confirm_write_scope": "For write or DDL statements, confirm WHERE clauses, backups, target database, and impact scope first.", + "ai_chat.inspection.sql_risk.next_action.read_only_check_target": "Read-only queries are lower risk, but still confirm the target connection and database name first.", + "ai_chat.inspection.database_bundle.error.db_name_required": "dbName is required", + "ai_chat.inspection.database_bundle.error.table_name_required": "tableName is required", + "ai_chat.inspection.database_bundle.error.database_overview_failed": "Failed to build database structure overview: {{detail}}", + "ai_chat.inspection.database_bundle.error.table_snapshot_failed": "Failed to build table structure snapshot: {{detail}}", + "ai_chat.inspection.database_bundle.error.unknown": "Unknown error", + "ai_chat.inspection.database_bundle.warning.all_columns_failed": "Failed to fetch column summary: {{detail}}", + "ai_chat.inspection.database_bundle.warning.columns_failed": "Failed to fetch column list: {{detail}}", + "ai_chat.inspection.database_bundle.warning.ddl_failed": "Failed to fetch DDL: {{detail}}", + "ai_chat.inspection.database_bundle.warning.foreign_keys_failed": "Failed to fetch foreign key relationships: {{detail}}", + "ai_chat.inspection.database_bundle.warning.indexes_failed": "Failed to fetch index definitions: {{detail}}", + "ai_chat.inspection.database_bundle.warning.sample_rows_failed": "Failed to fetch sample rows: {{detail}}", + "ai_chat.inspection.database_bundle.warning.tables_failed": "Failed to fetch table list: {{detail}}", + "ai_chat.inspection.database_bundle.warning.tables_failed_with_column_fallback": "Failed to fetch table list, fell back to column summary inference: {{detail}}", + "ai_chat.inspection.database_bundle.warning.triggers_failed": "Failed to fetch triggers: {{detail}}", + "ai_chat.inspection.setup.blocker.no_active_provider": "No active AI provider is selected", + "ai_chat.inspection.setup.blocker.missing_secret": "The active provider is missing an API Key / Secret", + "ai_chat.inspection.setup.blocker.missing_base_url": "The active provider is missing a base URL", + "ai_chat.inspection.setup.blocker.missing_model": "The active provider has no selected model", + "ai_chat.inspection.setup.next_action.select_provider": "Add and select an active provider in AI settings first", + "ai_chat.inspection.setup.next_action.fill_secret": "Fill in the active provider secret", + "ai_chat.inspection.setup.next_action.fill_base_url": "Fill in the active provider baseUrl", + "ai_chat.inspection.setup.next_action.select_model": "Select an available model for the active provider", + "ai_chat.inspection.setup.next_action.wait_models": "Wait for the model list to finish loading, then confirm the active model again", + "ai_chat.inspection.setup.next_action.add_mcp_server": "To extend AI tool capabilities, add and test at least one MCP server", + "ai_chat.inspection.setup.next_action.connect_external_client": "To let external Agents use GoNavi MCP, connect local clients such as Claude Code/Codex or configure a remote MCP bridge for cloud Agents", + "ai_chat.inspection.setup.next_action.test_mcp_servers": "Test each enabled MCP server and confirm its command, arguments, and environment variables can discover tools correctly", + "ai_chat.inspection.setup.next_action.add_guidance": "To pin response style or workflow, add custom prompts or enable Skills", + "ai_chat.inspection.setup.next_action.attach_schema_context": "For more accurate SQL or schema suggestions, attach the target table schema to AI context first", + "ai_chat.inspection.setup.warning.loading_models": "The model list is still loading, so model selection is not complete yet", + "ai_chat.inspection.setup.warning.no_mcp_servers": "No MCP servers are configured yet", + "ai_chat.inspection.setup.warning.external_client_not_connected": "Claude Code / Codex is not connected to the current GoNavi MCP as a local client yet; OpenClaw/Hermans need a remote bridge", + "ai_chat.inspection.setup.warning.no_mcp_tools": "MCP servers are enabled, but no available MCP tools have been discovered yet", + "ai_chat.inspection.setup.warning.no_guidance": "No custom prompts or Skills are configured", + "ai_chat.inspection.setup.warning.no_schema_context": "Chat is ready, but no table schema context is attached yet", + "ai_chat.inspection.setup.message.ready": "AI setup health passed; provider, chat prerequisites, and MCP runtime path are available", + "ai_chat.inspection.setup.message.blocked": "AI setup has {{count}} blockers; fix the active provider and chat prerequisites first", + "ai_chat.inspection.setup.message.needs_attention": "AI setup is usable overall, but {{count}} recommendations can still be optimized", + "ai_chat.inspection.ai_config.error.inspect_ai_setup_health": "Failed to inspect current AI setup", + "ai_chat.inspection.ai_config.error.inspect_ai_runtime": "Failed to read current AI runtime state", + "ai_chat.inspection.ai_config.error.inspect_ai_safety": "Failed to read current AI safety boundary", + "ai_chat.inspection.ai_config.error.inspect_ai_providers": "Failed to read current AI provider configuration", + "ai_chat.inspection.ai_config.error.inspect_ai_chat_readiness": "Failed to read AI chat prerequisites", + "ai_chat.inspection.ai_config.error.inspect_ai_tool_catalog": "Failed to read AI tool catalog", + "ai_chat.inspection.ai_config.error.inspect_mcp_setup": "Failed to read MCP setup state", + "ai_chat.inspection.ai_config.error.inspect_mcp_remote_access": "Failed to read MCP remote access guidance", + "ai_chat.inspection.mcp_remote.strategy.reverse_proxy.title": "Internal reverse proxy", + "ai_chat.inspection.mcp_remote.strategy.reverse_proxy.detail": "Use this when Windows GoNavi and the cloud Agent already share a trusted intranet or gateway.", + "ai_chat.inspection.mcp_remote.strategy.reverse_proxy.risk": "Keep source IP, TLS, and Bearer Token restrictions at the gateway; do not expose it directly to the public internet.", + "ai_chat.inspection.mcp_remote.strategy.ssh_reverse_tunnel.title": "SSH reverse tunnel", + "ai_chat.inspection.mcp_remote.strategy.ssh_reverse_tunnel.detail": "Use this to temporarily map Windows 127.0.0.1:8765 to cloud Linux. It is simple, but the SSH account and port must be controlled.", + "ai_chat.inspection.mcp_remote.strategy.ssh_reverse_tunnel.risk": "The cloud Agent becomes unavailable if the tunnel disconnects, so this fits PoC or controlled operations environments.", + "ai_chat.inspection.mcp_remote.strategy.cloudflare_tunnel.title": "Cloudflare Tunnel", + "ai_chat.inspection.mcp_remote.strategy.cloudflare_tunnel.detail": "Use this for Windows machines without a fixed public entry point, with Cloudflare Access layered for identity checks.", + "ai_chat.inspection.mcp_remote.strategy.cloudflare_tunnel.risk": "Access / Zero Trust rules must be enabled; do not rely only on a random URL.", + "ai_chat.inspection.mcp_remote.strategy.tailscale.title": "Tailscale / WireGuard", + "ai_chat.inspection.mcp_remote.strategy.tailscale.detail": "Use this when Windows GoNavi and the cloud Agent can join the same private network and prefer an intranet address.", + "ai_chat.inspection.mcp_remote.strategy.tailscale.risk": "Control ACLs so only the target Agent can reach the GoNavi MCP port.", + "ai_chat.inspection.mcp_remote.strategy.custom.title": "Custom bridge", + "ai_chat.inspection.mcp_remote.strategy.custom.detail": "Use this when an enterprise gateway, bastion host, or dedicated MCP gateway already exists.", + "ai_chat.inspection.mcp_remote.strategy.custom.risk": "Define TLS, authentication, audit, and source restrictions clearly to avoid exposing local database capabilities to unknown Agents.", + "ai_chat.inspection.mcp_remote.next_action.start_local_http": "Start GoNavi MCP HTTP mode on Windows and confirm /healthz is reachable.", + "ai_chat.inspection.mcp_remote.next_action.expose_mcp_only": "Expose only /mcp to the target cloud Agent through a tunnel, reverse proxy, or private network.", + "ai_chat.inspection.mcp_remote.next_action.configure_agent": "Configure Streamable HTTP MCP URL and Authorization Bearer Token in OpenClaw/Hermans.", + "ai_chat.inspection.mcp_remote.next_action.inspect_connections": "Call get_connections first to obtain connectionId, then read schemas; do not copy database passwords to the cloud Agent.", + "ai_chat.inspection.mcp_remote.warning.missing_public_url": "No MCP URL reachable by the cloud Agent was provided; a remote Agent cannot directly access Windows local 127.0.0.1.", + "ai_chat.inspection.mcp_remote.warning.non_https_public_url": "The remote MCP URL is not HTTPS; if it is not a private network address, add TLS or place it behind a controlled tunnel.", + "ai_chat.inspection.mcp_remote.warning.missing_token": "Bearer Token readiness is not confirmed; HTTP MCP must use a random token and must not be exposed without authentication.", + "ai_chat.inspection.mcp_remote.message.with_public_url": "The remote Agent should access GoNavi MCP through {{publicUrl}} and authenticate with Bearer Token", + "ai_chat.inspection.mcp_remote.message.no_public_url": "The remote Agent needs to access the Windows GoNavi MCP HTTP endpoint through a controlled tunnel or reverse proxy", + "ai_chat.inspection.mcp_remote.security.recommended_bind_address": "127.0.0.1 unless a controlled gateway or private network is in front", + "ai_chat.inspection.ai_config.error.inspect_mcp_authoring_guide": "Failed to read MCP authoring guide", + "ai_chat.inspection.ai_config.error.inspect_mcp_draft": "Failed to validate MCP draft", + "ai_chat.inspection.ai_config.error.inspect_mcp_docker_setup": "Failed to inspect Docker MCP setup", + "ai_chat.inspection.ai_config.error.inspect_mcp_tool_schema": "Failed to read MCP tool parameter schema", + "ai_chat.inspection.ai_config.error.inspect_ai_guidance": "Failed to read current AI prompts and Skills configuration", + "ai_chat.inspection.current_connection.no_active": "No active connection is currently selected", + "ai_chat.inspection.current_connection.cache_missing": "The current active connection does not exist in the local cache", + "ai_chat.inspection.ai_context.linked_summary": "Currently linked table schema contexts: {{count}}", + "ai_chat.inspection.ai_context.none_linked": "No AI table schema context is currently linked", + "ai_chat.inspection.connection_capabilities.no_connection": "No connection is available for capability analysis", + "ai_chat.inspection.connection_capabilities.cache_missing": "The target connection does not exist in the local cache", + "ai_chat.inspection.connection_capabilities.hint.readonly_result": "Query results for this data source are shown as read-only by default and cannot be edited directly.", + "ai_chat.inspection.connection_capabilities.hint.editable_result": "Query results for this data source can enter the edit path when row locating conditions are available.", + "ai_chat.inspection.connection_capabilities.hint.manual_total_count": "Total result counts should prefer manual or deferred counting instead of relying directly on fast totals.", + "ai_chat.inspection.connection_capabilities.hint.regular_total_count": "Total result counts can prefer the regular counting path.", + "ai_chat.inspection.connection_capabilities.hint.approximate_table_count": "Table browsing can show approximate row counts to reduce large-table counting overhead.", + "ai_chat.inspection.connection_capabilities.hint.exact_table_count": "Table browsing does not use approximate row counts by default.", + "ai_chat.inspection.connection_capabilities.hint.message_publish_supported": "This data source provides a test message publishing entry, suitable for Topic/Queue integration checks.", + "ai_chat.inspection.connection_capabilities.hint.message_publish_unsupported": "This data source does not expose a test message publishing entry.", + "ai_chat.inspection.connection_capabilities.summary": "Current connection {{connectionName}} ({{type}}) exposes {{count}} frontend capability signals", + "ai_chat.inspection.redis_topology.label.single": "Standalone Redis", + "ai_chat.inspection.redis_topology.label.cluster": "Redis Cluster", + "ai_chat.inspection.redis_topology.label.sentinel": "Redis Sentinel", + "ai_chat.inspection.redis_topology.warning.missing_host": "Host is empty; fill in a Redis node or Sentinel address before connecting", + "ai_chat.inspection.redis_topology.warning.ssh_unsupported": "{{topologyLabel}} is not supported with SSH tunnels by the current backend; use direct access, a proxy, or remote MCP HTTP instead", + "ai_chat.inspection.redis_topology.warning.missing_sentinel_master": "Sentinel master name is empty; go-redis FailoverClient cannot discover the primary node", + "ai_chat.inspection.redis_topology.warning.single_sentinel_node": "Only one Sentinel node is configured; provide at least 2-3 Sentinel addresses to avoid a single point of failure", + "ai_chat.inspection.redis_topology.warning.sentinel_default_redis_port": "The primary Sentinel address uses port 6379; confirm this is a Sentinel port, commonly 26379 by default", + "ai_chat.inspection.redis_topology.warning.cluster_single_seed": "Only one Cluster seed node is configured; add multiple master/replica nodes to improve discovery reliability", + "ai_chat.inspection.redis_topology.warning.cluster_logical_db": "Redis Cluster physically supports only db0; GoNavi uses the __gonavi_db_N__: prefix to emulate logical DB isolation", + "ai_chat.inspection.redis_topology.warning.cluster_sentinel_fields_ignored": "Sentinel master and Sentinel user fields do not take effect in Cluster mode", + "ai_chat.inspection.redis_topology.warning.single_multiple_nodes": "Standalone mode has multiple node addresses; the backend will use the multi-node Cluster path, so explicitly switch to Cluster mode", + "ai_chat.inspection.redis_topology.warning.single_sentinel_fields_ignored": "Sentinel fields do not take effect in Standalone mode; switch to Sentinel mode if Sentinel discovery is required", + "ai_chat.inspection.redis_topology.db_note.cluster_logical_namespace": "Redis Cluster physically supports only db0; GoNavi uses the __gonavi_db_N__: prefix to emulate a multi-DB view.", + "ai_chat.inspection.redis_topology.db_note.sentinel_selected_db": "After Sentinel discovers the master, GoNavi connects to the selected DB and keeps the Sentinel settings when reconnecting.", + "ai_chat.inspection.redis_topology.db_note.single_selected_db": "Standalone mode uses Redis SELECT DB directly.", + "ai_chat.inspection.redis_topology.next_action.fill_host": "Fill in the host first; use Sentinel addresses for Sentinel mode and Redis Cluster seed nodes for Cluster mode.", + "ai_chat.inspection.redis_topology.next_action.fill_sentinel_master": "Fill in the Sentinel master name, for example mymaster.", + "ai_chat.inspection.redis_topology.next_action.disable_ssh": "Disable the SSH tunnel and use direct access, proxy/VPN, or GoNavi MCP HTTP so the remote Agent can access Redis through local GoNavi.", + "ai_chat.inspection.redis_topology.next_action.check_sentinel_port": "Change the primary Sentinel address port to 26379 unless your Sentinel explicitly listens on another port.", + "ai_chat.inspection.redis_topology.next_action.review_cluster_logical_db": "Confirm whether the workload really needs a Redis Cluster multi-DB view; if this is only key grouping, prefer an application namespace.", + "ai_chat.inspection.redis_topology.next_action.align_single_topology": "Explicitly switch to Cluster mode, or remove extra nodes and keep one Standalone address to avoid mismatch between configured and backend topology.", + "ai_chat.inspection.redis_topology.next_action.test_connection": "The configuration looks usable for {{topologyLabel}}; next, test the connection and inspect the Redis DB/key tree.", + "ai_chat.inspection.redis_topology.recommendation.sentinel_addresses": "Confirm that host and extra nodes are Sentinel addresses, not Redis master addresses.", + "ai_chat.inspection.redis_topology.recommendation.separate_auth": "Fill Redis data-node credentials and Sentinel credentials separately; do not mix them.", + "ai_chat.inspection.redis_topology.recommendation.cluster_multiple_seeds": "Prefer at least two seed nodes, and confirm these nodes belong to the same Redis Cluster.", + "ai_chat.inspection.redis_topology.recommendation.cluster_namespace": "If a multi-DB view is needed, prefer explicit namespaces in business keys to avoid misunderstanding the physical db0 limit of Cluster.", + "ai_chat.inspection.redis_topology.recommendation.single_one_address": "Standalone mode should contain one Redis address; if there are multiple nodes, use Cluster or Sentinel mode instead.", + "ai_chat.inspection.redis_topology.recommendation.network_for_cluster_sentinel": "For cross-network Redis Cluster/Sentinel access, prefer a network proxy, VPN, or GoNavi MCP HTTP instead of a single-port SSH tunnel.", + "ai_chat.inspection.redis_topology.recommendation.check_tls": "TLS is enabled; if connection fails, first check sslMode, CA/certificate paths, and server SNI.", + "ai_chat.inspection.workspace.no_active_tab": "No active tab is currently selected", + "ai_chat.inspection.external_sql_file.error.read_failed": "Failed to read external SQL file: {{detail}}", + "ai_chat.inspection.external_sql_file.error.file_path_required": "filePath is required", + "ai_chat.inspection.external_sql_file.error.outside_configured_directory": "The target file is outside configured external SQL directories", + "ai_chat.inspection.external_sql_file.error.unsupported_runtime": "The current runtime does not support reading local SQL files yet", + "ai_chat.inspection.external_sql_file.error.unknown": "Unknown error", + "ai_chat.inspection.ai_sessions.untitled": "Untitled session", + "ai_chat.inspection.app_log.message.no_keyword_match": "No recent log entries matched keyword \"{{keyword}}\".", + "ai_chat.inspection.app_log.message.no_readable_entries": "No readable recent log entries are available.", + "ai_chat.inspection.message_flow.warning.unresolved_tool_calls": "{{count}} tool calls have no matching tool result messages.", + "ai_chat.inspection.message_flow.warning.consecutive_assistant": "{{count}} consecutive assistant message pairs were found; one reply may have been split into multiple bubbles.", + "ai_chat.inspection.message_flow.warning.empty_assistant": "{{count}} empty assistant messages were found.", + "ai_chat.inspection.message_flow.warning.loading_message": "The session still contains a loading message; streaming may still be active or a previous interruption was not cleaned up.", + "ai_chat.inspection.message_flow.next_action.check_tool_results": "Check whether useAIChatLocalTools writes a tool message for every tool_call_id.", + "ai_chat.inspection.message_flow.next_action.check_stream_append": "Check whether streaming append logic reuses the same assistantMsgId instead of creating a new assistant message for the same reply.", + "ai_chat.inspection.message_flow.next_action.check_empty_assistant": "Check whether exception or cancellation paths left empty assistant placeholder messages.", + "ai_chat.inspection.message_flow.next_action.inspect_render_or_logs": "No obvious message-flow structure issue was found; continue with inspect_ai_last_render_error or inspect_app_logs for rendering/runtime diagnostics.", + "ai_chat.inspection.sql_editor_transaction.no_active_tab": "No active tab is currently selected", + "ai_chat.inspection.sql_editor_transaction.not_sql_tab": "The current active tab is not a SQL editor tab", + "ai_chat.inspection.sql_editor_transaction.semantics.managed_dml": "When the SQL editor runs INSERT/UPDATE/DELETE/MERGE/REPLACE DML, it first enters a managed transaction. The commit setting only decides when COMMIT happens after successful execution.", + "ai_chat.inspection.sql_editor_transaction.semantics.explicit_transaction": "Explicit transaction control statements were detected, so GoNavi will not wrap another SQL editor managed transaction around them.", + "ai_chat.inspection.sql_editor_transaction.semantics.no_managed_transaction": "The current SQL does not trigger a SQL editor managed transaction. Read-only queries still use the normal query path.", + "ai_chat.inspection.sql_editor_transaction.warning.pending_transactions": "There are {{count}} SQL editor managed transactions pending commit or rollback", + "ai_chat.inspection.sql_editor_transaction.warning.active_pending_transaction": "The active SQL tab already has a pending transaction. Commit or roll it back before running another DML statement.", + "ai_chat.inspection.sql_editor_transaction.warning.auto_commit_managed_dml": "Auto commit is enabled, but DML still enters a managed transaction and only runs COMMIT after the delay expires.", + "ai_chat.inspection.sql_editor_transaction.warning.explicit_transaction_control": "The current SQL already contains explicit transaction control, so the SQL editor will not take over commit or rollback.", + "ai_chat.inspection.sql_editor_transaction.next_action.resolve_active_pending": "Ask the user to click \"Commit\" or \"Rollback\" in the result transaction bar, or wait for the auto-commit countdown to finish.", + "ai_chat.inspection.sql_editor_transaction.next_action.switch_to_pending_tab": "Before continuing with DML, switch back to the matching SQL tab and resolve the pending transaction.", + "ai_chat.inspection.sql_editor_transaction.next_action.explain_auto_commit": "Explain that the current DML opens a managed transaction first and auto-commits about {{seconds}} seconds after successful execution.", + "ai_chat.inspection.sql_editor_transaction.next_action.explain_manual_commit": "Explain that the current DML opens a managed transaction first and requires a manual Commit or Rollback after successful execution.", + "ai_chat.inspection.sql_editor_transaction.next_action.switch_to_sql_tab": "Switch to a query tab with a SQL draft first, or ask the user to paste the SQL they want to run.", + "ai_chat.inspection.sql_editor_transaction.next_action.inspect_recent_activity": "Review recent write or transaction execution results in recentRelevantLogs, and call inspect_recent_sql_activity for deeper inspection if needed.", + "ai_chat.inspection.sql_editor_transaction.commit_policy.semantics": "SQL editor runs INSERT/UPDATE/DELETE/MERGE/REPLACE DML inside a managed transaction. Manual or auto mode only controls when COMMIT happens after successful execution, not whether a transaction is opened.", + "ai_chat.inspection.sql_log.unspecified_database": "(Unspecified database)", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.desc": "View recent SQL execution logs", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.detail": "Accepts optional limit and status filters, then returns recent SQL execution records including database, duration, success or failure, error, affected rows, and SQL text. Use it to trace failed statements, locate slow queries, and let AI explain or optimize based on real execution history.", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.tool_description": "Get a summary of recent SQL execution logs, optionally filtered by success or failure. Use it to review recently executed SQL, diagnose failures, locate slow queries, and let AI explain or optimize from real execution history.", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.param.limit": "Optional. Number of log entries to return. Default 20, maximum 100.", + "ai_chat.inspection.tool_info.inspect_recent_sql_logs.param.status": "Optional. Filter by execution status: all, success, or error. Default all.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.desc": "Summarize recent SQL activity distribution", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.detail": "Can filter by status, activityKind, dbName, and keyword, then returns a structured summary of recent SQL activity including read/write/DDL ratio, statement type distribution, database distribution, recent errors, recent writes, and slowest statements. Use it when the user asks what ran recently, whether data may have been deleted, which database is failing most, or whether recent activity is mostly reads or writes.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.tool_description": "Summarize the structured profile of recent SQL activity, optionally filtered by execution status, activity type, database name, and keyword. Use it to inspect recent read/write operations, concentrated errors in a database, DELETE or DDL activity, and let AI judge from the real execution scene first.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.limit": "Optional. Maximum number of recent activity samples to return. Default 30, maximum 100.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.status": "Optional. Filter by execution status: all, success, or error. Default all.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.activityKind": "Optional. Filter by activity type: all, read, write, ddl, transaction, session, or other. Default all.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.dbName": "Optional. Only include logs whose database name contains this keyword.", + "ai_chat.inspection.tool_info.inspect_recent_sql_activity.param.keyword": "Optional. Filter by SQL text, error message, statement type, or database name.", + "ai_chat.inspection.tool_info.inspect_sql_editor_transaction.desc": "View SQL editor transaction commit state", + "ai_chat.inspection.tool_info.inspect_sql_editor_transaction.detail": "Returns SQL editor managed-DML transaction semantics, current manual or auto commit setting, whether the active SQL tab will enter a managed transaction, pending transactions, and recent write or transaction execution records. Use it when the user asks what manual or auto commit means, whether there are uncommitted transactions, or whether update/insert/delete will commit automatically.", + "ai_chat.inspection.tool_info.inspect_sql_editor_transaction.tool_description": "Read a SQL editor transaction state snapshot, including the real semantics that DML always enters a managed transaction, current commit mode, auto-commit delay, whether the active SQL tab triggers a managed transaction, pending transactions, and recent write or transaction logs. Use it when the user asks about SQL editor manual commit, auto commit, uncommitted transactions, or whether DML commits after execution.", + "ai_chat.inspection.tool_info.inspect_sql_editor_transaction.param.includeSqlPreview": "Optional. Whether to return a SQL preview from the active SQL tab. Default true.", + "ai_chat.inspection.tool_info.inspect_sql_risk.desc": "Check execution risk for current or specified SQL", + "ai_chat.inspection.tool_info.inspect_sql_risk.detail": "Reads supplied SQL or the current active query tab content, detects multiple statements, writes, DDL, DELETE/UPDATE without WHERE, DROP/TRUNCATE, and other risks, then combines the result with current AI safety policy to say whether execution is allowed. Use it before AI executes SQL, explains risk, or confirms whether a SQL statement can run.", + "ai_chat.inspection.tool_info.inspect_sql_risk.tool_description": "Check execution risk for supplied SQL or the current active query tab SQL, returning statement count, activity type, risk level, risk points, whether user confirmation is required, and the current AI safety policy result. Use it before answering or continuing when the user asks to execute, delete, update, run DDL, run batch SQL, or asks whether a SQL statement can run.", + "ai_chat.inspection.tool_info.inspect_sql_risk.param.sql": "Optional. SQL to inspect. If omitted, the current active query tab SQL draft is read by default.", + "ai_chat.inspection.tool_info.inspect_sql_risk.param.previewCharLimit": "Optional. Maximum number of characters to return in the SQL preview. Default 12000, maximum 40000.", + "ai_chat.message.tool_call.inspect_current_connection": "Read current connection summary", + "ai_chat.message.tool_call.inspect_connection_capabilities": "Read current connection capability matrix", + "ai_chat.message.tool_call.inspect_saved_connections": "Review locally saved connections", + "ai_chat.message.tool_call.inspect_redis_topology": "Diagnose Redis topology configuration", + "ai_chat.message.tool_call.inspect_external_sql_directories": "Review external SQL directories", + "ai_chat.message.tool_call.inspect_external_sql_file": "Read external SQL file", + "ai_chat.message.tool_call.inspect_ai_sessions": "Review local AI chat sessions", + "ai_chat.message.tool_call.inspect_active_tab": "Read current active tab", + "ai_chat.message.tool_call.inspect_workspace_tabs": "Review current workspace tabs", + "ai_chat.message.tool_call.inspect_recent_sql_logs": "Review recent SQL execution logs", + "ai_chat.message.tool_call.inspect_recent_sql_activity": "Summarize recent SQL activity", + "ai_chat.message.tool_call.inspect_sql_editor_transaction": "Read SQL editor transaction status", + "ai_chat.message.tool_call.inspect_app_logs": "Review GoNavi application logs", + "ai_chat.message.tool_call.inspect_recent_connection_failures": "Summarize recent connection failures", + "ai_chat.message.tool_call.inspect_ai_last_render_error": "Read latest AI rendering error", + "ai_chat.message.tool_call.inspect_ai_message_flow": "Diagnose current AI message flow", + "ai_chat.message.tool_call.inspect_ai_context_budget": "Diagnose AI context budget risk", + "ai_chat.message.tool_call.inspect_codebase_hotspots": "Read codebase hotspot files", + "ai_chat.message.tool_call.inspect_saved_queries": "Search locally saved queries", + "ai_chat.message.tool_call.inspect_sql_snippets": "Read SQL snippet templates", + "ai_chat.message.tool_call.inspect_shortcuts": "Read current shortcut settings", + "ai_chat.message.tool_call.preview_table_rows": "Preview real sample rows", "ai_chat.message.tool_call.execute_sql": "Execute SQL query", "ai_chat.message.tool_call.running": "Running data probes...", "ai_chat.message.tool_call.done": "Data probes completed ({{count}} items)", @@ -3462,12 +4687,20 @@ "ai_chat.panel.tool_error.unknown_function": "Unknown function: {{functionName}}", "ai_chat.panel.tool_error.fetch_databases_failed": "Failed to fetch database list: {{detail}}", "ai_chat.panel.tool_error.fetch_tables_failed": "Failed to fetch table list: {{detail}}", + "ai_chat.panel.tool_error.fetch_all_columns_failed": "Failed to fetch database column summary: {{detail}}", "ai_chat.panel.tool_result.columns_exact_fields": "⚠️ The following is the exact field list for table {{tableName}}. When generating SQL, use only these field values as column names, exactly as shown. Do not change, abbreviate, or invent field names.\nAvailable fields: {{fieldNames}}\nDetails: {{detailJson}}", "ai_chat.panel.tool_error.fetch_columns_failed": "Failed to fetch column list: {{detail}}", + "ai_chat.panel.tool_error.fetch_indexes_failed": "Failed to fetch index definitions: {{detail}}", + "ai_chat.panel.tool_error.fetch_foreign_keys_failed": "Failed to fetch foreign key relationships: {{detail}}", + "ai_chat.panel.tool_error.fetch_triggers_failed": "Failed to fetch trigger definitions: {{detail}}", "ai_chat.panel.tool_error.fetch_table_ddl_failed": "Failed to fetch CREATE TABLE statement: {{detail}}", + "ai_chat.panel.tool_error.table_name_required": "tableName cannot be empty", + "ai_chat.panel.tool_error.preview_table_rows_failed": "Failed to preview table rows: {{detail}}", "ai_chat.panel.tool_error.sql_blocked": "Security policy blocked this request: the current safety level does not allow {{operationType}} SQL. Show the SQL to the user and ask them to run it manually.", "ai_chat.panel.tool_error.sql_execute_failed": "SQL execution failed", "ai_chat.panel.tool_error.sql_execute_exception": "SQL execution exception: {{detail}}", + "ai_chat.panel.tool_error.mcp_failed": "MCP tool call failed", + "ai_chat.panel.tool_error.mcp_failed_with_detail": "MCP tool call failed: {{detail}}", "ai_chat.panel.error.unknown": "Unknown error", "ai_chat.panel.error.http_server": "HTTP {{code}} server error", "ai_chat.panel.error.html_response": "The server returned an abnormal HTML response, possibly a gateway timeout or unavailable service", @@ -3498,6 +4731,10 @@ "ai_settings.nav.safety.description": "Limit AI operation risk level", "ai_settings.nav.context.title": "Context", "ai_settings.nav.context.description": "Configure database schema context", + "ai_settings.nav.mcp.title": "MCP services", + "ai_settings.nav.mcp.description": "Connect GoNavi to external clients and manage tool sources", + "ai_settings.nav.skills.title": "Skills", + "ai_settings.nav.skills.description": "Configure reusable prompt modules", "ai_settings.nav.tools.title": "Built-in tools", "ai_settings.nav.tools.description": "View data probes available to AI", "ai_settings.nav.prompts.title": "Built-in prompts", @@ -3533,7 +4770,7 @@ "ai_settings.provider_preset.volcengine_coding.label": "Volcengine Coding Plan", "ai_settings.provider_preset.volcengine_coding.desc": "Ark Code / Coding Plan", "ai_settings.provider_preset.minimax.label": "MiniMax", - "ai_settings.provider_preset.minimax.desc": "M2.7 / M2.5 series (Anthropic-compatible)", + "ai_settings.provider_preset.minimax.desc": "M3 / M2.7 series (Anthropic-compatible)", "ai_settings.provider_preset.ollama.label": "Ollama", "ai_settings.provider_preset.ollama.desc": "Locally deployed open-source models", "ai_settings.provider_preset.custom.label": "Custom", @@ -3590,6 +4827,19 @@ "ai_settings.context.with_results.label": "With query results", "ai_settings.context.with_results.desc": "Send recent query results as context", "ai_settings.prompts.description": "These are the read-only system prompts preset by the current GoNavi version. They are injected dynamically into request context for matching scenarios.", + "ai_settings.prompts.user.title": "User-level custom prompts", + "ai_settings.prompts.user.description": "This content is appended as a system message after the built-in system prompts. Use it for personal style preferences, output constraints, or team rules. System rules still take precedence for safety boundaries.", + "ai_settings.prompts.field.global.title": "Global extra prompt", + "ai_settings.prompts.field.global.description": "Applies to all AI sessions, such as \"state the conclusion first\" or \"keep answers concise\".", + "ai_settings.prompts.field.database.title": "Database session extra prompt", + "ai_settings.prompts.field.database.description": "Applies only to database and SQL scenarios, such as \"confirm field names before generating SQL\".", + "ai_settings.prompts.field.jvm.title": "JVM resource analysis extra prompt", + "ai_settings.prompts.field.jvm.description": "Applies only to JVM resource browsing and analysis scenarios.", + "ai_settings.prompts.field.jvm_diagnostic.title": "JVM diagnostics extra prompt", + "ai_settings.prompts.field.jvm_diagnostic.description": "Applies only to the JVM diagnostics workspace, such as \"give the plan before commands\".", + "ai_settings.prompts.placeholder.empty": "Leave empty to add nothing extra", + "ai_settings.prompts.action.save": "Save custom prompts", + "ai_settings.prompts.builtin.description": "These are the read-only low-level AI prompts preset by the current GoNavi version. They are injected before the user-level prompts above in the matching request context.", "ai_settings.prompts.message.saved": "Custom prompts saved", "ai_settings.prompts.message.save_failed": "Failed to save custom prompts", "ai_settings.mcp_server.message.saved": "MCP server saved", @@ -3599,6 +4849,323 @@ "ai_settings.mcp_server.message.test_success": "MCP server connection succeeded", "ai_settings.mcp_server.message.test_failed": "MCP server test failed", "ai_settings.mcp_server.message.test_request_failed": "Failed to test MCP server", + "ai_settings.mcp_server.draft.default_name": "MCP service", + "ai_settings.mcp_server.command_preview.title": "Auto split preview", + "ai_settings.mcp_server.command_preview.description": "After clicking \"Auto split into fields below\", this parsed result will be written into the startup configuration area below the service name.", + "ai_settings.mcp_server.command_preview.env_title": "Environment variables", + "ai_settings.mcp_server.command_preview.env_count": "Will write {{count}} environment variables.", + "ai_settings.mcp_server.command_preview.env_empty": "No prefixed environment variables were detected in this command.", + "ai_settings.mcp_server.command_preview.empty_value": "None", + "ai_settings.mcp_server.command_preview.command_title": "Startup command", + "ai_settings.mcp_server.command_preview.command_hint": "Only the executable program itself is kept here.", + "ai_settings.mcp_server.command_preview.args_title": "Command arguments", + "ai_settings.mcp_server.command_preview.args_count": "Will split into {{count}} separate argument tags.", + "ai_settings.mcp_server.command_preview.args_empty": "No extra arguments were detected in this command.", + "ai_settings.mcp_server.validation.title": "Configuration check", + "ai_settings.mcp_server.validation.severity.error": "Needs fix", + "ai_settings.mcp_server.validation.severity.warning": "Check recommended", + "ai_settings.mcp_server.validation.severity.info": "Tip", + "ai_settings.mcp_server.validation.summary.errors": "Found {{count}} issue that must be fixed before testing or saving.", + "ai_settings.mcp_server.validation.summary.warnings": "Found {{count}} item to check. You can still test and save.", + "ai_settings.mcp_server.validation.summary.ready": "The current configuration can be tested and saved.", + "ai_settings.mcp_server.validation.issue.name_missing.title": "Service name is empty", + "ai_settings.mcp_server.validation.issue.name_missing.detail": "Use a purpose name such as Browser, GitHub, or Filesystem; otherwise it can only be identified by command after saving.", + "ai_settings.mcp_server.validation.issue.transport_unsupported.title": "Transport is not supported", + "ai_settings.mcp_server.validation.issue.transport_unsupported.detail": "GoNavi can only add stdio MCP services here. Keep the transport set to stdio.", + "ai_settings.mcp_server.validation.issue.command_missing.title": "Startup command is missing", + "ai_settings.mcp_server.validation.issue.command_missing.detail": "Enter at least node, uvx, python, or a local executable path. Put the script name and --stdio in command arguments.", + "ai_settings.mcp_server.validation.issue.command_whole_line.title": "Startup command may contain the whole command line", + "ai_settings.mcp_server.validation.issue.command_whole_line.detail": "Put only the executable itself in startup command. Move the script name, module name, --stdio, and environment variables into arguments or environment variables.", + "ai_settings.mcp_server.validation.issue.args_missing_for_launcher.title": "Command arguments may be missing the script or module name", + "ai_settings.mcp_server.validation.issue.args_missing_for_launcher.detail": "Launchers such as node, python, uvx, and npx usually also need server.js, -m your_server, or a package name as an argument.", + "ai_settings.mcp_server.validation.issue.docker_run_missing.title": "Docker arguments are missing run", + "ai_settings.mcp_server.validation.issue.docker_run_missing.detail": "Docker MCP usually uses command=docker, with run, --rm, -i, the image name, and service arguments entered separately in args.", + "ai_settings.mcp_server.validation.issue.docker_interactive_missing.title": "Docker arguments are missing -i", + "ai_settings.mcp_server.validation.issue.docker_interactive_missing.detail": "MCP needs to keep reading standard input. Add -i or --interactive for docker run, otherwise tool discovery may disconnect immediately.", + "ai_settings.mcp_server.validation.issue.docker_image_missing.title": "Docker arguments may be missing the image name", + "ai_settings.mcp_server.validation.issue.docker_image_missing.detail": "Enter the image name from the README after docker run options, for example mcp/server-fetch:latest.", + "ai_settings.mcp_server.validation.issue.args_contain_env_or_shell_glue.title": "Command arguments may include environment variables or shell glue", + "ai_settings.mcp_server.validation.issue.args_contain_env_or_shell_glue.detail": "KEY=VALUE, $env:KEY=VALUE, set, env, and && belong in full-command auto split or in the environment variables field.", + "ai_settings.mcp_server.validation.issue.timeout_out_of_range.title": "Timeout is outside the recommended range", + "ai_settings.mcp_server.validation.issue.timeout_out_of_range.detail": "GoNavi will clamp it between 3 and 120 seconds. Regular local services usually use 20 seconds; slow-starting services can use 45 or 60 seconds.", + "ai_settings.mcp_server.validation.issue.env_invalid_lines.title": "Environment variables contain invalid lines", + "ai_settings.mcp_server.validation.issue.env_invalid_lines.detail": "Each line must be KEY=VALUE. {{count}} line(s) will not be saved: {{lines}}", + "ai_settings.mcp_server.help.field_state.required": "Required", + "ai_settings.mcp_server.help.field_state.fixed": "Fixed", + "ai_settings.mcp_server.help.field_state.optional": "Optional", + "ai_settings.mcp_server.help.example_prefix": "Example: ", + "ai_settings.mcp_server.form.name.title": "Service name", + "ai_settings.mcp_server.form.name.description": "Give this MCP a name you can recognize; it will appear directly in the AI tool list later. Avoid vague names like server or test.", + "ai_settings.mcp_server.form.name.placeholder": "Service name, for example: Filesystem / Browser / GitHub", + "ai_settings.mcp_server.form.enabled.title": "Enabled", + "ai_settings.mcp_server.form.enabled.description": "Disable it temporarily when not in use; the configuration stays saved but will not join AI tool discovery.", + "ai_settings.mcp_server.form.enabled.option.enabled": "Enabled", + "ai_settings.mcp_server.form.enabled.option.disabled": "Disabled", + "ai_settings.mcp_server.form.transport.title": "Transport", + "ai_settings.mcp_server.form.transport.description": "Only stdio is supported for now. GoNavi starts this process locally and communicates over standard input and output.", + "ai_settings.mcp_server.form.command.title": "Startup command", + "ai_settings.mcp_server.form.command.description": "Enter only the command itself here. For launchers such as npx/node/uvx/python/docker, put the package name, script name, module name, or docker run arguments in the arguments field below. Do not paste the whole npx -y package --stdio, node server.js --stdio, or docker run -i image command here.", + "ai_settings.mcp_server.form.command.placeholder": "Startup command, for example: npx / node / uvx / python / docker", + "ai_settings.mcp_server.form.timeout.title": "Timeout (seconds)", + "ai_settings.mcp_server.form.timeout.description": "Maximum wait time for one tool discovery or tool call. Most local tools can keep the default 20 seconds; increase it for remote services or slow-starting scripts.", + "ai_settings.mcp_server.form.timeout.placeholder": "Timeout (seconds)", + "ai_settings.mcp_server.form.timeout.preset.default": "Default 20 seconds", + "ai_settings.mcp_server.form.timeout.preset.relaxed": "Relaxed 45 seconds", + "ai_settings.mcp_server.form.timeout.preset.slow": "Slow start 60 seconds", + "ai_settings.mcp_server.form.args.title": "Command arguments", + "ai_settings.mcp_server.form.args.description": "Enter each argument as a separate tag; do not put the command itself here. For npx -y package --stdio, split -y, package, and --stdio. For node server.js --stdio, split server.js and --stdio. For docker run --rm -i image, split run, --rm, -i, and the image name. If unsure, use the full command box above to auto-split it first.", + "ai_settings.mcp_server.form.args.placeholder": "Command arguments, press Enter to add, for example: -y / package / --stdio", + "ai_settings.mcp_server.form.launch_preview.title": "Actual launch command preview", + "ai_settings.mcp_server.form.launch_preview.description": "GoNavi will start the process in the form below so you can confirm the command and arguments were split correctly.", + "ai_settings.mcp_server.form.env.title": "Environment variables", + "ai_settings.mcp_server.form.env.description": "Use one KEY=VALUE per line, usually for API Key, working directory, service URL, and similar configuration. Leave it empty if not needed. These values are saved to local configuration and passed as environment variables when starting the MCP process; do not write export, and do not put secrets into chat content.", + "ai_settings.mcp_server.form.env.placeholder": "Environment variables, one KEY=VALUE per line, for example:\nOPENAI_API_KEY=...\nGITHUB_TOKEN=...", + "ai_settings.mcp_server.form.env_status.invalid": "Detected {{validCount}} environment variable(s), plus {{invalidCount}} invalid line(s) that will not be saved this time: {{invalidLines}}", + "ai_settings.mcp_server.form.env_status.valid": "Detected {{count}} environment variable(s).", + "ai_settings.mcp_server.form.env_status.empty": "Each line must be KEY=VALUE; lines without an equals sign or with spaces in the key will not be saved.", + "ai_settings.mcp_server.form.instructions.title": "Action guide", + "ai_settings.mcp_server.form.instructions.test_title": "Test tool discovery", + "ai_settings.mcp_server.form.instructions.test_description": "starts once with the current fields, checks which tools can be discovered, and does not save the configuration.", + "ai_settings.mcp_server.form.instructions.save_title": "Save", + "ai_settings.mcp_server.form.instructions.save_description": "writes this MCP into local configuration for long-term use.", + "ai_settings.mcp_server.form.instructions.tools_found": "The tools listed above are the aliases found by the most recent successful test.", + "ai_settings.mcp_server.form.instructions.test_first": "Test successfully before saving; after the test passes, the tools discovered from this service will appear above.", + "ai_settings.mcp_server.form.action.test": "Test tool discovery", + "ai_settings.mcp_server.form.action.save": "Save", + "ai_settings.mcp_server.form.action.delete": "Delete", + "ai_settings.mcp_server.form.action.delete_confirm": "Delete this MCP service?", + "ai_settings.mcp_server.form.action.delete_ok": "Delete", + "ai_settings.mcp_server.form.action.delete_cancel": "Cancel", + "ai_settings.mcp_server.guide.examples.title": "Examples", + "ai_settings.mcp_server.guide.examples.description": "Put only the executable itself in the startup command. Do not mix arguments into it. Common forms:", + "ai_settings.mcp_server.guide.order.title": "Recommended fill order", + "ai_settings.mcp_server.guide.order.description": "New users can follow this order: choose a template above or paste the full command, confirm the required fields below, then add arguments, environment variables, and timeout only when needed.", + "ai_settings.mcp_server.guide.step.template.title": "Template / full command", + "ai_settings.mcp_server.guide.step.template.detail": "Prefer the closest template, or paste one full command first and let GoNavi split it automatically.", + "ai_settings.mcp_server.guide.step.name.title": "Service name", + "ai_settings.mcp_server.guide.step.name.detail": "Name it by purpose, such as Browser, GitHub, or Filesystem, so it is recognizable at a glance.", + "ai_settings.mcp_server.guide.step.command.title": "Startup command", + "ai_settings.mcp_server.guide.step.command.detail": "Enter only the program name or launcher here. Do not paste the whole command line into it.", + "ai_settings.mcp_server.guide.step.args.title": "Command arguments", + "ai_settings.mcp_server.guide.step.args.detail": "Split script names, module names, Docker run arguments, and flags such as --stdio into separate entries.", + "ai_settings.mcp_server.guide.step.env_timeout.title": "Environment variables / timeout", + "ai_settings.mcp_server.guide.step.env_timeout.detail": "Add them only when the service really needs extra configuration. Leave them empty when not needed.", + "ai_settings.mcp_server.guide.field_lookup.title": "Field quick reference", + "ai_settings.mcp_server.guide.field_lookup.description": "If you do not know what to put in a parameter field, start here. Each field below also has more specific examples and cautions.", + "ai_settings.mcp_server.guide.field.fill_label": "Fill: ", + "ai_settings.mcp_server.argument_hints.category.secret": "Sensitive", + "ai_settings.mcp_server.argument_hints.category.path": "Path", + "ai_settings.mcp_server.argument_hints.category.endpoint": "Endpoint", + "ai_settings.mcp_server.argument_hints.category.network": "Network", + "ai_settings.mcp_server.argument_hints.category.mode": "Mode", + "ai_settings.mcp_server.argument_hints.category.runtime": "Runtime", + "ai_settings.mcp_server.argument_hints.category.generic": "Business", + "ai_settings.mcp_server.argument_hints.current_command": "Current command {{command}} argument hints", + "ai_settings.mcp_server.argument_hints.argument_details": "Argument details", + "ai_settings.mcp_server.argument_hints.masked_value": "Value is masked", + "ai_settings.mcp_server.argument_hints.value_hint_prefix": "Expected: ", + "ai_settings.mcp_server.argument_hints.business_arguments": "Detected business arguments", + "ai_settings.mcp_server.argument_hints.dont_screenshot": "Do not screenshot the real value", + "ai_settings.mcp_server.argument_hints.next_actions": "Next: {{actions}}", + "ai_settings.mcp_server.argument_hints.action_separator": "; ", + "ai_settings.mcp_server.argument_hints.required_complete": "Required arguments look complete. If the test fails, compare the README again for business arguments and environment variables.", + "ai_settings.mcp_server.argument_hints.fill_missing_required": "Fill missing required arguments: {{args}}", + "ai_settings.mcp_server.argument_hints.split_inline_args": "Split startup command field: keep {{command}}, move {{count}} arguments", + "ai_settings.mcp_server.env_hints.category.secret": "Secret", + "ai_settings.mcp_server.env_hints.category.endpoint": "Endpoint", + "ai_settings.mcp_server.env_hints.category.proxy": "Proxy", + "ai_settings.mcp_server.env_hints.category.path": "Path", + "ai_settings.mcp_server.env_hints.category.runtime": "Runtime", + "ai_settings.mcp_server.env_hints.category.generic": "Custom", + "ai_settings.mcp_server.env_hints.title": "Environment variable usage hints", + "ai_settings.mcp_server.env_hints.summary": "Detected {{envVarCount}} variables; {{secretLikeCount}} look like secrets. Only the key purpose and risk are explained here; values are not shown.", + "ai_settings.mcp_server.env_hints.recognized": "Recognized", + "ai_settings.mcp_server.env_hints.value_hint_prefix": "Expected: ", + "ai_settings.mcp_server.env_hints.empty_value": " Current value is empty.", + "ai_settings.mcp_server.env_hints.placeholder_value": " Current value looks like an example placeholder.", + "ai_settings.mcp_server.env_hints.warning_prefix": "Warning: {{warnings}}", + "ai_settings.mcp_server.env_hints.next_actions": "Next: {{actions}}", + "ai_settings.mcp_server.env_hints.action_separator": "; ", + "ai_settings.mcp_server.env_hints.known.github_token.label": "GitHub Token", + "ai_settings.mcp_server.env_hints.known.github_token.detail": "Usually used by GitHub MCP services to read repositories, issues, pull requests, or Actions.", + "ai_settings.mcp_server.env_hints.known.github_token.value_hint": "Enter a GitHub Personal Access Token with the minimum permissions required by the MCP README.", + "ai_settings.mcp_server.env_hints.known.gitlab_token.label": "GitLab Token", + "ai_settings.mcp_server.env_hints.known.gitlab_token.detail": "Usually used by GitLab MCP services to access projects, merge requests, or CI.", + "ai_settings.mcp_server.env_hints.known.gitlab_token.value_hint": "Enter a GitLab Access Token and restrict it to the required project scope.", + "ai_settings.mcp_server.env_hints.known.openai_api_key.label": "OpenAI API Key", + "ai_settings.mcp_server.env_hints.known.openai_api_key.detail": "Used by MCP services that depend on OpenAI APIs for model or embedding calls.", + "ai_settings.mcp_server.env_hints.known.openai_api_key.value_hint": "Enter the real API Key; do not put it in command, args, or chat messages.", + "ai_settings.mcp_server.env_hints.known.anthropic_api_key.label": "Anthropic API Key", + "ai_settings.mcp_server.env_hints.known.anthropic_api_key.detail": "Used by MCP services that depend on the Anthropic Claude API.", + "ai_settings.mcp_server.env_hints.known.anthropic_api_key.value_hint": "Enter the real API Key only after confirming the service requires this variable.", + "ai_settings.mcp_server.env_hints.known.gemini_api_key.label": "Gemini API Key", + "ai_settings.mcp_server.env_hints.known.gemini_api_key.detail": "Used by MCP services that depend on the Google Gemini API.", + "ai_settings.mcp_server.env_hints.known.gemini_api_key.value_hint": "Enter the real API Key; some services may require GOOGLE_API_KEY instead.", + "ai_settings.mcp_server.env_hints.known.google_api_key.label": "Google API Key", + "ai_settings.mcp_server.env_hints.known.google_api_key.detail": "Used by Google, Gemini, Maps, or Search MCP services.", + "ai_settings.mcp_server.env_hints.known.google_api_key.value_hint": "Enter the real API Key and confirm whether the README requires GOOGLE_API_KEY or GEMINI_API_KEY.", + "ai_settings.mcp_server.env_hints.known.slack_bot_token.label": "Slack Bot Token", + "ai_settings.mcp_server.env_hints.known.slack_bot_token.detail": "Used by Slack MCP services to read channels, messages, or send notifications.", + "ai_settings.mcp_server.env_hints.known.slack_bot_token.value_hint": "Enter the Bot Token starting with xoxb- and restrict workspace permissions.", + "ai_settings.mcp_server.env_hints.known.notion_api_key.label": "Notion API Key", + "ai_settings.mcp_server.env_hints.known.notion_api_key.detail": "Used by Notion MCP services to access pages, databases, or workspace content.", + "ai_settings.mcp_server.env_hints.known.notion_api_key.value_hint": "Enter the Notion integration secret and authorize only the required pages.", + "ai_settings.mcp_server.env_hints.known.database_url.label": "Database connection string", + "ai_settings.mcp_server.env_hints.known.database_url.detail": "Lets the MCP service connect to a database itself; this gives database connection information to that MCP process.", + "ai_settings.mcp_server.env_hints.known.database_url.value_hint": "Fill this only when the MCP must connect to the database directly; prefer GoNavi MCP to avoid password exposure.", + "ai_settings.mcp_server.env_hints.known.http_proxy.label": "HTTP proxy", + "ai_settings.mcp_server.env_hints.known.http_proxy.detail": "Routes HTTP resource access from the MCP process through the specified proxy.", + "ai_settings.mcp_server.env_hints.known.http_proxy.value_hint": "Enter http://host:port; treat it as sensitive if the proxy includes a username or password.", + "ai_settings.mcp_server.env_hints.known.https_proxy.label": "HTTPS proxy", + "ai_settings.mcp_server.env_hints.known.https_proxy.detail": "Routes HTTPS resource access from the MCP process through the specified proxy.", + "ai_settings.mcp_server.env_hints.known.https_proxy.value_hint": "Enter http://host:port or https://host:port.", + "ai_settings.mcp_server.env_hints.known.no_proxy.label": "Proxy bypass list", + "ai_settings.mcp_server.env_hints.known.no_proxy.detail": "Specifies which domains or addresses should bypass the proxy.", + "ai_settings.mcp_server.env_hints.known.no_proxy.value_hint": "Use comma-separated entries, for example localhost,127.0.0.1,.corp.local.", + "ai_settings.mcp_server.env_hints.known.docker_host.label": "Docker Daemon address", + "ai_settings.mcp_server.env_hints.known.docker_host.detail": "Tells the docker CLI which Docker Engine to connect to.", + "ai_settings.mcp_server.env_hints.known.docker_host.value_hint": "Common on Windows: npipe:////./pipe/docker_engine; confirm security boundaries for remote Docker.", + "ai_settings.mcp_server.env_hints.known.gonavi_mcp_http_token.label": "GoNavi MCP HTTP Token", + "ai_settings.mcp_server.env_hints.known.gonavi_mcp_http_token.detail": "Used when a remote MCP HTTP service enables Bearer Token authentication.", + "ai_settings.mcp_server.env_hints.known.gonavi_mcp_http_token.value_hint": "Enter a high-entropy random token; do not reuse database passwords or model API Keys.", + "ai_settings.mcp_server.env_hints.known.node_env.label": "Node runtime environment", + "ai_settings.mcp_server.env_hints.known.node_env.detail": "Affects logging, debugging, or production mode for some Node MCP services.", + "ai_settings.mcp_server.env_hints.known.node_env.value_hint": "Usually production, development, or a value specified by the README.", + "ai_settings.mcp_server.env_hints.known.log_level.label": "Log level", + "ai_settings.mcp_server.env_hints.known.log_level.detail": "Controls how much log output the MCP service emits.", + "ai_settings.mcp_server.env_hints.known.log_level.value_hint": "Common values are debug, info, warn, and error; temporarily raise it for troubleshooting.", + "ai_settings.mcp_server.env_hints.inferred.secret.label": "Secret / Token", + "ai_settings.mcp_server.env_hints.inferred.secret.detail": "The variable name looks like a secret, token, password, or connection string.", + "ai_settings.mcp_server.env_hints.inferred.secret.value_hint": "Enter the real value, but keep it only in local MCP configuration; do not put it in command, args, or chat content.", + "ai_settings.mcp_server.env_hints.inferred.proxy.label": "Proxy configuration", + "ai_settings.mcp_server.env_hints.inferred.proxy.detail": "The variable name looks like a network proxy setting.", + "ai_settings.mcp_server.env_hints.inferred.proxy.value_hint": "Follow the README or enterprise proxy format, for example http://127.0.0.1:7890.", + "ai_settings.mcp_server.env_hints.inferred.endpoint.label": "Service endpoint", + "ai_settings.mcp_server.env_hints.inferred.endpoint.detail": "The variable name looks like a service URL, API endpoint, or host configuration.", + "ai_settings.mcp_server.env_hints.inferred.endpoint.value_hint": "Enter the URL, host, or endpoint the MCP Server needs to access.", + "ai_settings.mcp_server.env_hints.inferred.path.label": "Path / config file", + "ai_settings.mcp_server.env_hints.inferred.path.detail": "The variable name looks like a local path, directory, or config file location.", + "ai_settings.mcp_server.env_hints.inferred.path.value_hint": "Enter an absolute path accessible to the local MCP process; keep the drive letter for Windows paths.", + "ai_settings.mcp_server.env_hints.inferred.runtime.label": "Runtime switch", + "ai_settings.mcp_server.env_hints.inferred.runtime.detail": "The variable name looks like a runtime environment, logging, or debug switch.", + "ai_settings.mcp_server.env_hints.inferred.runtime.value_hint": "Use the enum value specified by the README.", + "ai_settings.mcp_server.env_hints.inferred.generic.label": "Custom configuration", + "ai_settings.mcp_server.env_hints.inferred.generic.detail": "No built-in variable hint matched; follow the matching field description in the MCP README.", + "ai_settings.mcp_server.env_hints.inferred.generic.value_hint": "Confirm the variable name casing exactly matches the README.", + "ai_settings.mcp_server.env_hints.warning.empty": "{{count}} environment variable values are empty and must be filled or removed before testing.", + "ai_settings.mcp_server.env_hints.warning.placeholder": "{{count}} environment variables still look like example placeholder values.", + "ai_settings.mcp_server.env_hints.warning.docker_env_not_forwarded": "When command=docker, these environment variables are passed only to the docker CLI and do not automatically enter the container.", + "ai_settings.mcp_server.env_hints.next_action.empty": "Fill values for {{keys}}, or remove variables you do not need.", + "ai_settings.mcp_server.env_hints.next_action.placeholder": "Replace {{keys}} with real values before testing tool discovery.", + "ai_settings.mcp_server.env_hints.next_action.docker_env": "If the MCP inside the container needs these variables, add -e KEY=VALUE or --env KEY=VALUE to args according to the README.", + "ai_settings.mcp_server.env_hints.next_action.secrets_local": "Secret-like variables are stored only in local configuration; do not send real values to chat, issues, or screenshots.", + "ai_settings.mcp_server.env_hints.next_action.keys_recognized": "Environment variable keys are recognizable; if testing fails, first check the variable name casing required by the README.", + "ai_settings.mcp_server.guide.field.avoid_label": "Avoid: ", + "ai_settings.mcp_server.guide.field.example_label": "Example:", + "ai_settings.mcp_server.guide.field.name.title": "Service name", + "ai_settings.mcp_server.guide.field.name.summary": "The name shown to you and the AI after saving.", + "ai_settings.mcp_server.guide.field.name.detail": "Name it by purpose. Prefer recognizable names such as Browser, GitHub, or Filesystem.", + "ai_settings.mcp_server.guide.field.name.fill": "The purpose name of this MCP, such as GitHub or Filesystem.", + "ai_settings.mcp_server.guide.field.name.avoid": "Avoid vague names such as server, test, or mcp1.", + "ai_settings.mcp_server.guide.field.enabled.title": "Enabled", + "ai_settings.mcp_server.guide.field.enabled.summary": "Controls whether this configuration currently participates in tool discovery and calls.", + "ai_settings.mcp_server.guide.field.enabled.detail": "Disabling only stops using it. It does not delete the configuration below.", + "ai_settings.mcp_server.guide.field.enabled.fill": "Choose disabled when it is temporarily unused; choose enabled when the AI should use it.", + "ai_settings.mcp_server.guide.field.enabled.avoid": "Do not delete it just to pause it temporarily, or you will need to configure command, args, and env again.", + "ai_settings.mcp_server.guide.field.enabled.example": "Enabled / Disabled", + "ai_settings.mcp_server.guide.field.transport.title": "Transport", + "ai_settings.mcp_server.guide.field.transport.summary": "How GoNavi communicates with this MCP Server.", + "ai_settings.mcp_server.guide.field.transport.detail": "Currently fixed to stdio, meaning GoNavi starts a local process and exchanges data through standard input and output.", + "ai_settings.mcp_server.guide.field.transport.fill": "Keep stdio.", + "ai_settings.mcp_server.guide.field.transport.avoid": "Do not enter HTTP, SSE, a URL, or a port. This add flow is not remote MCP URL configuration.", + "ai_settings.mcp_server.guide.field.command.title": "Startup command", + "ai_settings.mcp_server.guide.field.command.summary": "Enter only the program name or launcher itself.", + "ai_settings.mcp_server.guide.field.command.detail": "Common values are npx, node, uvx, python, and docker. Put package names, script names, run, -i, and --stdio into arguments.", + "ai_settings.mcp_server.guide.field.command.fill": "Enter npx, node, uvx, python, docker, or an absolute path to an exe.", + "ai_settings.mcp_server.guide.field.command.avoid": "Do not enter the whole command line, such as npx -y pkg --stdio.", + "ai_settings.mcp_server.guide.field.args.title": "Command arguments", + "ai_settings.mcp_server.guide.field.args.summary": "Split script names, module names, and flags into separate entries.", + "ai_settings.mcp_server.guide.field.args.detail": "For npx -y pkg --stdio, split it into -y, pkg, and --stdio. For docker run --rm -i image, split it into run, --rm, -i, and image.", + "ai_settings.mcp_server.guide.field.args.fill": "Enter -y, package name, script name, -m, --stdio, run, --rm, -i, image name, and similar arguments one by one.", + "ai_settings.mcp_server.guide.field.args.avoid": "Do not enter npx/node/uvx/python/docker again, and do not paste multiple arguments into one long string.", + "ai_settings.mcp_server.guide.field.env.title": "Environment variables", + "ai_settings.mcp_server.guide.field.env.summary": "Pass KEY=VALUE configuration to the MCP Server.", + "ai_settings.mcp_server.guide.field.env.detail": "Usually used for API Key, service URL, working directory, and similar values. Use one line per variable and do not write export.", + "ai_settings.mcp_server.guide.field.env.fill": "Use one KEY=VALUE line, such as GITHUB_TOKEN=....", + "ai_settings.mcp_server.guide.field.env.avoid": "Do not write export, set, or a $env: prefix, and do not mix environment variables into command or args.", + "ai_settings.mcp_server.guide.field.timeout.title": "Timeout (seconds)", + "ai_settings.mcp_server.guide.field.timeout.summary": "Maximum wait time for one tool discovery or call.", + "ai_settings.mcp_server.guide.field.timeout.detail": "Regular local tools usually work with 20 seconds. Increase it for slow startup or remote links.", + "ai_settings.mcp_server.guide.field.timeout.fill": "Use 20 normally; use 45 or 60 for slow startup.", + "ai_settings.mcp_server.guide.field.timeout.avoid": "Do not choose an arbitrarily tiny value. Below 3 seconds can easily make tool discovery look failed.", + "ai_settings.mcp_server.guide.troubleshooting.title": "Common setup mistakes", + "ai_settings.mcp_server.guide.troubleshooting.description": "If a test fails, use this section to find which field to fix. Most issues are not a broken MCP, but a split command, argument, or environment variable problem.", + "ai_settings.mcp_server.guide.troubleshooting.cause_label": "Common cause: ", + "ai_settings.mcp_server.guide.troubleshooting.fix_label": "Fix: ", + "ai_settings.mcp_server.guide.troubleshooting.example_label": "Example:", + "ai_settings.mcp_server.guide.troubleshooting.command_not_found.symptom": "Test says the command cannot be found", + "ai_settings.mcp_server.guide.troubleshooting.command_not_found.cause": "The startup command contains the whole command line, the command is not on PATH, or a Windows path contains spaces but is not a real exe path.", + "ai_settings.mcp_server.guide.troubleshooting.command_not_found.fix": "Put only the executable itself in startup command. Put script names and --stdio in command arguments. If the command is not on PATH, enter the absolute path.", + "ai_settings.mcp_server.guide.troubleshooting.timeout_or_no_tools.symptom": "Test times out or discovers 0 tools", + "ai_settings.mcp_server.guide.troubleshooting.timeout_or_no_tools.cause": "The service starts slowly, lacks a stdio argument, the Docker container lacks -i, or the service only supports HTTP/SSE.", + "ai_settings.mcp_server.guide.troubleshooting.timeout_or_no_tools.fix": "Confirm the service supports stdio, then add --stdio or Docker -i. Increase timeout to 45 or 60 seconds when startup is slow.", + "ai_settings.mcp_server.guide.troubleshooting.auth_failed.symptom": "Authentication failed, 401, or 403", + "ai_settings.mcp_server.guide.troubleshooting.auth_failed.cause": "API Key, Token, service URL, or other environment variables are missing, or KEY=VALUE is invalid.", + "ai_settings.mcp_server.guide.troubleshooting.auth_failed.fix": "Write one KEY=VALUE line per environment variable. Do not write export, and do not mix environment variables into the startup command.", + "ai_settings.mcp_server.guide.troubleshooting.stdio_only.symptom": "README only provides URL or SSE configuration", + "ai_settings.mcp_server.guide.troubleshooting.stdio_only.cause": "This usually is not a local stdio process, and the current GoNavi add flow does not directly support it.", + "ai_settings.mcp_server.guide.troubleshooting.stdio_only.fix": "Prefer the service's stdio startup method. If it only has HTTP/SSE, use an official gateway or local wrapper to convert it to stdio first.", + "ai_settings.mcp_server.guide.full_command.title": "Only have one full command?", + "ai_settings.mcp_server.guide.full_command.description": "Paste the full command directly. GoNavi will split it into startup command / command arguments / environment variables. It supports Unix KEY=VALUE, Windows PowerShell $env:KEY=VALUE;, and cmd set KEY=VALUE && prefixes.", + "ai_settings.mcp_server.guide.full_command.placeholder": "Paste the full command, for example:\n{{example}}", + "ai_settings.mcp_server.guide.full_command.parsed_summary": "Will parse as: command {{command}}, {{argsCount}} argument(s), {{envCount}} environment variable(s).", + "ai_settings.mcp_server.guide.full_command.support_hint": "Supports quoted paths, arguments with spaces, and KEY=VALUE / $env:KEY=VALUE; / set KEY=VALUE && environment variable prefixes.", + "ai_settings.mcp_server.guide.full_command.apply": "Auto-split into the fields below", + "ai_settings.mcp_server.quick_add.title": "Quick add from one command", + "ai_settings.mcp_server.quick_add.description": "Choose the closest template, or paste a full startup command from the README. GoNavi will split it into command, args, and env, then create an editable MCP draft.", + "ai_settings.mcp_server.quick_add.templates_title": "Common startup templates", + "ai_settings.mcp_server.quick_add.templates_description": "If you are not sure how to split command and args, click a template to create a draft. Each card shows the command GoNavi will actually launch.", + "ai_settings.mcp_server.quick_add.action.parse_and_add": "Parse and add draft", + "ai_settings.mcp_server.command_parse.error.unclosed_quote": "The command contains an unclosed quote. Check it and try again.", + "ai_settings.mcp_server.command_parse.error.empty": "Paste the full command first.", + "ai_settings.mcp_server.command_parse.error.missing_command": "No startup command was parsed. Provide at least an executable name.", + "ai_settings.mcp_server.command_parse.error.failed": "Failed to parse the full command. Check the command format.", + "ai_settings.mcp_server.template.npx.title": "npx package", + "ai_settings.mcp_server.template.npx.description": "For npm MCP packages whose README uses `npx -y xxx --stdio`.", + "ai_settings.mcp_server.template.npx.detail": "The example uses `npx -y @modelcontextprotocol/server-filesystem --stdio`; replace the package name and path arguments with the real values.", + "ai_settings.mcp_server.template.npx.seed_name": "npx package", + "ai_settings.mcp_server.template.uvx.title": "uvx tool", + "ai_settings.mcp_server.template.uvx.description": "For published MCP packages in the Python/uv ecosystem.", + "ai_settings.mcp_server.template.uvx.detail": "The example uses `uvx some-mcp-server`; replace the package name before saving.", + "ai_settings.mcp_server.template.uvx.seed_name": "uvx tool", + "ai_settings.mcp_server.template.node.title": "Node script", + "ai_settings.mcp_server.template.node.description": "For local js/ts scripts or node launchers installed from npm.", + "ai_settings.mcp_server.template.node.detail": "The example uses `node server.js --stdio`; you can adjust the script name and arguments.", + "ai_settings.mcp_server.template.node.seed_name": "Node script", + "ai_settings.mcp_server.template.python.title": "Python module", + "ai_settings.mcp_server.template.python.description": "For services launched as modules, such as `python -m xxx`.", + "ai_settings.mcp_server.template.python.detail": "The example uses `python -m your_mcp_server`; replace the module name with the real one.", + "ai_settings.mcp_server.template.python.seed_name": "Python module", + "ai_settings.mcp_server.template.docker.title": "Docker image", + "ai_settings.mcp_server.template.docker.description": "For containerized MCP services whose README uses `docker run -i --rm image`. Docker must be installed locally.", + "ai_settings.mcp_server.template.docker.detail": "The example uses `docker run --rm -i mcp/server-fetch:latest`; container tokens are usually passed with -e KEY=VALUE in arguments.", + "ai_settings.mcp_server.template.docker.seed_name": "Docker MCP", + "ai_settings.mcp_server.template.exe.title": "Local EXE", + "ai_settings.mcp_server.template.exe.description": "For compiled local binaries or internal company tools.", + "ai_settings.mcp_server.template.exe.detail": "The example uses `your-mcp-server.exe stdio`; replace the exe path with the real value.", + "ai_settings.mcp_server.template.exe.seed_name": "Local EXE", + "ai_settings.mcp_server.guide.note.command_only": "Startup command should contain only the program itself. Do not mix script names, module names, or --stdio into it.", + "ai_settings.mcp_server.guide.note.npx": "When README shows an npx example, set command to npx and put -y, package name, and --stdio into args one by one. Do not put the whole npx command in command.", + "ai_settings.mcp_server.guide.note.docker": "When README shows a Docker example, set command to docker and put run, --rm, -i, image name, and container arguments into args one by one. Container tokens are usually passed with -e KEY=VALUE.", + "ai_settings.mcp_server.guide.note.full_command": "If README only gives one full command line, paste it into the full command box first for auto-splitting. It supports KEY=VALUE, env KEY=VALUE, PowerShell $env:KEY=VALUE;, and Windows set KEY=VALUE && prefixes.", + "ai_settings.mcp_server.guide.note.env_lines": "Use one KEY=VALUE line per environment variable. Do not write export, and do not mix it into the startup command.", + "ai_settings.mcp_server.guide.note.secrets": "Secret environment variables are saved in local configuration and passed only as process environment when starting the MCP process. Do not put secrets into chat content.", + "ai_settings.mcp_server.guide.note.test_discovery": "Test tool discovery starts the service temporarily for probing and does not save the configuration automatically.", + "ai_settings.mcp_server.section.quick_reference.title": "New MCP parameter quick reference", + "ai_settings.mcp_server.section.quick_reference.description": "Check these fields before adding a service: `command` is only the executable, `args` holds script names and --stdio, `env` uses one KEY=VALUE per line, and `timeout` controls one tool discovery or call wait.", + "ai_settings.mcp_server.section.quick_reference.footer": "Supports command, arguments, environment variables, and timeout. If unsure, read the Field quick reference cards first. After saving, the service enters the AI tool list automatically.", + "ai_settings.mcp_server.section.action.add_server": "Add MCP service", + "ai_settings.mcp_server.section.empty": "No MCP service yet. Common forms include `npx -y package --stdio`, `node server.js`, `uvx some-mcp-server`, `python -m server`, and `docker run --rm -i image`.", "ai_settings.clipboard.error.unsupported": "Copying to the clipboard is not supported in this environment", "ai_settings.mcp_http.error.control_unsupported_runtime": "The current runtime does not support MCP HTTP server control", "ai_settings.mcp_http.error.start_unsupported_version": "This version does not support starting the MCP HTTP server", @@ -3611,6 +5178,38 @@ "ai_settings.mcp_http.message.authorization_header_required": "Start the MCP HTTP server first to generate the Authorization Header", "ai_settings.mcp_http.message.authorization_header_copied": "Authorization Header copied", "ai_settings.mcp_http.status.not_running": "GoNavi MCP HTTP server is not running", + "ai_settings.mcp_http.panel.title": "GoNavi MCP HTTP service", + "ai_settings.mcp_http.panel.status.running": "Running", + "ai_settings.mcp_http.panel.status.stopped": "Stopped", + "ai_settings.mcp_http.panel.description": "For remote Agents such as OpenClaw and Hermans. When enabled, it listens on a local address and only exposes structure-reading tools for connections, databases, tables, columns, and DDL.", + "ai_settings.mcp_http.panel.switch.on": "On", + "ai_settings.mcp_http.panel.switch.off": "Off", + "ai_settings.mcp_http.panel.addr_label": "Listen address / port", + "ai_settings.mcp_http.panel.authorization_placeholder": "Bearer gnv_xxx (leave empty to generate automatically)", + "ai_settings.mcp_http.panel.running_hint": "The service is running. Configure the URL and Authorization Header in the remote MCP client.", + "ai_settings.mcp_http.panel.stopped_hint": "You can customize the local listen port and Bearer Token. If Authorization is empty, a random Token is generated on startup.", + "ai_settings.mcp_http.panel.copy_url": "Copy URL", + "ai_settings.mcp_http.panel.copy_authorization": "Copy Authorization", + "ai_settings.skill.description": "A Skill is not another large prompt. It is a named prompt module + scope + tool dependencies. At this stage, keep it in the main repository. You do not need a separate GitHub repository unless you later distribute a shared skill pack.", + "ai_settings.skill.hint": "When enabled, it is injected by scope. If a required tool is missing, this Skill is skipped automatically.", + "ai_settings.skill.action.add": "Add Skill", + "ai_settings.skill.empty": "No Skills yet. You can define dedicated system prompts for database, JVM, and diagnostic scenarios.", + "ai_settings.skill.name_placeholder": "Skill name, for example: SQL review / JVM diagnostic plan", + "ai_settings.skill.status.enabled": "Enabled", + "ai_settings.skill.status.disabled": "Disabled", + "ai_settings.skill.description_placeholder": "Private note, for example: confirm field names and risks before outputting SQL", + "ai_settings.skill.scope.global.label": "Global", + "ai_settings.skill.scope.global.desc": "Enabled for all AI sessions", + "ai_settings.skill.scope.database.label": "Database", + "ai_settings.skill.scope.database.desc": "Only enabled for SQL / database scenarios", + "ai_settings.skill.scope.jvm.label": "JVM resources", + "ai_settings.skill.scope.jvm.desc": "Only enabled for JVM resource analysis scenarios", + "ai_settings.skill.scope.jvm_diagnostic.label": "JVM diagnostics", + "ai_settings.skill.scope.jvm_diagnostic.desc": "Only enabled for the JVM diagnostic workbench", + "ai_settings.skill.scopes_placeholder": "Select where this Skill should apply", + "ai_settings.skill.required_tools_placeholder": "Optional: declare which tools this Skill depends on", + "ai_settings.skill.system_prompt_placeholder": "Enter the system prompt this Skill should append. Keep it focused on one clear capability and avoid duplicating global prompts.", + "ai_settings.skill.confirm_delete": "Delete this Skill?", "ai_settings.skill.message.saved": "Skill saved", "ai_settings.skill.message.save_failed": "Failed to save Skill", "ai_settings.skill.message.deleted": "Skill deleted", @@ -3655,6 +5254,7 @@ "driver_manager.action.install_enable": "Install and Enable", "driver_manager.action.remove": "Remove", "driver_manager.action.logs": "Logs", + "driver_manager.action.switch_version": "Switch version", "driver_manager.column.data_source": "Data Source", "driver_manager.column.package_size": "Package Size", "driver_manager.column.status": "Status", @@ -3698,6 +5298,8 @@ "driver_manager.message.load_version_failed": "Failed to load version list for {{name}}", "driver_manager.message.load_version_failed_detail": "Failed to load version list for {{name}}: {{detail}}", "driver_manager.message.install_start": "Starting installation", + "driver_manager.message.install_watchdog_timeout": "Installing {{name}} has not completed after {{minutes}} minutes. The background task may still be downloading or building. Refresh the status later; if this repeats, check the proxy or import a local driver package.", + "driver_manager.message.install_failed_fallback": "Failed to install {{name}}", "driver_manager.message.install_failed": "Failed to install {{name}}", "driver_manager.message.install_failed_detail": "Failed to install {{name}}: {{detail}}", "driver_manager.message.install_success": "{{name}}{{version}} installed and enabled", @@ -3754,12 +5356,15 @@ "driver_manager.progress.agent_install_start": "Starting {{name}} driver agent installation", "driver_manager.progress.agent_install_done": "{{name}} driver agent installation completed", "driver_manager.progress.download_prebuilt_agent": "Downloading prebuilt {{name}} driver agent", + "driver_manager.progress.download_prebuilt_package": "Downloading prebuilt {{name}} driver package", "driver_manager.progress.download_bundle": "Downloading {{name}} driver bundle", + "driver_manager.progress.wait_bundle": "Waiting for {{name}} driver bundle download to finish", "driver_manager.progress.extract_agent_from_bundle": "Extracting {{name}} agent from driver bundle", "driver_manager.progress.unzip_agent": "Extracting {{name}} driver agent", "driver_manager.progress.source_build_preferred": "Using local source build first for {{name}} driver agent", "driver_manager.progress.dev_build_fallback": "No prebuilt package matched; trying development local build", "driver_manager.progress.plan.source_only": "Preparing {{name}} driver agent installation (version {{version}}); this version only allows local source builds", + "driver_manager.progress.plan.require_source_first": "Preparing {{name}} driver agent installation (version {{version}}); development build uses local source only and will not fall back to release packages", "driver_manager.progress.plan.source_first": "Preparing {{name}} driver agent installation (version {{version}}); trying local source build first, then download fallback", "driver_manager.progress.plan.direct_then_bundle": "Preparing {{name}} driver agent installation (version {{version}}); trying {{direct}} prebuilt direct links, then {{bundle}} driver bundle sources", "driver_manager.progress.plan.explicit_direct": "Preparing {{name}} driver agent installation (version {{version}}); explicit version assets only, trying {{direct}} prebuilt direct links", @@ -3797,7 +5402,18 @@ "driver_manager.version.placeholder.load_on_expand": "Expand to load versions", "driver_manager.version.installed_locked_with_version": "{{version}} (installed; remove it to change)", "driver_manager.version.installed_locked": "Installed (remove it to change)", + "driver_manager.version.switch_pending": "Currently installed {{installedVersion}}; selected {{targetVersion}}. Click \"Switch version\" to apply.", + "driver_manager.version.current_fallback": "current version", + "driver_manager.version.target_fallback": "target version", + "driver_manager.version.installed_with_version": "{{version}} (installed{{suffix}})", + "driver_manager.version.installed": "Installed{{suffix}}", + "driver_manager.version.needs_reinstall_suffix": ", reinstall required", "driver_manager.version.mongodb_hint": "Currently only MongoDB 1.17.x and 2.x are supported. Older 1.x versions are not available for installation.", + "driver_manager.version.unlabeled": "Unlabeled version", + "driver_manager.version.latest_suffix": " (latest)", + "driver_manager.version.recommended_suffix": " (recommended)", + "driver_manager.package_size.built_in": "Built-in", + "driver_manager.package_size.pending_release": "Pending release", "driver_manager.backend.dialog.select_download_directory": "Select driver download directory", "driver_manager.backend.dialog.select_package_file": "Select driver package file (not a JDBC Jar)", "driver_manager.backend.dialog.select_package_directory": "Select driver package directory", @@ -3829,9 +5445,15 @@ "driver_manager.backend.error.version_empty": "Version is empty", "driver_manager.backend.error.asset_name_empty": "Driver asset name is empty", "driver_manager.backend.error.mongo_version_unsupported": "MongoDB version {{version}} is not supported; only 1.17.x and 2.x are supported", + "driver_manager.backend.error.driver_version_unsupported": "{{name}} version {{version}} is not supported", "driver_manager.backend.error.open_directory_unsupported": "Opening directories is not supported on this platform: {{platform}}", "driver_manager.backend.error.open_directory_failed": "Failed to open driver directory: {{detail}}", "driver_manager.backend.error.create_directory_failed": "Failed to create driver directory: {{detail}}", + "driver_manager.backend.error.create_named_directory_failed": "Failed to create {{name}} driver directory: {{detail}}", + "driver_manager.backend.error.remove_installed_agent_failed": "Failed to remove installed {{name}} driver-agent: {{detail}}", + "driver_manager.backend.error.agent_path_occupied_by_directory": "{{name}} driver-agent path is occupied by a directory: {{path}}", + "driver_manager.backend.error.copy_bundled_agent_failed": "Failed to copy bundled {{name}} driver-agent: {{detail}}", + "driver_manager.backend.error.bundled_agent_hash_failed": "Failed to calculate bundled {{name}} driver-agent checksum: {{detail}}", "driver_manager.backend.error.remove_package_failed": "Failed to remove driver package: {{detail}}", "driver_manager.backend.error.manifest_scheme_unsupported": "Unsupported driver manifest URL scheme: {{scheme}}", "driver_manager.backend.error.manifest_fetch_failed": "Failed to fetch driver manifest: {{detail}}", @@ -3860,19 +5482,68 @@ "driver_manager.backend.error.local_package_path_empty": "Local driver package path is empty", "driver_manager.backend.error.local_directory_path_empty": "Local driver directory path is empty", "driver_manager.backend.error.file_path_empty": "File path is empty", + "driver_manager.backend.error.metadata_payload_encode_failed": "Failed to encode driver metadata: {{detail}}", + "driver_manager.backend.error.metadata_file_write_failed": "Failed to write driver metadata file: {{detail}}", + "driver_manager.backend.error.zip_entry_empty": "Zip entry is empty", "driver_manager.backend.error.download_url_empty": "Download URL is empty", "driver_manager.backend.error.bundle_url_empty": "Driver bundle download URL is empty", "driver_manager.backend.error.read_local_package_failed": "Failed to read local driver package: {{detail}}", "driver_manager.backend.error.read_local_directory_failed": "Failed to read local driver directory: {{detail}}", + "driver_manager.backend.error.local_directory_not_directory": "Local driver directory path is not a directory: {{path}}", + "driver_manager.backend.error.local_directory_scan_limit": "Local driver directory has too many entries (over {{max}}). Narrow the directory or choose a zip/single file directly.", "driver_manager.backend.error.scan_local_directory_failed": "Failed to scan local driver directory: {{detail}}", + "driver_manager.backend.error.local_directory_entry_missing": "{{name}} agent file was not found in the directory (preferred path: {{path}}, candidate file names: {{assetCandidates}} / {{baseCandidates}})", "driver_manager.backend.error.open_local_package_failed": "Failed to open local driver package: {{detail}}", + "driver_manager.backend.error.local_package_entry_missing": "{{name}} agent file was not found in the local driver package (expected path: {{path}})", "driver_manager.backend.error.read_local_package_entry_failed": "Failed to read local driver package entry: {{detail}}", + "driver_manager.backend.error.import_local_agent_failed": "Failed to import local driver agent: {{detail}}", + "driver_manager.backend.error.import_local_agent_runtime_failed": "Failed to import local driver agent runtime dependencies: {{detail}}", + "driver_manager.backend.error.runtime_dependency_directory_empty": "Runtime dependency directory is empty", + "driver_manager.backend.error.copy_runtime_dependency_entry_failed": "Failed to copy runtime dependency {{name}}: {{detail}}", + "driver_manager.backend.error.runtime_dependency_target_directory_empty": "Runtime dependency target directory is empty", + "driver_manager.backend.error.runtime_dependency_entry_missing": "Driver package is missing runtime dependency: {{name}}", + "driver_manager.backend.error.extract_runtime_dependency_failed": "Failed to extract runtime dependency {{name}}: {{detail}}", "driver_manager.backend.error.download_failed": "Download failed: {{detail}}", "driver_manager.backend.error.bundle_download_failed": "Failed to download driver bundle: {{detail}}", "driver_manager.backend.error.open_bundle_failed": "Failed to open driver bundle: {{detail}}", "driver_manager.backend.error.read_bundle_entry_failed": "Failed to read driver bundle entry: {{detail}}", "driver_manager.backend.error.source_build_failed": "Local source build failed: {{detail}}", + "driver_manager.backend.error.source_build_tag_unconfigured": "No build tags are configured for driver type: {{driverType}}", + "driver_manager.backend.error.source_build_workdir_unavailable": "Failed to get current directory: {{detail}}", + "driver_manager.backend.error.source_build_project_root_missing": "Optional driver agent source was not found in the project; please use a published build", + "driver_manager.backend.error.source_build_go_mod_read_failed": "Failed to read go.mod: {{detail}}", + "driver_manager.backend.error.source_build_module_dependency_missing": "Driver dependency was not found in go.mod: {{modulePath}}", + "driver_manager.backend.error.source_build_temp_directory_create_failed": "Failed to create a temporary directory for driver build: {{detail}}", + "driver_manager.backend.error.source_build_temp_go_mod_write_failed": "Failed to write temporary go.mod: {{detail}}", + "driver_manager.backend.error.source_build_temp_go_sum_write_failed": "Failed to write temporary go.sum: {{detail}}", + "driver_manager.backend.error.source_build_module_or_version_empty": "Driver module path or version is empty", + "driver_manager.backend.error.source_build_duckdb_windows_cgo_toolchain_prepare_failed": "Failed to prepare DuckDB Windows CGO toolchain: {{detail}}", + "driver_manager.backend.error.source_build_duckdb_windows_dynamic_library_prepare_failed": "Failed to prepare DuckDB Windows dynamic library: {{detail}}", + "driver_manager.backend.error.source_build_duckdb_windows_toolchain_install_hint": "Please install the MSYS2 UCRT64 toolchain first: winget install --id MSYS2.MSYS2 -e; then run C:\\msys64\\usr\\bin\\bash.exe -lc \"pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-binutils\"", + "driver_manager.backend.error.source_build_duckdb_windows_gcc_not_found": "No usable gcc.exe/g++.exe was found; {{hint}}", + "driver_manager.backend.error.source_build_duckdb_windows_gcc_not_found_with_checked": "No usable gcc.exe/g++.exe was found. Checked: {{checked}}; {{hint}}", + "driver_manager.backend.error.source_build_duckdb_windows_dynamic_library_missing_files": "DuckDB official dynamic library package is missing files: {{files}}", + "driver_manager.backend.error.source_build_duckdb_windows_dlltool_resolve_failed": "Failed to locate DuckDB Windows dlltool: {{detail}}", "driver_manager.backend.error.prebuilt_downloads_failed": "Prebuilt package download failed: {{detail}}", + "driver_manager.backend.error.install_prebuilt_package_failed": "Failed to install prebuilt driver package: {{detail}}", + "driver_manager.backend.error.agent_hash_failed": "Failed to calculate driver agent checksum: {{detail}}", + "driver_manager.backend.error.agent_metadata_unavailable": "{{name}} driver-agent version metadata is unavailable. Install the driver-agent that matches the current version: {{detail}}", + "driver_manager.backend.error.agent_revision_mismatch": "{{name}} driver-agent revision does not match (installed: {{actual}}, required: {{expected}}). Install the driver-agent that matches the current version.", + "driver_manager.backend.error.agent_revision_mismatch_empty_actual": "{{name}} driver-agent revision does not match (installed: empty, required: {{expected}}). Install the driver-agent that matches the current version.", + "driver_manager.backend.error.runtime_dependency_required": "{{name}} requires bundled runtime dependencies on the current platform ({{files}}); single-file agent installation is not supported. Use the driver bundle, a dedicated driver zip, or a local source build.", + "driver_manager.backend.error.chmod_agent_failed": "Failed to set driver agent permissions: {{detail}}", + "driver_manager.backend.error.replace_agent_failed": "Failed to replace driver agent: {{detail}}", + "driver_manager.backend.error.bundle_entry_missing": "{{name}} was not found in the driver bundle (expected path: {{path}})", + "driver_manager.backend.error.create_agent_temp_file_failed": "Failed to create driver agent temporary file: {{detail}}", + "driver_manager.backend.error.write_agent_failed": "Failed to write driver agent: {{detail}}", + "driver_manager.backend.error.sync_agent_failed": "Failed to flush driver agent to disk: {{detail}}", + "driver_manager.backend.error.close_agent_file_failed": "Failed to close driver agent file: {{detail}}", + "driver_manager.backend.error.go_not_found_prebuilt_missing": "Go is not installed in the current environment, and no available prebuilt {{name}} agent package was found", + "driver_manager.backend.error.source_build_timeout": "Building the {{name}} driver agent timed out (over {{timeout}}). Prefer a prebuilt driver package or local driver package import.", + "driver_manager.backend.error.source_build_command_failed": "Failed to build the {{name}} driver agent: {{detail}}; output: {{output}}", + "driver_manager.backend.error.copy_runtime_dependency_failed": "Failed to copy {{name}} runtime dependencies: {{detail}}", + "driver_manager.backend.error.named_chmod_agent_failed": "Failed to set {{name}} driver agent permissions: {{detail}}", + "driver_manager.backend.error.named_agent_hash_failed": "Failed to calculate {{name}} driver agent checksum: {{detail}}", "driver_manager.backend.status.built_in_available": "Built-in driver is ready to connect", "driver_manager.backend.status.optional_enabled": "Go driver is enabled and ready to connect", "driver_manager.backend.status.installed_pending_with_version": "Driver is installed (version: {{version}}) and pending activation", @@ -3883,6 +5554,9 @@ "driver_manager.backend.status.installed_revision": "installed revision {{revision}}.", "driver_manager.backend.status.expected_revision": "expected revision {{revision}}.", "driver_manager.backend.status.affected_connections": "Affects {{count}} saved connections", + "driver_manager.backend.status.agent_revision_update_detail": "Reason: the current GoNavi version requires the updated {{name}} driver-agent (revision: {{expected}}). Impact: the driver-agent is a standalone binary and is not updated automatically with the main app. Without reinstalling, the old agent logic will continue to run, so driver-side fixes or optimizations will not take effect and old-version issues may continue. Strongly recommend reinstalling the matching driver-agent.", + "driver_manager.backend.status.agent_revision_update_detail_with_actual": "Reason: the current GoNavi version requires the updated {{name}} driver-agent (revision: {{expected}}). Impact: the driver-agent is a standalone binary and is not updated automatically with the main app. Without reinstalling, the old agent logic will continue to run, so driver-side fixes or optimizations will not take effect and old-version issues may continue. Strongly recommend reinstalling the matching driver-agent (installed marker: {{actual}}, required: {{expected}}).", + "driver_manager.backend.status.mongodb_compatibility_update_detail": "Reason: the recommended MongoDB compatibility driver version is {{recommended}}, and the installed version is {{installed}}. Impact: MongoDB 2.x driver-agent uses the official v2 driver and requires MongoDB server 4.2+; connecting to MongoDB 4.0 can hit the wire version 7 incompatibility. Strongly recommend reinstalling the matching driver-agent.", "driver_manager.backend.status.unrecognized_driver_type": "Unrecognized data source type", "driver_manager.backend.status.slim_build_required": "{{name}} is not included in the current slim build. Install the Full edition to use this driver.", "driver_manager.backend.status.agent_path_failed": "{{name}} driver agent path could not be resolved; reinstall and enable it in Driver Manager.", @@ -3895,6 +5569,7 @@ "driver_manager.backend.network.probe.go_module_proxy": "Go module proxy", "driver_manager.backend.network.error.probe_url_empty": "Probe URL is empty", "driver_manager.backend.network.error.probe_host_missing": "Probe URL is missing a host", + "driver_manager.backend.network.error.timeout": "Network connection timed out", "driver_manager.backend.network.summary.download_chain_unreachable": "GitHub API is reachable, but the driver download chain is unreachable. Enable GoNavi global proxy first, allow github.com, api.github.com, release-assets.githubusercontent.com, objects.githubusercontent.com, and raw.githubusercontent.com in proxy rules, then consider TUN mode if it still fails.", "driver_manager.backend.progress.plan.source_only": "Preparing to install {{name}} driver agent (version {{version}}); this version only allows local source builds", "driver_manager.backend.progress.plan.source_first": "Preparing to install {{name}} driver agent (version {{version}}); try a local source build first, then continue with download fallback if it fails", @@ -4456,6 +6131,9 @@ "file.backend.dialog.export_table": "Export {{table}}", "file.backend.dialog.export_tables_sql": "Export tables (SQL)", "file.backend.dialog.import_data": "Import into {{table}}", + "file.backend.dialog.select_ca_server_certificate_file": "Select CA/server certificate file", + "file.backend.dialog.select_client_certificate_file": "Select client certificate file", + "file.backend.dialog.select_client_private_key_file": "Select client private key file", "file.backend.dialog.select_config_file": "Select config file", "file.backend.dialog.select_database_file": "Select database file", "file.backend.dialog.select_duckdb_file": "Select DuckDB data file", @@ -4463,6 +6141,7 @@ "file.backend.dialog.select_sql_file": "Select SQL file", "file.backend.dialog.select_sqlite_file": "Select SQLite data file", "file.backend.dialog.select_ssh_key_file": "Select SSH private key file", + "file.backend.dialog.select_tls_certificate_file": "Select TLS certificate file", "file.backend.html_export.document_title": "GoNavi Export", "file.backend.html_export.empty_rows": "(0 rows)", "file.backend.html_export.heading": "GoNavi Data Export", @@ -4481,9 +6160,17 @@ "file.backend.error.connection_package_password_required": "Connection package password cannot be empty", "file.backend.error.connection_package_payload_too_large": "Connection package payload is too large", "file.backend.error.connection_package_unsupported": "Unsupported connection package format", + "file.backend.error.create_directory_failed": "Unable to create directory: {{detail}}", + "file.backend.error.create_sql_file_failed": "Unable to create SQL file: {{detail}}", "file.backend.error.database_name_required": "Database name cannot be empty", + "file.backend.error.delete_sql_directory_failed": "Unable to delete directory: {{detail}} (only empty directories can be deleted)", + "file.backend.error.delete_sql_file_failed": "Unable to delete SQL file: {{detail}}", + "file.backend.error.directory_exists": "Directory already exists", + "file.backend.error.directory_name_no_separator": "Directory name cannot contain path separators", + "file.backend.error.directory_name_required": "Directory name cannot be empty", "file.backend.error.directory_path_required": "Directory path cannot be empty", "file.backend.error.export_unsupported_format": "Unsupported export format: {{format}}", + "file.backend.error.app_log_file_not_found": "GoNavi log file was not found", "file.backend.error.file_path_empty": "File path is empty", "file.backend.error.file_path_required": "File path cannot be empty", "file.backend.error.import_file_empty": "File path cannot be empty", @@ -4500,13 +6187,33 @@ "file.backend.error.invalid_export_mode": "Invalid export mode", "file.backend.error.mysql_workbench_no_connections": "No valid connection profiles were found in the XML", "file.backend.error.mysql_workbench_parse_failed": "Failed to parse MySQL Workbench XML: {{detail}}", + "file.backend.error.navicat_connection_password_parse_failed": "Failed to parse password for connection {{name}}", + "file.backend.error.navicat_connection_proxy_password_parse_failed": "Failed to parse proxy password for connection {{name}}", + "file.backend.error.navicat_connection_ssh_password_parse_failed": "Failed to parse SSH password for connection {{name}}", + "file.backend.error.navicat_ncx_no_connections": "No valid GoNavi-supported connection configuration was found in Navicat NCX", + "file.backend.error.navicat_ncx_parse_failed": "Failed to parse Navicat NCX", + "file.backend.error.navicat_secret_decrypt_failed": "Failed to decrypt Navicat password", "file.backend.error.open_file_failed": "Unable to open file: {{detail}}", "file.backend.error.query_required": "Query statement cannot be empty", + "file.backend.error.read_directory_info_failed": "Unable to read directory info: {{detail}}", "file.backend.error.read_file_error_summary": "File read error: {{detail}}. Executed {{count}}.", "file.backend.error.read_file_info_failed": "Unable to read file info: {{detail}}", + "file.backend.error.read_target_directory_info_failed": "Unable to read target directory info: {{detail}}", + "file.backend.error.read_target_file_info_failed": "Unable to read target file info: {{detail}}", + "file.backend.error.rename_directory_failed": "Unable to rename directory: {{detail}}", + "file.backend.error.rename_sql_file_failed": "Unable to rename SQL file: {{detail}}", "file.backend.error.selected_path_not_directory": "Selected path is not a directory", "file.backend.error.selected_path_not_sql_file": "Selected path is not a SQL file", "file.backend.error.select_with_query_required": "Only SELECT/WITH query export is supported", + "file.backend.error.schema_export_no_objects": "No exportable tables or views were found in schema {{schema}}", + "file.backend.error.schema_name_required": "Schema name cannot be empty", + "file.backend.error.sql_file_batch_execution_failed": "Batch execution failed starting at statement {{index}}: {{detail}}", + "file.backend.error.sql_file_batch_rollback_failed": "Batch execution failed: {{detail}}; rollback failed: {{rollbackDetail}}", + "file.backend.error.sql_file_exists": "SQL file already exists", + "file.backend.error.sql_file_extension_required": "Only SQL files are supported", + "file.backend.error.sql_file_name_no_separator": "SQL file name cannot contain path separators", + "file.backend.error.sql_file_name_required": "SQL file name cannot be empty", + "file.backend.error.sql_file_statement_execution_failed": "Statement {{index}} failed: {{detail}}", "file.backend.error.task_not_found": "Task not found", "file.backend.error.table_data_batch_limit": "You can process at most {{max}} tables at once; currently selected {{count}}", "file.backend.error.table_data_clear_failed": "Failed to clear {{table}}: {{detail}}", @@ -4516,6 +6223,8 @@ "file.backend.error.table_data_truncate_failed": "Failed to truncate {{table}}: {{detail}}", "file.backend.error.table_data_truncate_failed_partial": "Failed to truncate {{table}}: {{detail}}. Warning: the first {{count}} tables have been truncated and cannot be restored", "file.backend.error.table_data_truncate_unsupported": "Current database type {{type}} does not support truncating tables. Use clearing instead", + "file.backend.error.target_directory_exists": "Target directory already exists", + "file.backend.error.target_sql_file_exists": "Target SQL file already exists", "file.backend.error.write_failed": "Write failed: {{detail}}", "file.backend.message.cancel_requested": "Cancellation request sent", "file.backend.message.execution_cancelled": "Execution canceled. Executed {{executed}}, failed {{failed}}, duration {{duration}}.", @@ -4533,6 +6242,7 @@ "file.backend.message.user_cancelled": "User canceled execution", "file.backend.filter.all_files": "All files", "file.backend.filter.all_files_pattern": "All files (*.*)", + "file.backend.filter.certificate_files": "Certificate files", "file.backend.filter.connection_package": "GoNavi Connection Package (*.gonavi-conn)", "file.backend.filter.database_files": "Database files", "file.backend.filter.data_files": "Data files", @@ -4542,7 +6252,52 @@ "file.backend.filter.private_key_files": "Private key files", "file.backend.filter.sql_files": "SQL files (*.sql)", "file.backend.filter.sqlite_files": "SQLite files", + "ai_service.backend.builtin_prompt.title.general_chat": "General chat assistant", + "ai_service.backend.builtin_prompt.body.general_chat": "You are the GoNavi AI assistant, a dedicated expert system deeply integrated into the GoNavi database and cache client.\nYour goal is to be the most useful second brain for developers, DBAs, and data scientists by providing professional, precise, and forward-looking data-side solutions.\n\nCore persona and interaction tone:\n- Professionally grounded: make sound judgments about database products such as MySQL, PostgreSQL, DuckDB, and Redis, including execution plans, indexing, and storage behavior.\n- Direct and practical: avoid empty chatter. When the user's intent is clear, lead with elegant code or steps they can use directly.\n- Structured and readable: use Markdown headings, emphasis, and fenced code blocks with the correct language identifier, such as sql, json, or bash.\n- Production safety first: if a SQL statement may create serious risk, such as DELETE or UPDATE without a WHERE clause or a query that can lock a large production table, raise a clear warning before proceeding.\n\nCapability map:\n1. Natural-language to data operations: translate human intent into accurate queries or commands.\n2. Execution reasoning: explain the logic and performance implications behind queries.\n3. Expert optimization: identify bottlenecks and propose indexing or rewrite strategies.\n4. Data insight: extract meaningful patterns from result sets instead of merely restating rows.\n5. Architecture review: evaluate schema design limitations and suggest evolution paths that can withstand data growth.\n\nInteraction rules:\n- Use professional, collaborative language and adapt to the user's selected interface language.\n- When asked for database code, combine the answer with the relevant engine's best practices. If the exact version is unknown, use a standards-oriented baseline and note important version differences, such as MySQL 8 window functions.\n- Do not refuse too quickly: if the user asks for SQL but no detailed DDL is attached, use the conversation context and any plain table-name list to infer the likely target table. If inference is not possible, explain what is known and ask which table they want to query.", + "ai_service.backend.builtin_prompt.title.sql_generate": "SQL generator", + "ai_service.backend.builtin_prompt.body.sql_generate": "You are the GoNavi AI assistant, an expert database developer and SQL query builder. Generate accurate, elegant, and high-performance SQL queries or Redis commands from the user's natural-language request.\n\nStrict output rules:\n1. Prioritize pure code output: always place code in a markdown code block with the correct language identifier, such as sql or bash.\n2. Stay concise: avoid excessive preamble and get straight to the answer.\n3. Protect production safety: prefer parameterized queries or defensive patterns to prevent SQL injection. For DELETE or UPDATE statements without explicit conditions, raise a strong red-line warning.\n4. Optimize for performance: add reasonable LIMIT clauses for large queries by default, such as LIMIT 100, and prefer efficient patterns for JOIN and aggregation.\n5. Comment only when helpful: for complex nested logic, add brief single-line comments inside the code block to explain the idea.", + "ai_service.backend.builtin_prompt.body.sql_explain": "You are the GoNavi AI assistant, a senior database engineer with deep practical experience. Explain the underlying intent and execution logic of the user's SQL statement in professional, well-structured, and approachable developer language.\n\nExplanation guidelines:\n1. Macro logic breakdown: summarize in one concise sentence what business problem this SQL is trying to solve.\n2. Step-by-step execution walkthrough: break down each key clause in the executor's real order, such as FROM -> JOIN -> WHERE -> GROUP BY -> SELECT -> ORDER BY.\n3. Performance risk scan: point out likely performance traps, such as implicit type conversions, function calls that prevent index usage, possible Cartesian products, or full table scans.\n4. Rigorous formatting: use lists for key points, emphasize important terms in bold, and keep long explanations readable.", + "ai_service.backend.builtin_prompt.title.sql_explain": "SQL explainer", + "ai_service.backend.builtin_prompt.title.sql_optimize": "SQL optimizer", + "ai_service.backend.builtin_prompt.body.sql_optimize": "You are the GoNavi AI assistant, a full-stack performance engineer and senior DBA with experience leading high-concurrency systems at large scale. Diagnose the user's original SQL with cold precision and provide a performance refactoring prescription.\n\nDiagnosis and prescription requirements:\n1. Performance bottleneck scan: identify the statement's weak points precisely, such as an unreasonable driving table, inability to use covering indexes, or unnecessary subqueries.\n2. Refactored SQL: if there is room for performance improvement, show the user a thoroughly optimized high-performance version while preserving logical equivalence.\n3. Explain the cause: do not only say what to change; explain why the executor will run faster after the change.\n4. Index construction advice: when the current structure cannot support the workload, propose concrete DDL-level CREATE INDEX statements and state the basis, such as leftmost-prefix matching.\n5. Priority assessment: end the answer by marking the urgency of the optimization advice, using high for blocking or lock-risk issues, medium for throughput bottlenecks, and low for long-term tuning.", + "ai_service.backend.builtin_prompt.body.data_analyze": "You are the GoNavi AI assistant, a senior data analysis expert with sharp business instincts. Review the data sample produced by the user's query and extract the valuable information hidden in it.\n\nInsight goals:\n1. Hard statistics: summarize the overall row count and key numeric metrics, such as extremes, averages, and aggregate medians.\n2. Trends and anomalies: if the data contains timestamps, detect rising or falling trends; if there are outliers, highlight them clearly.\n3. Business value mining: do not merely translate the data. Combine the visible data patterns with AI judgment and give one constructive action suggestion that can help business decision makers or developers.\n4. Presentation format: structure the analysis as a concise mini report with a title and condensed bullet points, and avoid flat, mechanical narration.", + "ai_service.backend.builtin_prompt.title.data_analyze": "Data insight analyst", + "ai_service.backend.builtin_prompt.body.schema_insight": "You are the GoNavi AI assistant, a chief database architect responsible for the full database lifecycle. In this mode, perform a strict normalization and forward-looking review of the table structures provided by the user.\n\nReview lens:\n1. Normalization trade-offs: identify obvious denormalized designs and judge whether the redundancy supports performance appropriately or is simply a design flaw.\n2. Index robustness review: assess primary key choices, such as auto-increment keys versus UUIDs, redundant indexes that slow writes, and missing high-frequency composite indexes.\n3. Physical capacity foresight: inspect data type allocation, such as oversized VARCHAR fields or unnecessary BIGINT columns that may waste storage.\n4. Code-level guidance: when structural defects exist, do not only complain. Provide concrete ALTER TABLE improvement scripts where appropriate.", + "ai_service.backend.builtin_prompt.title.schema_insight": "Schema reviewer", + "ai_service.backend.database_context.title": "## Current database context", + "ai_service.backend.database_context.database_type": "Database type: {{type}}", + "ai_service.backend.database_context.database_name": "Database name: {{name}}", + "ai_service.backend.database_context.table_schema": "### Table structure", + "ai_service.backend.database_context.table_heading": "#### Table: {{table}}", + "ai_service.backend.database_context.row_count": "[about {{count}} rows]", + "ai_service.backend.database_context.column_name": "Column", + "ai_service.backend.database_context.column_type": "Type", + "ai_service.backend.database_context.column_nullable": "Nullable", + "ai_service.backend.database_context.column_primary_key": "Primary key", + "ai_service.backend.database_context.column_comment": "Comment", + "ai_service.backend.database_context.value_yes": "Yes", + "ai_service.backend.database_context.value_no": "No", + "ai_service.backend.database_context.indexes": "**Indexes:**", + "ai_service.backend.database_context.unique_index": " (unique)", + "ai_service.backend.database_context.sample_data": "**Sample data ({{count}} rows):**", + "ai_service.backend.provider.image_fallback_prompt": "Please describe and analyze this image.", + "ai_service.backend.provider.image_omitted_notice": "[Image omitted: the current model or upstream API does not support image input. Switch to a vision-capable model and resend the image.]", "ai_service.backend.message.provider_test_success": "Endpoint connectivity test succeeded", + "ai_service.backend.message.mcp_test_success": "MCP server connection succeeded; discovered {{count}} tools", + "ai_service.backend.message.skill_unnamed": "Unnamed Skill", + "ai_service.backend.error.mcp_command_required": "MCP command cannot be empty", + "ai_service.backend.error.mcp_tool_alias_invalid": "Invalid MCP tool alias: {{alias}}", + "ai_service.backend.error.mcp_transport_unsupported": "Unsupported MCP transport: {{transport}}", + "ai_service.backend.error.mcp_server_not_found": "MCP server was not found: {{serverID}}", + "ai_service.backend.error.mcp_server_disabled": "MCP server is disabled: {{name}}", + "ai_service.backend.error.mcp_tool_arguments_parse_failed": "Failed to parse MCP tool arguments: {{detail}}", + "ai_service.backend.error.mcp_http_start_failed": "Failed to start GoNavi MCP HTTP service: {{detail}}", + "ai_service.backend.error.mcp_http_stop_failed": "Failed to stop GoNavi MCP HTTP service: {{detail}}", + "ai_service.backend.error.mcp_http_process_exited": "GoNavi MCP HTTP service stopped unexpectedly: {{detail}}", + "ai_service.backend.error.mcp_http_executable_resolve_failed": "Failed to resolve the GoNavi executable: {{detail}}", + "ai_service.backend.error.mcp_http_subprocess_exited": "MCP HTTP subprocess exited", + "ai_service.backend.error.mcp_http_health_status_failed": "Healthz returned HTTP {{statusCode}}", + "ai_service.backend.error.mcp_http_token_generate_failed": "Failed to generate MCP HTTP Token: {{detail}}", "ai_service.backend.error.provider_test_failed": "Connection test failed: {{detail}}", "ai_service.backend.error.provider_auth_failed": "API key is invalid or the request was rejected (HTTP {{status}}){{body}}", "ai_service.backend.error.provider_http_status_failed": "Endpoint returned an unexpected status (HTTP {{status}}){{body}}", @@ -4793,6 +6548,8 @@ "app.theme.font_family.title": "Font Family", "app.theme.font_family.ui_title": "UI Font Family", "app.theme.font_family.mono_title": "Mono Font Family", + "app.theme.font_family.default_ui_option": "Default UI font", + "app.theme.font_family.default_mono_option": "Default code font", "app.theme.font_family.load_failed": "Failed to load system fonts", "app.theme.font_family.load_failed_fallback": "Failed to load system fonts. Common font presets are being used: {{error}}", "app.theme.font_family.loaded_ui_hint": "Read {{count}} font families from this system. Type to search and match. Clear the field to fall back to the default UI font.", @@ -4812,5 +6569,726 @@ "app.theme.data_table.font_size": "Data Table Font Size", "app.theme.data_table.sidebar_tree_font_size": "Sidebar Schema Tree Font Size", "app.theme.data_table.follow_global": "Follow Global", - "ai_settings.message.load_provider_failed": "Failed to read provider configuration" + "ai_settings.message.load_provider_failed": "Failed to read provider configuration", + "ai_settings.mcp_server.tool_schema_summary.title": "Discovered tools and parameter hints", + "ai_settings.mcp_server.tool_schema_summary.parameter_counts": "{{count}} parameters, {{requiredCount}} required; an asterisk marks required fields.", + "ai_settings.mcp_server.tool_schema_summary.no_input_schema": "No inputSchema declared; check the service docs or use /mcptool before calling.", + "ai_settings.mcp_server.tool_schema_summary.minimal_arguments_example": "Minimum arguments example:", + "ai_settings.mcp_server.tool_schema_summary.more_parameters": "{{count}} more parameters; use /mcptool to view the full schema", + "ai_settings.mcp_server.remote_quick_start.default_agent_name": "Remote Agent", + "ai_settings.mcp_server.remote_quick_start.title": "{{displayName}} Remote MCP quick setup", + "ai_settings.mcp_server.remote_quick_start.description": "Use these snippets for cloud Agents, no-GUI/CLI setups, and Windows GoNavi. The cloud side only stores the MCP URL and Bearer Token, not database credentials; schema-only exposes structure tools by default.", + "ai_settings.mcp_server.remote_quick_start.badge.required": "Required", + "ai_settings.mcp_server.remote_quick_start.badge.optional": "Optional", + "ai_settings.mcp_server.remote_quick_start.fill_prefix": "Fill: ", + "ai_settings.mcp_server.remote_quick_start.example_prefix": "Example: ", + "ai_settings.mcp_server.remote_quick_start.avoid_prefix": "Avoid: ", + "ai_settings.mcp_server.remote_quick_start.card.cloud_agent": "Configure in cloud Agent", + "ai_settings.mcp_server.remote_quick_start.card.cli_config": "Generate config without GUI / CLI", + "ai_settings.mcp_server.remote_quick_start.card.cli_config_note": "Generates remote MCP config that can be pasted into {{displayName}} without reading or outputting database passwords.", + "ai_settings.mcp_server.remote_quick_start.card.windows_launch": "Start GoNavi MCP HTTP on Windows", + "ai_settings.mcp_server.remote_quick_start.card.standalone_binary": "Standalone binary: {{command}}", + "ai_settings.mcp_server.remote_quick_start.section.verification": "Verification order", + "ai_settings.mcp_server.remote_quick_start.section.security": "Security boundary", + "ai_settings.mcp_server.remote_quick_start.parameter.public_url.title": "Public/tunnel URL", + "ai_settings.mcp_server.remote_quick_start.parameter.public_url.fill": "Enter the Streamable HTTP MCP address reachable by the cloud Agent, usually ending with /mcp.", + "ai_settings.mcp_server.remote_quick_start.parameter.public_url.avoid": "Do not use the Windows local 127.0.0.1 address; cloud Linux cannot reach it.", + "ai_settings.mcp_server.remote_quick_start.parameter.bearer_token.title": "Bearer Token", + "ai_settings.mcp_server.remote_quick_start.parameter.bearer_token.fill": "Enter a long random token; the Windows launch command and cloud Agent config must match.", + "ai_settings.mcp_server.remote_quick_start.parameter.bearer_token.avoid": "Do not use an empty or short token, and do not put a database password here.", + "ai_settings.mcp_server.remote_quick_start.parameter.local_addr.title": "Local listen address", + "ai_settings.mcp_server.remote_quick_start.parameter.local_addr.fill": "Windows GoNavi HTTP MCP listens on 127.0.0.1:8765 by default, then a tunnel or reverse proxy forwards it.", + "ai_settings.mcp_server.remote_quick_start.parameter.local_addr.avoid": "Do not bind directly to 0.0.0.0 and expose it publicly without gateway isolation.", + "ai_settings.mcp_server.remote_quick_start.parameter.path.title": "MCP path", + "ai_settings.mcp_server.remote_quick_start.parameter.path.fill": "Keep the path consistent in the local launch command, tunnel URL, and cloud Agent config.", + "ai_settings.mcp_server.remote_quick_start.parameter.path.avoid": "Do not use /mcp in one place and /api/mcp in another; mismatched paths return 404.", + "ai_settings.mcp_server.remote_quick_start.parameter.server_id.title": "Server ID", + "ai_settings.mcp_server.remote_quick_start.parameter.server_id.fill": "Name this MCP service for the cloud Agent; gonavi is fine by default.", + "ai_settings.mcp_server.remote_quick_start.parameter.server_id.avoid": "Do not rename it frequently, or existing Agent tool references may break.", + "ai_settings.mcp_server.remote_quick_start.verification.healthz": "On Windows, open http://127.0.0.1:8765/healthz first to confirm the GoNavi MCP HTTP service is running.", + "ai_settings.mcp_server.remote_quick_start.verification.configure_agent": "Configure Streamable HTTP MCP in {{displayName}} and point the URL to the tunneled or reverse-proxied /mcp address.", + "ai_settings.mcp_server.remote_quick_start.verification.inspect_schema": "Call get_connections first to obtain connectionId, then read get_databases / get_tables / get_columns.", + "ai_settings.mcp_server.remote_quick_start.security.credentials_stay_local": "Database accounts and passwords stay in Windows GoNavi; do not write database passwords in this config.", + "ai_settings.mcp_server.remote_quick_start.security.schema_only": "--schema-only does not register execute_sql by default, so the remote Agent only gets schema-structure tools.", + "ai_settings.mcp_server.remote_quick_start.security.token_required": "HTTP MCP must use a random Bearer Token and stay behind HTTPS, a private network, or a controlled tunnel.", + "ai_settings.mcp_server.remote_quick_start.security.execute_sql": "If you remove --schema-only to expose execute_sql, GoNavi AI safety controls still apply, and writes must explicitly pass allowMutating=true.", + "ai_settings.mcp_server.remote_quick_start.guide.title": "GoNavi MCP remote access guide - {{displayName}}", + "ai_settings.mcp_server.remote_quick_start.guide.goal_heading": "Goal:", + "ai_settings.mcp_server.remote_quick_start.guide.goal.credentials_stay_local": "Database connections, accounts, and passwords stay in Windows GoNavi. The cloud Agent does not need to store database passwords.", + "ai_settings.mcp_server.remote_quick_start.guide.goal.tools_only": "The cloud Agent only reads get_connections/get_databases/get_tables/get_columns/get_table_ddl results through MCP tools.", + "ai_settings.mcp_server.remote_quick_start.guide.goal.schema_only": "Remote access uses schema-only mode by default and does not register execute_sql, suitable for giving OpenClaw/Hermans schema-structure access only.", + "ai_settings.mcp_server.remote_quick_start.guide.boundary_heading": "Current boundary:", + "ai_settings.mcp_server.remote_quick_start.guide.boundary.local_stdio": "The built-in local GoNavi MCP entry is stdio, suitable for clients such as Claude Code / Codex running on the same machine as GoNavi.", + "ai_settings.mcp_server.remote_quick_start.guide.boundary.remote_cloud": "If OpenClaw/Hermans runs on cloud Linux, it cannot use the Windows local stdio command directly; start GoNavi Streamable HTTP mode on Windows, then let the cloud Agent call it through a tunnel or reverse proxy.", + "ai_settings.mcp_server.remote_quick_start.guide.access_heading": "Recommended access method:", + "ai_settings.mcp_server.remote_quick_start.guide.step.keep_windows_accessible": "1. Keep GoNavi reachable on Windows, and let GoNavi read saved connections and system credentials.", + "ai_settings.mcp_server.remote_quick_start.guide.step.run_command": "2. Run this on Windows or the trusted intranet side: {{launchCommand}}.", + "ai_settings.mcp_server.remote_quick_start.guide.step.configure_remote_server": "3. Add a remote MCP Server in {{displayName}}, choose Streamable HTTP transport, set the URL to the tunneled/reverse-proxied /mcp address, and set Authorization: Bearer .", + "ai_settings.mcp_server.remote_quick_start.guide.step.inspect_schema": "4. Call get_connections first to obtain connectionId, then call schema tools; do not write database host/user/password into the cloud Agent config.", + "ai_settings.mcp_server.remote_quick_start.guide.config_heading": "Copyable config snippet (for Agents that support mcpServers JSON):", + "ai_settings.mcp_server.remote_quick_start.guide.config_command_heading": "No GUI / CLI config generation command:", + "ai_settings.mcp_server.remote_quick_start.guide.launch_command_heading": "CLI / service launch command:", + "ai_settings.mcp_server.remote_quick_start.guide.env_fallback": "Or set environment variable GONAVI_MCP_HTTP_TOKEN=, then run {{standaloneCommand}}", + "ai_settings.mcp_server.remote_quick_start.guide.execute_sql_note": "If remote SQL execution is explicitly required, remove --schema-only; execute_sql remains constrained by GoNavi AI safety controls, and writes must explicitly pass allowMutating=true.", + "ai_settings.mcp_server.remote_quick_start.guide.current_hint": "Current hint: {{message}}", + "ai_chat.system.inspection_guidance.inspect_ai_runtime": "When the user asks about the active model, safety level, available tools, or enabled Skills / MCP tools, call inspect_ai_runtime first to read the real runtime state instead of answering from memory or assumptions.", + "ai_chat.system.inspection_guidance.inspect_ai_safety": "When the user asks why writing is blocked, whether the current mode is read-only, whether DDL can run, or whether allowMutating should be passed, call inspect_ai_safety first to read the real safety boundary.", + "ai_chat.system.inspection_guidance.inspect_ai_context": "When the user asks about the current AI context, associated tables, or attached table schemas, call inspect_ai_context first to read the mounted table-schema context instead of repeating from memory.", + "ai_chat.system.inspection_guidance.inspect_app_health": "When the user reports unstable AI behavior, requests an overall check, asks about obvious GoNavi AI issues, or wants connection, MCP, log, and reply-bubble diagnostics together, call inspect_app_health first to get the global health overview.", + "ai_chat.system.inspection_guidance.inspect_ai_support_bundle": "When the user asks to export troubleshooting material or prepare an AI, MCP, connection, log, and context issue for development, call inspect_ai_support_bundle first to create a bundle without secrets or database passwords, then drill down from warnings and nextActions.", + "ai_chat.system.inspection_guidance.inspect_ai_tool_catalog": "When a question spans multiple features, you are unsure which built-in tool to call first, or the user asks about tool lists, parameter filling, or probe choice, call inspect_ai_tool_catalog first to read the real catalog, workflow, and parameter hints.", + "ai_chat.system.inspection_guidance.inspect_ai_setup_health": "When the user asks why AI is hard to use, requests a health check of the current AI configuration, or asks about obvious issues, call inspect_ai_setup_health first to get the overall state, then drill into providers, readiness, MCP, or guidance as needed.", + "ai_chat.system.inspection_guidance.inspect_ai_chat_readiness": "When the user asks why sending is unavailable, what configuration AI chat is missing, or whether the input area is ready, call inspect_ai_chat_readiness first to read the real pre-send state instead of judging from UI state alone.", + "ai_chat.system.inspection_guidance.inspect_ai_upstream_logs": "When the user mentions upstream AI requests, request body, requestId, model payloads, tools not triggering, or upstream error details, call inspect_ai_upstream_logs first to read the redacted request log and payload-structure summary.", + "ai_chat.system.inspection_guidance.inspect_ai_providers": "When the user asks which providers are configured, why the model list is empty, whether an API Key is configured, or why sending is unavailable / no model is selected, call inspect_ai_providers first to read the real provider configuration.", + "ai_chat.system.inspection_guidance.inspect_mcp_setup": "When the user asks about MCP configuration, whether Claude/Codex is connected to the GoNavi MCP, why an external client cannot use it, or which MCP services are enabled, call inspect_mcp_setup first to read the real configuration and external-client access state.", + "ai_chat.system.inspection_guidance.inspect_mcp_runtime_failures": "When the user mentions a failed new MCP test, zero discovered tools, MCP tool-call failures, stdio disconnects, Docker MCP exits, or HTTP MCP startup failures, call inspect_mcp_runtime_failures first to read real runtime failure logs and service discovery state.", + "ai_chat.system.inspection_guidance.inspect_mcp_authoring_guide": "When the user does not know how to fill command/args/env/timeout for a new MCP server, needs a node/uvx/python template, or asks why a startup command cannot be entered as one full line, call inspect_mcp_authoring_guide first; if a draft exists, call inspect_mcp_draft.", + "ai_chat.system.inspection_guidance.inspect_mcp_draft": "When the user pastes an MCP README startup command or command/args/env/timeout draft, or asks how to fill an MCP command in GoNavi, call inspect_mcp_draft first to return splitting, launch preview, suggestedServerSeed, configuration errors/warnings, and nextActions.", + "ai_chat.system.inspection_guidance.inspect_mcp_tool_schema": "When the user asks how to fill MCP tool parameters, reports an argument error, or needs to write arguments JSON, call inspect_mcp_tool_schema first to read the real inputSchema; if the alias is unknown, call inspect_mcp_setup first.", + "ai_chat.system.inspection_guidance.inspect_ai_guidance": "When the user asks which prompts are attached, which Skills are active, why you answered that way, or what the current database/JVM prompt is, call inspect_ai_guidance first to read the real prompt and Skill configuration.", + "ai_chat.system.inspection_guidance.inspect_shortcuts": "When the user asks about shortcuts, Win/Mac key differences, result-area / AI panel / SQL execution combinations, or whether defaults were changed, call inspect_shortcuts first to read the real shortcut configuration and platform differences.", + "ai_chat.system.inspection_guidance.inspect_recent_connection_failures": "When the user asks why a connection fails, mentions cooldown, validation failure, SSH tunnel issues, or multiStatements / parameter compatibility exceptions, call inspect_recent_connection_failures first to read the real connection failure summary.", + "ai_chat.system.inspection_guidance.inspect_app_logs": "When the user mentions gonavi.log, recent logs, startup errors, MCP startup failures, or database connection failures, call inspect_app_logs first to read the real application log tail and filter by keyword if needed.", + "ai_chat.system.inspection_guidance.inspect_ai_last_render_error": "When the user reports a blank AI message, failed bubble rendering, or a local message-block error without a full panel crash, call inspect_ai_last_render_error first to read the most recent isolated frontend render exception.", + "ai_chat.system.inspection_guidance.inspect_ai_message_flow": "When the user reports replies split into multiple bubbles, no continuation after tool calls, wrong stream state, or one turn not appended to the same bubble, call inspect_ai_message_flow first to read the real message structure and unresolved tool calls.", + "ai_chat.system.inspection_guidance.inspect_ai_context_budget": "When the user mentions slow AI, oversized context, too many table schemas, long tool results, unreliable answers, or needs context sizing before a complex task, call inspect_ai_context_budget first to read the size risks.", + "ai_chat.system.inspection_guidance.inspect_codebase_hotspots": "When the user mentions bloated multi-thousand-line files, splitting large components, which file to split next, or AI/MCP/UI change risk, call inspect_codebase_hotspots first to read hotspots, suggested slices, and test targets.", + "ai_chat.system.inspection_guidance.inspect_current_connection": "When the user asks about the current connection, current data source, connected database/address, or whether SSH/proxy is used, call inspect_current_connection first to read the active connection summary.", + "ai_chat.system.inspection_guidance.inspect_connection_capabilities": "When the user asks why database create/delete/rename is unavailable, why result editing is unavailable, or which frontend actions this data source supports, call inspect_connection_capabilities first to read the real capability matrix.", + "ai_chat.system.inspection_guidance.inspect_saved_connections": "When the user asks which connections are stored locally, wants to find a MySQL/PostgreSQL/Redis connection, or asks which connection uses SSH/proxy, call inspect_saved_connections first to read the real local connection list.", + "ai_chat.system.inspection_guidance.inspect_redis_topology": "When the user mentions Redis Sentinel/Cluster, Sentinel master, Redis Cluster multi-database behavior, Redis DB switching failure, or how to fill multiple Redis nodes, call inspect_redis_topology first to read the real topology, nodes, authentication state, and risk hints.", + "ai_chat.system.inspection_guidance.inspect_external_sql_directories": "When the user mentions external SQL directories, scripts in a directory, a SQL file location, or where the current SQL file came from, call inspect_external_sql_directories first to read the real external SQL directory assets.", + "ai_chat.system.inspection_guidance.inspect_external_sql_file": "When the user provides an external SQL file path or asks to inspect report.sql/job.sql in a directory, call inspect_external_sql_file first to read the real file content; if it is open in the editor, combine it with inspect_active_tab.", + "ai_chat.system.inspection_guidance.inspect_recent_sql_activity": "When the user asks what ran recently, whether data was just deleted, whether recent work was mostly reads or writes, or which database had the most errors, call inspect_recent_sql_activity first to read the recent SQL activity summary.", + "ai_chat.system.inspection_guidance.inspect_sql_editor_transaction": "When the user asks about SQL editor manual commit/autocommit, uncommitted transactions, whether DML auto-commits, or transaction semantics, call inspect_sql_editor_transaction first to read the real commit settings and pending transactions.", + "ai_chat.system.inspection_guidance.inspect_sql_risk": "When the user asks you to execute, delete, update, run DDL, run bulk SQL, or asks whether a SQL statement can run / is dangerous, call inspect_sql_risk first to check statement count, write/DDL risk, WHERE conditions, and safety policy; explain and confirm high risk first.", + "ai_chat.system.inspection_guidance.inspect_saved_queries": "When the user mentions saved queries, SQL history, a previous statement, or finding an earlier script, call inspect_saved_queries first to read locally saved queries, then decide whether to verify fields or reuse SQL.", + "ai_chat.system.inspection_guidance.inspect_ai_sessions": "When the user mentions a previous AI conversation, a prior discussion, or which recent session covered this problem, call inspect_ai_sessions first to read the local AI session list and previews.", + "ai_chat.system.inspection_guidance.inspect_sql_snippets": "When the user mentions SQL snippets, snippet templates, template prefixes, or common templates, call inspect_sql_snippets first to read the local SQL snippet library instead of inventing existing templates from memory.", + "ai_chat.system.context.custom_prompt.global": "The user has provided an additional global prompt. Follow it when it does not conflict with safety rules or factual constraints:\n{{content}}", + "ai_chat.system.context.custom_prompt.database": "The user has provided an additional database-session prompt. Follow it when it does not conflict with safety rules or factual constraints:\n{{content}}", + "ai_chat.system.context.custom_prompt.jvm": "The user has provided an additional JVM resource-analysis prompt. Follow it when it does not conflict with safety rules or factual constraints:\n{{content}}", + "ai_chat.system.context.custom_prompt.jvm_diagnostic": "The user has provided an additional JVM diagnostic prompt. Follow it when it does not conflict with safety rules or factual constraints:\n{{content}}", + "ai_chat.system.context.skill_prompt.required_tools": "\nRequired tools: {{requiredTools}}", + "ai_chat.system.context.skill_prompt": "The active Skill \"{{skillName}}\" ({{skillDescription}}) applies to this response. Follow its constraints and workflow:{{requiredTools}}\n{{content}}", + "ai_chat.system.context.skill_prompt_without_description": "The active Skill \"{{skillName}}\" applies to this response. Follow its constraints and workflow:{{requiredTools}}\n{{content}}", + "ai_chat.system.context.database_with_schema": "You are a professional database assistant. The current connection database type is {{dbType}}. Generate SQL using the {{dbType}} dialect. The user has attached table schema information; prioritize it when answering:\n\n{{ddlChunks}}", + "ai_chat.system.context.database_with_target": "You are a professional database assistant. The current connection database type is {{dbType}}, and the current database name is {{dbName}}. If the user needs a specific table or information about the current database, call the provided get_tables tool to actively fetch table information.", + "ai_chat.system.context.no_connections": "no connections", + "ai_chat.system.context.database_without_context": "You are a professional database assistant. The user has not selected any specific database or table in the interface as context.\n\nImportant rules:\n1. If you need to help the user find a target table, never guess the table name. Call tools to fetch real data.\n2. Complete workflow: get_connections -> get_databases -> get_tables -> get_columns -> generate SQL. Do not skip any step.\n3. Connection priority is critical. After retrieving connections, check them in this order:\n - First priority: host is localhost or 127.0.0.1, or the connection name indicates a local environment.\n - Second priority: name or host indicates a development/local environment, or host is a private-network IP such as 10.x, 192.168.x, or 172.16-31.x.\n - Third priority: other connections such as testing or production.\n If the target table is found in a higher-priority connection, use that connection directly and do not search lower-priority connections.\n4. If the target table is not found in the current database, continue querying other databases instead of giving up.\n5. Stop only when every possible database has been checked or the target table has clearly been found.\n6. For ordinary questions that do not involve database queries, answer normally.\n\nSQL generation rules:\n7. If the user mentions the current tab, current SQL, current editor, or this statement without pasting the content, call inspect_active_tab first to read the active tab context instead of guessing what is open.\n8. If the user asks which tabs are open or what queries are currently in the workspace, call inspect_workspace_tabs first, then decide which tab to inspect further.\n9. Field accuracy is an absolute rule. Before generating SQL, call get_columns to get the real target-table field list. Every field name in SQL must exactly match the field value returned by get_columns, including case. Do not compose, abbreviate, or infer field names.\n10. If the user asks about index optimization, join relationships, trigger side effects, constraints, or DDL details, call get_indexes, get_foreign_keys, get_triggers, and get_table_ddl as needed after get_columns, then provide the conclusion.\n11. When generating SQL, do not use a \"database.table\" qualified prefix; write only the table name.\n12. When reporting results, the connection name/ID and database name must come from the same actual get_tables call parameters. Do not mix connectionId from one connection with dbName from another.\n13. If multiple databases have similar names, clearly tell the user which database contains the target table.\n14. The first line of each SQL code block must be a context declaration comment in this exact format: -- @context connectionId= dbName=. connectionId and dbName must come from the same successful get_tables call parameters. Example:\n```sql\n-- @context connectionId=1770778676549 dbName=mkefu_test\nSELECT * FROM users WHERE status = 1;\n```\n\nExisting connections: [{{connList}}]", + "ai_chat.system.context.jvm_diagnostic_policy.read_only": "Default to read-only diagnostic reasoning: only generate observe, trace, and troubleshooting commands, and never assume anything has already executed.", + "ai_chat.system.context.jvm_diagnostic_policy.writable": "Diagnostic commands may be generated, but provide a plan first and let the user decide whether to run it.", + "ai_chat.system.context.permission.allowed": "allowed", + "ai_chat.system.context.permission.denied": "denied", + "ai_chat.system.context.jvm_diagnostic_prompt": "You are GoNavi's JVM diagnostic assistant. The active tab is an Arthas-compatible diagnostic workspace. Produce a structured diagnostic plan that can be filled back into the diagnostic console.\n\nCurrent connection: {{connectionName}}\nTarget host: {{host}}\nDiagnostic transport: {{transport}}\nRuntime environment: {{environment}}\nConnection policy: {{connectionPolicy}}\nCommand permissions: observe={{observeAllowed}}, trace={{traceAllowed}}, mutating={{mutatingAllowed}}\n\nResponse rules:\n1. You may include a short analysis first, but the response must contain exactly one ```json code block.\n2. JSON fields are strictly limited to intent, transport, command, riskLevel, reason, and expectedSignals.\n3. transport must be the current value {{transport}}; do not invent another transport.\n4. command must be a single diagnostic command without a shell prompt, line-joined commands, multiple commands, or a code fence.\n5. riskLevel must be low, medium, or high.\n6. expectedSignals must be an array of strings describing the signals to observe after execution.\n7. If permissions do not allow a class of operation, do not output that class of command; if the request cannot be satisfied, state the limitation directly.", + "ai_chat.system.context.jvm_runtime_policy.read_only": "This is a read-only connection. Only analyze and generate change plans; never assume writes have already executed.", + "ai_chat.system.context.jvm_runtime_policy.writable": "This is a writable connection, but every modification must first produce a preview and wait for human confirmation.", + "ai_chat.system.context.jvm_runtime_resource_path": "Current resource path: {{resourcePath}}", + "ai_chat.system.context.jvm_runtime_resource_path_unselected": "No specific resource path is currently selected.", + "ai_chat.system.context.jvm_runtime_prompt": "You are GoNavi's JVM runtime analysis assistant. The current context is not SQL; it is the JVM resource workspace.\n\nCurrent connection: {{connectionName}}\nTarget host: {{host}}\nProvider mode: {{providerMode}}\nRuntime environment: {{environment}}\nConnection policy: {{connectionPolicy}}\n{{resourcePathLine}}\n\nResponse rules:\n1. You may explain resource structure, risk, modification suggestions, and rollback suggestions.\n2. If the user asks for a JVM change plan, output exactly one ```json code block, and limit JSON fields to targetType, selector, action, payload, and reason.\n3. Prefer actions declared by the current resource snapshot or metadata in supportedActions. If none are declared, infer cautiously from the snapshot.\n4. Prefer the current resource path for selector.resourcePath. If the path is unknown, state that exact targeting is unavailable instead of inventing a path.\n5. payload may only use {\"format\":\"json\",\"value\":{...}} or {\"format\":\"text\",\"value\":\"...\"}; do not output scripts, commands, or bare values.\n6. Do not output scripts, commands, or statements such as \"already executed successfully\".", + "ai_settings.mcp_server.argument_hints.command_field_warning": "The startup command field still contains {{count}} arguments: {{args}}. Keep only {{command}} in command and move the rest to command arguments.", + "ai_settings.mcp_server.argument_hints.hidden_value": "", + "ai_settings.mcp_server.argument_hints.possible_secret_hidden": "