export type Locale = "zh" | "en"; export const I18N: Record> = { zh: { title: "pi-provider-trace", live: "实时", offline: "离线", traceOn: "抓包 开", traceOff: "抓包 关", sessions: "Pi 会话", exchanges: "HTTP 交换", newestFirst: "最新在上", autoFollow: "自动跟随最新", filterPlaceholder: "过滤 URL / id", pause: "暂停", resume: "继续", clear: "清空视图", tabStream: "流式 SSE", tabReqHeaders: "请求头", tabRequestBody: "请求体", tabResponse: "响应头", tabRaw: "原始行", tabTimeline: "时间线", timelinePi: "Pi 事件", timelineSession: "会话/树/压缩", providerLabel: "供应商", timelineHttp: "HTTP", emptyTimeline: "暂无时间线条目", showSseInTimeline: "SSE / 合并流", mergeSseDelta: "合并 delta SSE", timelineSseMergedLabel: "SSE 合并", timelineSseMerged: "{lines} 行 · {chars} 字符", reqSectionParams: "参数", reqSectionMessages: "消息", reqSectionTools: "工具", reqSectionRaw: "其它字段", reqTabInput: "input", reqTabJson: "完整 JSON", reqTabPlain: "原文", reqToolSchema: "参数 schema", reqToolNoSchema: "无 schema", reqToolNone: "无工具定义", copyCode: "复制", copiedCode: "已复制", copyFailed: "复制失败", emptyWait: "执行 /trace on 后发起对话,或选择左侧会话", emptySelectExchange: "选择一条 HTTP 交换", emptyNoSessions: "暂无会话记录", emptyNoExchanges: "该会话尚无交换", emptyNoSse: "尚无 SSE 数据", emptyNoResponse: "尚无响应元数据", emptyCleared: "已清空视图(磁盘 JSONL 保留)", assembledTitle: "拼接文本(delta 累积)", parseEvents: "解析事件", linesChars: "{lines} 行 · {chars} 字符", stats: "{exchanges} 交换 · {sse} SSE · {pi} Pi", lang: "语言", themeLabel: "主题", themeSystem: "跟随系统", themeLight: "浅色", themeDark: "深色", noBody: "无 body", noHeaders: "无请求头", noUrl: "无 URL", emptyNoRequest: "尚无请求元数据", sessionUnsaved: "未保存会话", downloadTrace: "下载", deleteTrace: "删除", deleteConfirm: "删除该会话全部轨迹文件?不可恢复", bytes: "{n} B", records: "{n} 条", tabOverview: "Trace 总览", overviewTitle: "本会话用量", overviewLead: "{gens} 次 HTTP 模型请求,合计 {tokens} tokens({records} 条轨迹含 SSE/Pi 事件)。", overviewHintCost: "累计费用(有定价时)", overviewHintLatency: "平均往返耗时", overviewHintGens: "HTTP 交换次数", overviewErrors: "{n} 次错误", overviewCache: "缓存命中约 {pct}", overviewDetailsTitle: "明细", scoreAvg: "评分均值", colLatency: "延迟", colTtft: "首 token", colTps: "tokens/s", colGenerations: "调用次数", colSpans: "Spans", colErrors: "错误", emptyNoMetrics: "无指标数据", emptySelectSession: "选择左侧 Pi 会话", scoreName: "评分名", scoreValue: "分值", scoreAdd: "添加评分", tabUsage: "用量/成本", usageTitle: "LLM 用量汇总", usageSession: "本会话累计", usageExchange: "本交换", colInput: "输入", colOutput: "输出", colCacheRead: "缓存读", colCacheWrite: "缓存写", colCacheRate: "缓存命中率", colCost: "成本", colTotal: "合计 tokens", tabGenerations: "模型调用明细", colHttp: "请求", mediaTitle: "媒体", apiError: "接口错误", cmdTraceDesc: "Provider HTTP/SSE 抓包:on | off | ui | path | port", cmdTraceSubOn: "开启抓包并启动 Web UI(不自动打开浏览器)", cmdTraceSubOff: "关闭抓包", cmdTraceSubUi: "启动 Web UI(不自动打开浏览器)", cmdTraceSubPath: "日志根目录", cmdTraceSubPort: "查看或设置 UI 端口", cmdTracePortItem: "UI 端口", cmdTraceEnabled: "provider trace {status}", cmdTraceDisabled: "provider trace 已关闭", cmdTraceUiLine: "UI {url}", cmdTraceUiNotify: "trace UI: {url}", cmdTracePortShow: "当前 UI 端口:{port}\n未单独配置时默认:{defaultPort}\n\n设置:/trace port <1024-65535>\n环境变量:PI_PROVIDER_TRACE_UI_PORT\n配置文件:~/.pi/provider-trace/ui-config.json", cmdTracePortInvalid: "无效端口", cmdTracePortSet: "UI 端口已设为 {port},下次 /trace on 或 /trace ui 生效", cmdTraceStatusHelp: "trace: {status}\n/trace on | ui | off | path | port", flagTraceDesc: "启动时等同 /trace on(抓包 + Web UI,不自动开浏览器)", cmdTraceUiStartFailed: "无法启动 Trace Web UI:{message}", cmdTracePortInUse: "端口 {port} 已被占用。请 /trace port <端口>,或设置 PI_PROVIDER_TRACE_UI_PORT,或编辑 ~/.pi/provider-trace/ui-config.json", cmdTraceStatusOn: "ON → {dir}/sessions/…{ui}", cmdTraceStatusOff: "OFF", cmdTraceStatusUiSuffix: " | UI {url}", }, en: { title: "pi-provider-trace", live: "live", offline: "offline", traceOn: "trace ON", traceOff: "trace OFF", sessions: "Pi sessions", exchanges: "HTTP exchanges", newestFirst: "newest first", autoFollow: "Follow latest", filterPlaceholder: "Filter URL / id", pause: "Pause", resume: "Resume", clear: "Clear view", tabStream: "Stream SSE", tabReqHeaders: "Req headers", tabRequestBody: "Req body", tabResponse: "Response", tabRaw: "Raw lines", tabTimeline: "Timeline", timelinePi: "Pi", timelineSession: "Session/tree/compact", providerLabel: "Provider", timelineHttp: "HTTP", emptyTimeline: "No timeline entries", showSseInTimeline: "SSE / merged stream", mergeSseDelta: "Merge delta SSE", timelineSseMergedLabel: "SSE merged", timelineSseMerged: "{lines} lines · {chars} chars", reqSectionParams: "Parameters", reqSectionMessages: "Messages", reqSectionTools: "Tools", reqSectionRaw: "Other fields", reqTabInput: "input", reqTabJson: "Full JSON", reqTabPlain: "Plain", reqToolSchema: "Parameter schema", reqToolNoSchema: "No schema", reqToolNone: "No tools", copyCode: "Copy", copiedCode: "Copied", copyFailed: "Failed", emptyWait: "Run /trace on and chat, or pick a session", emptySelectExchange: "Select an HTTP exchange", emptyNoSessions: "No sessions yet", emptyNoExchanges: "No exchanges in this session", emptyNoSse: "No SSE data yet", emptyNoResponse: "No response metadata", emptyCleared: "View cleared (JSONL on disk kept)", assembledTitle: "Assembled text (delta accumulation)", parseEvents: "Parse events", linesChars: "{lines} lines · {chars} chars", stats: "{exchanges} exchanges · {sse} SSE · {pi} Pi", lang: "Language", themeLabel: "Theme", themeSystem: "System", themeLight: "Light", themeDark: "Dark", noBody: "no body", noHeaders: "no headers", noUrl: "no URL", emptyNoRequest: "No request metadata yet", sessionUnsaved: "unsaved session", downloadTrace: "Download", deleteTrace: "Delete", deleteConfirm: "Delete all trace files for this session? Cannot undo.", bytes: "{n} B", records: "{n} records", tabOverview: "Trace overview", overviewTitle: "This session", overviewLead: "{gens} HTTP model request(s), {tokens} tokens total ({records} trace lines incl. SSE/Pi events).", overviewHintCost: "Estimated cost when priced", overviewHintLatency: "Average round-trip time", overviewHintGens: "HTTP exchanges", overviewErrors: "{n} errors", overviewCache: "Cache hit ~{pct}", overviewDetailsTitle: "Details", scoreAvg: "Avg score", colLatency: "Latency", colTtft: "TTFT", colTps: "tokens/s", colGenerations: "Generations", colSpans: "Spans", colErrors: "Errors", emptyNoMetrics: "No metrics yet", emptySelectSession: "Select a Pi session", scoreName: "Score name", scoreValue: "Value", scoreAdd: "Add score", tabUsage: "Usage & cost", usageTitle: "LLM usage", usageSession: "Session total", usageExchange: "This exchange", colInput: "Input", colOutput: "Output", colCacheRead: "Cache read", colCacheWrite: "Cache write", colCacheRate: "Cache hit rate", colCost: "Cost", colTotal: "Total tokens", tabGenerations: "Generations", colHttp: "Request", mediaTitle: "Media", apiError: "API error", cmdTraceDesc: "Provider HTTP/SSE trace: on | off | ui | path | port", cmdTraceSubOn: "Enable capture and start Web UI (does not open browser)", cmdTraceSubOff: "Disable capture", cmdTraceSubUi: "Start Web UI (does not open browser)", cmdTraceSubPath: "Log root directory", cmdTraceSubPort: "Show or set UI port", cmdTracePortItem: "UI port", cmdTraceEnabled: "provider trace {status}", cmdTraceDisabled: "provider trace OFF", cmdTraceUiLine: "UI {url}", cmdTraceUiNotify: "trace UI: {url}", cmdTracePortShow: "Current UI port: {port}\nDefault when unset: {defaultPort}\n\nSet: /trace port <1024-65535>\nEnv: PI_PROVIDER_TRACE_UI_PORT\nFile: ~/.pi/provider-trace/ui-config.json", cmdTracePortInvalid: "Invalid port", cmdTracePortSet: "UI port set to {port}; applies on next /trace on or /trace ui", cmdTraceStatusHelp: "trace: {status}\n/trace on | ui | off | path | port", flagTraceDesc: "On startup, same as /trace on (capture + Web UI, no auto browser)", cmdTraceUiStartFailed: "Failed to start Trace Web UI: {message}", cmdTracePortInUse: "Port {port} is in use. Try /trace port , PI_PROVIDER_TRACE_UI_PORT, or ~/.pi/provider-trace/ui-config.json", cmdTraceStatusOn: "ON → {dir}/sessions/…{ui}", cmdTraceStatusOff: "OFF", cmdTraceStatusUiSuffix: " | UI {url}", }, }; export function t(locale: Locale, key: string, vars?: Record): string { let s = I18N[locale][key] ?? I18N.en[key] ?? key; if (vars) { for (const [k, v] of Object.entries(vars)) { s = s.replace(`{${k}}`, String(v)); } } return s; }