/** * i18n engine for pi-mcp-market. * * Supports zh (Chinese) and en (English) out of the box. * Extensible — add entries to the dictionary for more languages. * * Usage: * import { t, setLang } from "./i18n.ts"; * t("market.title") // → "MCP Server Market" (en) or "MCP 服务器市场" (zh) * setLang("zh"); // switch at runtime * t("common.enter") // → "确认" * * Dictionary sections match file/feature domains for maintainability. */ export type Lang = "zh" | "en"; let currentLang: Lang = "zh"; export function getLang(): Lang { return currentLang; } export function setLang(lang: Lang): void { currentLang = lang; } /** Shorthand for t(). */ export const _ = t; /** Look up a key in the current language dictionary. Falls back to zh, then the key itself. */ export function t(key: string, fallback?: string): string { const dict = DICTIONARY[key]; if (!dict) return fallback ?? key; return (dict as Record)[currentLang] ?? (dict as Record).zh ?? key; } /** All keys in the current language (for panel building). */ export function lang(): Lang { return currentLang; } type DictValue = string | Record; const DICTIONARY: Record = { // ── Market Panel ──────────────────────────────────────────────── "market.title": { zh: "MCP 服务器市场", en: "MCP Server Market" }, "market.adapterMissing": { zh: "⚠ pi-mcp-adapter 未安装", en: "⚠ pi-mcp-adapter not installed" }, "market.searchLabel": { zh: "搜索: ", en: "Search: " }, "market.searchPlaceholder": { zh: "输入以模糊搜索…", en: "Type to fuzzy search…" }, "market.searchHint": { zh: " Esc 清空 ", en: " Esc clear " }, "market.sourceAll": { zh: "全部", en: "All" }, "market.sourceSwitch": { zh: "←/→ 切换", en: "←/→ switch" }, "market.loading": { zh: "正在加载 MCP 市场源…", en: "Loading MCP market sources…" }, "market.noRegistries": { zh: "未配置 remote registry,请编辑 registries.json", en: "No registries configured, edit registries.json" }, "market.allFailed": { zh: "所有源拉取失败,且无可用缓存", en: "All sources failed and no cached data" }, "market.noServers": { zh: "源已配置,但没有可安装的服务器", en: "Sources configured but no installable servers" }, "market.noMatch": { zh: "没有匹配的服务器", en: "No servers match" }, "market.helpUpDown": { zh: "↑/↓ 移动", en: "↑/↓ navigate" }, "market.helpEnter": { zh: "Enter 预览/安装", en: "Enter preview/install" }, "market.helpSource": { zh: "←/→ 切源", en: "←/→ source" }, "market.helpClose": { zh: "Esc 关闭", en: "Esc close" }, "market.installed": { zh: "● 已安装", en: "● Installed" }, "market.ready": { zh: "✅ 已就绪", en: "✅ Ready" }, "market.pendingConfig": { zh: "⚠ 待配置", en: "⚠ Needs config" }, "market.installedTo": { zh: "已安装到 mcp.json", en: "Installed to mcp.json" }, "market.installedToPending": { zh: "已安装到 mcp.json,但有配置未完成", en: "Installed to mcp.json, config pending" }, "market.needsEnv": { zh: "需设置", en: "Needs" }, "market.badgeInstalled": { zh: "● 已安装", en: "● Installed" }, "market.badgeConfig": { zh: "⚠ 待配置", en: "⚠ Needs config" }, "market.toolCount": { zh: "🧰 {n} tools", en: "🧰 {n} tools" }, "market.previewBeforeInstall": { zh: "安装前 Enter 预览配置", en: "Press Enter to preview config" }, // ── Source filter tabs ────────────────────────────────────────── "market.sourceInstalled": { zh: "已安装", en: "Installed" }, "market.loadingSpinner": { zh: " ⏳", en: " ⏳" }, "market.failedIcon": { zh: " ⚠", en: " ⚠" }, // ── Connection failures ──────────────────────────────────────── "market.failedSources": { zh: "⚠ 无法连接: {sources}(已用缓存/跳过)", en: "⚠ Unreachable: {sources} (fallback to cache)" }, // ── Form panel ───────────────────────────────────────────────── "form.title": { zh: "填写必填项: {name}", en: "Fill required fields: {name}" }, "form.hint": { zh: "值将明文保存在 mcp.json(MCP 标准)。Esc 跳过 → 标记「待配置」。", en: "Values stored in plaintext (MCP standard). Esc to skip → marked as pending." }, "form.inputPlaceholder": { zh: "输入值…", en: "Enter value…" }, "form.skipped": { zh: "(跳过)", en: "(skipped)" }, "form.defaultHint": { zh: " 必填环境变量,请在 {key} 对应服务的官网申请", en: " Required env, obtain from the service provider" }, "form.helpNext": { zh: "Enter 下一步/完成", en: "Enter next/finish" }, "form.helpSkip": { zh: "Esc 跳过", en: "Esc skip" }, "form.helpCancel": { zh: "Ctrl+C 取消", en: "Ctrl+C cancel" }, // ── Preview panel ────────────────────────────────────────────── "preview.title": { zh: "安装确认", en: "Install Confirmation" }, "preview.auth": { zh: "认证: {type}", en: "Auth: {type}" }, "preview.tools": { zh: "工具: {n} 个", en: "Tools: {n}" }, "preview.willWrite": { zh: "将写入 ~/.pi/agent/mcp.json:", en: "Will write to ~/.pi/agent/mcp.json:" }, "preview.confirmInstall": { zh: "Enter 确认安装", en: "Enter to install" }, "preview.cancelInstall": { zh: "Esc 取消", en: "Esc cancel" }, "preview.hintReload": { zh: "安装后:运行 /mcp reconnect 或 /reload 加载新服务器", en: "After install: run /mcp reconnect or /reload" }, "preview.hintOauth": { zh: "认证方式:OAuth — 安装后运行 /mcp-auth <服务器名> 完成授权", en: "Auth: OAuth — run /mcp-auth after install" }, "preview.hintBearer": { zh: "需要设置环境变量 ${var}(安装后运行 /reload 生效)", en: "Set env ${var} (run /reload after install)" }, "preview.hintWhereToGet": { zh: "获取地址:{url}", en: "Get from: {url}" }, "preview.hintOnekey": { zh: "OneKey 代理:需要设置 $DEEPNLP_ONEKEY_ROUTER_ACCESS", en: "OneKey proxy: need $DEEPNLP_ONEKEY_ROUTER_ACCESS" }, "preview.hintOnekeyUrl": { zh: "获取密钥:https://www.deepnlp.org/workspace/keys", en: "Get key: https://www.deepnlp.org/workspace/keys" }, "preview.hintOnekeyCredit": { zh: "注意:DeepNLP MCP 使用 credits 计费(有免费试用额度,Pro $9.9/月),并非全免费服务", en: "Note: DeepNLP MCP uses credits (free trial, Pro $9.9/month)" }, "preview.hintNpx": { zh: "npx 服务器:如果连接失败,在终端手动运行此命令查看具体错误", en: "npx server: if connection fails, run the command manually to see the error" }, "preview.hintUrl": { zh: "远程服务器:如果连接失败,检查是否需要 API Key 或正确的 endpoint URL", en: "Remote server: if connection fails, check for API Key or correct endpoint URL" }, // ── Auth labels ──────────────────────────────────────────────── "auth.none": { zh: "✅ 无需认证", en: "✅ No auth required" }, "auth.apiKey": { zh: "🔑 需要 API Key", en: "🔑 API Key needed" }, "auth.bearer": { zh: "🔑 需要 Bearer Token", en: "🔑 Bearer Token needed" }, "auth.oauth": { zh: "🔐 需要 OAuth", en: "🔐 OAuth needed" }, "auth.unknown": { zh: "❓ 认证类型未知", en: "❓ Unknown auth type" }, // ── Config errors ────────────────────────────────────────────── "config.mcpCorrupted": { zh: "mcp.json 配置损坏,已备份到 {backup}。请检查后决定是否修复,市场不会覆盖它。", en: "mcp.json corrupted, backed up to {backup}. Check before fixing, the market won't overwrite it." }, "config.backupFailed": { zh: "无法备份损坏的配置文件:{msg}", en: "Cannot backup corrupted config: {msg}" }, // ── Install / uninstall messages ─────────────────────────────── "install.failed": { zh: "安装失败: {msg}", en: "Install failed: {msg}" }, "install.success": { zh: "安装成功", en: "Install succeeded" }, "uninstall.failed": { zh: "卸载失败: {msg}", en: "Uninstall failed: {msg}" }, "uninstall.success": { zh: "卸载成功", en: "Uninstall succeeded" }, // ── Adapter messages ─────────────────────────────────────────── "adapter.missing": { zh: "未检测到 pi-mcp-adapter。市场可以照常浏览/安装(写入 mcp.json),但连接服务器需要安装它:运行 `pi install npm:pi-mcp-adapter`。", en: "pi-mcp-adapter not detected. Market can browse/install (write to mcp.json) but needs it to connect servers: run `pi install npm:pi-mcp-adapter`." }, // ── Headless mode ────────────────────────────────────────────── "headless.available": { zh: "MCP 市场: {n} 个服务器可用 ({names})。交互面板需要 TUI 模式(/mcp-market)。", en: "MCP Market: {n} servers available ({names}). Interactive panel needs TUI mode (/mcp-market)." }, // ── Language command ─────────────────────────────────────────── "lang.current": { zh: "当前语言: {lang}", en: "Current language: {lang}" }, "lang.switchedTo": { zh: "已切换到 {lang}", en: "Switched to {lang}" }, "lang.usage": { zh: "用法: /mcp-market lang [zh|en] — 切换语言", en: "Usage: /mcp-market lang [zh|en] — switch language" }, "lang.unknown": { zh: "不支持的语言: {lang},可用: zh, en", en: "Unsupported language: {lang}, available: zh, en" }, "lang.invalid": { zh: "语言代码无效。用法: /mcp-market lang zh|en", en: "Invalid lang code. Usage: /mcp-market lang zh|en" }, "lang.displayName_zh": { zh: "中文", en: "Chinese" }, "lang.displayName_en": { zh: "英文", en: "English" }, }; type Interpolate = Record; /** t() with interpolation — replaces {key} placeholders. */ export function ti(key: string, values: Interpolate, fallback?: string): string { let s = t(key, fallback); for (const [k, v] of Object.entries(values)) { s = s.replace(new RegExp(`\\{${k}\\}`, "g"), String(v)); } return s; }