diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index ce3818db7..2d6cd9e5e 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -136,7 +136,7 @@ jobs: set -euo pipefail test -L "dist/macos/stage/Applications" test "$(readlink "dist/macos/stage/Applications")" = "/Applications" - for app in "dist/macos/stage/Codex++.app" "dist/macos/stage/Codex++ 管理工具.app"; do + for app in "dist/macos/stage/Codex++.app" "dist/macos/stage/Codex++ Manager.app"; do test -f "$app/Contents/Info.plist" test -f "$app/Contents/PkgInfo" test -x "$app/Contents/MacOS/$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Contents/Info.plist")" diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml index 3fc0ad6a5..46e2afa06 100644 --- a/.github/workflows/release-assets.yml +++ b/.github/workflows/release-assets.yml @@ -133,7 +133,7 @@ jobs: set -euo pipefail test -L "dist/macos/stage/Applications" test "$(readlink "dist/macos/stage/Applications")" = "/Applications" - for app in "dist/macos/stage/Codex++.app" "dist/macos/stage/Codex++ 管理工具.app"; do + for app in "dist/macos/stage/Codex++.app" "dist/macos/stage/Codex++ Manager.app"; do test -f "$app/Contents/Info.plist" test -f "$app/Contents/PkgInfo" test -x "$app/Contents/MacOS/$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Contents/Info.plist")" diff --git a/apps/codex-plus-manager/index.html b/apps/codex-plus-manager/index.html index 0bfda5710..f0bce202d 100644 --- a/apps/codex-plus-manager/index.html +++ b/apps/codex-plus-manager/index.html @@ -3,7 +3,7 @@ - Codex++ 管理工具 + Codex++ Manager
diff --git a/apps/codex-plus-manager/src-tauri/src/lib.rs b/apps/codex-plus-manager/src-tauri/src/lib.rs index ae70cb725..1b1ab691c 100644 --- a/apps/codex-plus-manager/src-tauri/src/lib.rs +++ b/apps/codex-plus-manager/src-tauri/src/lib.rs @@ -35,7 +35,7 @@ pub fn run() { }; let mut main_window_builder = tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App(url.into())) - .title("Codex++ 管理工具") + .title("Codex++ Manager") .inner_size(1180.0, 820.0) .min_inner_size(960.0, 720.0); if let Some(icon) = app.default_window_icon().cloned() { diff --git a/apps/codex-plus-manager/src-tauri/tauri.conf.json b/apps/codex-plus-manager/src-tauri/tauri.conf.json index d1540cf6e..6a13477dc 100644 --- a/apps/codex-plus-manager/src-tauri/tauri.conf.json +++ b/apps/codex-plus-manager/src-tauri/tauri.conf.json @@ -14,7 +14,7 @@ { "create": false, "label": "main", - "title": "Codex++ 管理工具", + "title": "Codex++ Manager", "width": 1180, "height": 820, "minWidth": 960, diff --git a/apps/codex-plus-manager/src-tauri/tests/windows_subsystem.rs b/apps/codex-plus-manager/src-tauri/tests/windows_subsystem.rs index 6ee1edf4e..fa114c379 100644 --- a/apps/codex-plus-manager/src-tauri/tests/windows_subsystem.rs +++ b/apps/codex-plus-manager/src-tauri/tests/windows_subsystem.rs @@ -110,6 +110,11 @@ fn windows_binaries_request_administrator_privileges() { assert!(windows_manifest.contains("requireAdministrator")); assert!(windows_manifest.contains("Microsoft.Windows.Common-Controls")); assert!(windows_installer.contains("RequestExecutionLevel admin")); + assert!(windows_installer.contains("CreateShortcut \"$DESKTOP\\Codex++ Manager.lnk\"")); + assert!(windows_installer.contains("CreateShortcut \"$SMPROGRAMS\\Codex++\\Codex++ Manager.lnk\"")); + assert!(windows_installer.contains("CreateShortcut \"$SMPROGRAMS\\Codex++\\Uninstall Codex++.lnk\"")); + assert!(!windows_installer.contains("CreateShortcut \"$DESKTOP\\Codex++ 管理工具.lnk\"")); + assert!(!windows_installer.contains("CreateShortcut \"$SMPROGRAMS\\Codex++\\卸载 Codex++.lnk\"")); } #[test] @@ -159,8 +164,11 @@ fn macos_packager_hides_silent_launcher_but_not_manager() { "create_app \"Codex++\" \"CodexPlusPlus\" \"$BINARY_DIR/codex-plus-plus\" \"com.bigpizzav3.codexplusplus\" \"true\"" )); assert!(script.contains( - "create_app \"Codex++ 管理工具\" \"CodexPlusPlusManager\" \"$BINARY_DIR/codex-plus-plus-manager\" \"com.bigpizzav3.codexplusplus.manager\" \"false\"" + "create_app \"Codex++ Manager\" \"CodexPlusPlusManager\" \"$BINARY_DIR/codex-plus-plus-manager\" \"com.bigpizzav3.codexplusplus.manager\" \"false\"" )); + assert!(script.contains("sign_app \"$STAGE/Codex++ Manager.app\"")); + assert!(script.contains("verify_app \"$STAGE/Codex++ Manager.app\"")); + assert!(!script.contains("create_app \"Codex++ 管理工具\"")); } #[test] diff --git a/apps/codex-plus-manager/src/App.tsx b/apps/codex-plus-manager/src/App.tsx index 11a2c709b..55d2b9df8 100644 --- a/apps/codex-plus-manager/src/App.tsx +++ b/apps/codex-plus-manager/src/App.tsx @@ -68,7 +68,7 @@ import { serializeModelWindowRows, type ModelWindowRow, } from "./model-windows"; -import { getLanguage, t, tf, toggleLanguage } from "@/i18n"; +import { getLanguage, languageButtonTitle, t, tf, toggleLanguage } from "@/i18n"; type Status = "ok" | "failed" | "not_implemented" | "not_checked" | string; @@ -730,6 +730,10 @@ const defaultSettings: BackendSettings = { }; export function App() { + useEffect(() => { + document.title = t("Codex++ 管理工具"); + }, []); + const [theme, setTheme] = useState(() => loadInitialTheme()); const [route, setRoute] = useState(() => loadInitialRoute()); const [notice, setNotice] = useState<{ title: string; message: string; status?: Status } | null>(null); @@ -2070,7 +2074,7 @@ export function App() { ); diff --git a/apps/codex-plus-manager/src/i18n-en.ts b/apps/codex-plus-manager/src/i18n-en.ts index afcb02de2..4a9411fd0 100644 --- a/apps/codex-plus-manager/src/i18n-en.ts +++ b/apps/codex-plus-manager/src/i18n-en.ts @@ -5,6 +5,11 @@ // Plain strings: t("中文") -> EN_PLAIN["中文"]. export const EN_PLAIN: Record = { + "JOJO Code 包月": "JOJO Code Monthly", + "Codex++ 管理工具": "Codex++ Manager", + "小米 MiMo": "Xiaomi MiMo", + "百度千帆 Coding Plan": "Baidu Qianfan Coding Plan", + "火山引擎 Ark": "Volcengine Ark", "API Key 模式下扩展插件市场请求,尽量显示完整插件列表;官方/混合模式通常不需要。": "Expands plugin marketplace requests in API Key mode to show the full plugin list. Usually unnecessary in official/mixed mode.", "API Key 环境变量": "API Key environment variable", @@ -158,6 +163,7 @@ export const EN_PLAIN: Record = { "切换到此供应商时会写入 ~/.codex/auth.json": "Switching to this provider writes ~/.codex/auth.json", "切换到此供应商时会写入的预览;上下文开关变化会立即反映": "Preview of what gets written when switching to this provider; context toggles are reflected immediately", "切换到中文": "Switch to Chinese", + "切换到越南语": "Switch to Vietnamese", "切换到英文": "Switch to English", "切换到浅色": "Switch to light", "切换到深色": "Switch to dark", diff --git a/apps/codex-plus-manager/src/i18n-vi.ts b/apps/codex-plus-manager/src/i18n-vi.ts new file mode 100644 index 000000000..7536352d4 --- /dev/null +++ b/apps/codex-plus-manager/src/i18n-vi.ts @@ -0,0 +1,789 @@ +// Vietnamese translations for the Codex++ manager UI, keyed by original Chinese source text. + + +export const VI_PLAIN: Record = { + "JOJO Code 包月": "JOJO Code theo tháng", + "Codex++ 管理工具": "Trình quản lý Codex++", + "小米 MiMo": "Xiaomi MiMo", + "百度千帆 Coding Plan": "Kế hoạch mã hóa Baidu Qianfan", + "火山引擎 Ark": "Hòm động cơ Volc", + "API Key 模式下扩展插件市场请求,尽量显示完整插件列表;官方/混合模式通常不需要。": "Mở rộng các yêu cầu về thị trường plugin trong chế độ Khóa API để hiển thị danh sách plugin đầy đủ. Thường không cần thiết ở chế độ chính thức/hỗn hợp.", + "API Key 环境变量": "Biến môi trường khóa API", + "Chat Completions 转 Responses": "Hoàn thành trò chuyện để phản hồi", + "Codex 启动参数": "Đối số khởi chạy Codex", + "Codex 工具与插件": "Công cụ và plugin Codex", + "Codex 应用": "ứng dụng Codex", + "Codex 应用路径": "Đường dẫn ứng dụng Codex", + "Codex 意外停止": "Codex dừng đột ngột", + "Codex 版本": "Phiên bản Codex", + "Codex 目标": "Mục tiêu Codex", + "Codex++ 官方中转站,主打稳定接入和划算价格,支持 GPT-5.5、GPT-5.4、Claude Opus 4.8、Claude Opus 4.7、gpt-image-2 等模型与图像能力。": "Rơle Codex++ chính thức, được thiết kế để truy cập ổn định và có giá trị tốt, hỗ trợ GPT-5.5, GPT-5.4, Claude Opus 4.8, Claude Opus 4.7, gpt-image-2 và các mẫu khác cùng với khả năng hình ảnh.", + "Codex++ 导入": "Nhập Codex++", + "Codex++ 版本": "Phiên bản Codex++", + "Codex增强": "Cải tiến Codex", + "Codex增强模式": "Chế độ nâng cao Codex", + "Debug 端口": "Cổng gỡ lỗi", + "Fast 按钮": "Nút nhanh", + "GitHub Release 更新": "Cập nhật phát hành GitHub", + "GitHub Release 检查": "Kiểm tra phát hành GitHub", + "Helper 端口": "Cổng trợ giúp", + "MCP、Skills、Plugins 作为全局配置独立管理,切换任意供应商都会合并。": "MCP, Kỹ năng và Plugin được quản lý độc lập dưới dạng cấu hình chung và được hợp nhất bất cứ khi nào bạn chuyển đổi nhà cung cấp.", + "Markdown 导出": "Xuất khẩu giảm giá", + "Provider 同步目标": "Mục tiêu đồng bộ hóa nhà cung cấp", + "Stepwise 直接发送": "Gửi trực tiếp từng bước", + "基于当前对话生成下一步建议,使用独立 API 配置。": "Tạo đề xuất bước tiếp theo từ cuộc trò chuyện hiện tại bằng cách sử dụng cấu hình API riêng.", + "插件与模型": "Plugin và mô hình", + "管理插件市场、模型列表和服务档位相关增强。": "Quản lý các cải tiến cho thị trường plugin, danh sách mô hình và cấp dịch vụ.", + "对话与输入": "Cuộc trò chuyện và đầu vào", + "调整会话管理、输入行为和对话阅读体验。": "Điều chỉnh quản lý phiên, hành vi đầu vào và trải nghiệm đọc hội thoại.", + "界面与启动": "Giao diện và khởi động", + "控制语言、启动速度和 Codex 原生界面调整。": "Kiểm soát ngôn ngữ, tốc độ khởi động và điều chỉnh giao diện người dùng gốc Codex.", + "远程项目": "Dự án từ xa", + "连接 Zed Remote 和 upstream worktree 辅助能力。": "Kết nối Zed Remote và các khả năng trợ giúp của cây công việc ngược dòng.", + "最多建议数": "Đề xuất tối đa", + "最大输入字符": "Ký tự đầu vào tối đa", + "最大输出 tokens": "Mã thông báo đầu ra tối đa", + "测试连接": "Kiểm tra kết nối", + "超时毫秒": "Hết thời gian tính bằng mili giây", + "连接": "Sự liên quan", + "高级参数": "Thông số nâng cao", + "TOML 配置体": "Nội dung cấu hình TOML", + "Watcher 操作": "Hành động của người theo dõi", + "Watcher 状态": "Trạng thái người theo dõi", + "Watcher 用于保持 Codex++ 接管状态": "Người theo dõi giữ quyền kiểm soát Codex++", + "Watcher 自动接管": "Watcher tự động tiếp quản", + "Zed 远程打开": "Zed mở remote", + "Zed 远程项目": "Dự án từ xa của Zed", + "Zed 项目记录": "Lịch sử dự án Zed", + "Zed 默认打开策略": "Chiến lược mở Zed mặc định", + "Zed 默认行为": "Hành vi mặc định của Zed", + "cc-switch 导入": "nhập cc-switch", + "config.toml 预览": "xem trước config.toml", + "provider 未记录": "nhà cung cấp không được ghi lại", + "ssh:// URL 已复制。": "ssh:// URL đã được sao chép.", + "、": ", ", + "」的供应商": "”", + "上一次切换还没有完成,请稍后再试。": "Việc chuyển đổi trước đó vẫn chưa kết thúc. Vui lòng thử lại trong thời gian ngắn.", + "上下文大小": "Kích thước ngữ cảnh", + "上下文窗口": "Cửa sổ ngữ cảnh", + "上次修复结果": "Kết quả sửa chữa cuối cùng", + "上次更新结果": "Kết quả cập nhật lần cuối", + "上游协议": "Giao thức ngược dòng", + "下载并运行安装包": "Tải xuống và chạy trình cài đặt", + "不写 API 文件": "Không ghi tệp API", + "与 Codex 内插件菜单使用同一个远端广告源": "Sử dụng nguồn quảng cáo từ xa giống như menu plugin trong Codex", + "个": "", + "个会话": "(các) phiên", + "中国官方": "quan chức trung quốc", + "主页": "Trang chủ", + "主题和启动参数": "Chủ đề và khởi động các đối số", + "从 GitHub 静态清单加载": "Được tải từ bảng kê khai GitHub tĩnh", + "从 Word 等富文本粘贴到 Codex composer 时只保留纯文本,避免被识别为图片/文件附件。需重启 Codex 才生效。": "Chỉ giữ lại văn bản thuần túy khi dán văn bản có định dạng (ví dụ: từ Word) vào trình soạn thảo Codex, tránh bị coi là văn bản đính kèm hình ảnh/tệp. Yêu cầu khởi động lại Codex.", + "从上游获取": "Tìm nạp từ thượng nguồn", + "从最新 upstream 分支创建 Git worktree。": "Tạo cây công việc Git từ nhánh ngược dòng mới nhất.", + "从环境变量和 config.toml 的 /v1/models 拉取模型并补进模型列表。": "Kéo các mô hình từ các biến môi trường và /v1/models của config.toml rồi thêm chúng vào danh sách mô hình.", + "从第三方导入": "Nhập khẩu từ bên thứ ba", + "从预设模板创建": "Tạo từ mẫu đặt trước", + "会话": "Phiên họp", + "会话 ID 标识": "Huy hiệu ID phiên", + "会话删除": "Xóa phiên", + "会话删除、导出、项目移动和用户脚本等界面能力": "Các khả năng của giao diện người dùng như xóa phiên, xuất, di chuyển dự án và tập lệnh người dùng", + "会话删除、导出、项目移动和脚本能力": "Khả năng xóa phiên, xuất, di chuyển dự án và tập lệnh", + "会话总数": "Tổng số phiên", + "会话管理": "Quản lý phiên", + "会话项目移动": "Phiên di chuyển dự án", + "使用": "Sử dụng", + "使用中": "Đang sử dụng", + "例如 C:\\Program Files\\WindowsApps\\OpenAI.Codex...\\app": "ví dụ. C:\\Tệp chương trình\\WindowsApps\\OpenAI.Codex...\\app", + "例如 context7": "ví dụ. bối cảnh7", + "例如 deepseek-v4-pro": "ví dụ. deepseek-v4-pro", + "例如 gpt-5.4-mini": "ví dụ. gpt-5.4-mini", + "例如 主力聚合池": "ví dụ. Nhóm tổng hợp chính", + "供": "P", + "供应商": "nhà cung cấp", + "供应商切换": "Chuyển đổi nhà cung cấp", + "供应商切换不可用": "Chuyển đổi nhà cung cấp không có sẵn", + "供应商切换中": "Chuyển đổi nhà cung cấp", + "供应商列表": "Danh sách nhà cung cấp", + "供应商测试": "Kiểm tra nhà cung cấp", + "供应商测试模型": "Mô hình thử nghiệm nhà cung cấp", + "供应商配置": "Cấu hình nhà cung cấp", + "供应商配置可能不正确": "Cấu hình nhà cung cấp có thể không chính xác", + "供应商配置已关闭": "Đã tắt cấu hình nhà cung cấp", + "供应商配置总开关已关闭": "Công tắc chính cấu hình nhà cung cấp đang tắt", + "供应商配置总开关已关闭;当前只保存配置,不写入 Codex live 文件": "Công tắc chính cấu hình nhà cung cấp bị tắt; các thay đổi chỉ được lưu, không được ghi vào tệp trực tiếp Codex", + "供应商预设列表": "Danh sách đặt trước của nhà cung cấp", + "保存": "Cứu", + "保存为默认路径": "Lưu làm đường dẫn mặc định", + "保存到 Codex++ state,不改写 Zed settings。": "Đã lưu vào trạng thái Codex++, không sửa đổi cài đặt Zed.", + "保存增强设置": "Lưu cài đặt nâng cao", + "保存扩展项": "Lưu mục nhập", + "保存的应用路径": "Đường dẫn ứng dụng đã lưu", + "保存策略": "Lưu chiến lược", + "保存自动修复设置": "Lưu cài đặt tự động sửa chữa", + "保存设置": "Lưu cài đặt", + "保存路径": "Đường dẫn đã lưu", + "修复入口": "Sửa chữa điểm vào", + "修复快捷方式": "Sửa chữa phím tắt", + "修复插件市场": "Thị trường plugin sửa chữa", + "健康检查": "Kiểm tra sức khỏe", + "先添加至少 1 个已填写 Base URL / Key 的 API 供应商,再创建聚合供应商。": "Thêm ít nhất 1 nhà cung cấp API có URL/Khóa cơ sở trước khi tạo nhà cung cấp tổng hợp.", + "免安装版或解包版只需要选择一次,之后静默启动会自动复用": "Các bản dựng di động hoặc đã giải nén chỉ cần được chọn một lần; khởi động im lặng sẽ tái sử dụng nó sau đó", + "入口卸载": "Gỡ cài đặt điểm đầu vào", + "入口安装": "Cài đặt điểm đầu vào", + "入口安装、修复、Watcher 与手动启动": "Cài đặt điểm truy cập, sửa chữa, theo dõi và khởi chạy thủ công", + "入口管理": "Quản lý điểm vào", + "全选当前列表": "Chọn tất cả trong danh sách hiện tại", + "关于": "Về", + "关于 Codex++": "Giới thiệu về Codex++", + "关闭": "Tắt", + "关闭后会停用删除、导出、项目移动、插件相关和菜单位置增强。": "Khi tắt, nó sẽ vô hiệu hóa tính năng xóa, xuất, di chuyển dự án, các cải tiến liên quan đến plugin và vị trí menu.", + "关闭后本工具不会在手动切换时写入 Codex 的 config.toml / auth.json;启动 Codex 时始终不会自动改这些文件。": "Khi tắt, công cụ này sẽ không ghi config.toml / auth.json của Codex trên các công tắc thủ công; nó không bao giờ tự động sửa đổi các tệp này khi khởi chạy Codex.", + "兼容增强": "Cải tiến tương thích", + "内容来自 BigPizzaV3/Ad-List,分为赞助商推荐和普通推荐。": "Nội dung đến từ BigPizzaV3/Ad-List, được chia thành đề xuất của nhà tài trợ và đề xuất thông thường.", + "内置": "Tích hợp sẵn", + "内置、手动和市场安装脚本;可在这里启停或删除用户脚本": "Các tập lệnh tích hợp, thủ công và được cài đặt trên thị trường; bật, tắt hoặc xóa tập lệnh người dùng tại đây", + "内置和用户自定义脚本清单": "Khoảng không quảng cáo tập lệnh tích hợp và do người dùng xác định", + "切换 thread 时恢复上一次浏览位置。": "Khôi phục vị trí cuộn cuối cùng khi chuyển chủ đề.", + "切换中": "Chuyển đổi", + "切换主题": "Chuyển đổi chủ đề", + "切换到此供应商时会写入 ~/.codex/auth.json": "Chuyển sang nhà cung cấp này ghi ~/.codex/auth.json", + "切换到此供应商时会写入的预览;上下文开关变化会立即反映": "Xem trước nội dung được viết khi chuyển sang nhà cung cấp này; chuyển đổi bối cảnh được phản ánh ngay lập tức", + "切换到中文": "Chuyển sang tiếng Trung", + "切换到越南语": "Chuyển sang tiếng Việt", + "切换到英文": "Chuyển sang tiếng Anh", + "切换到浅色": "Chuyển sang ánh sáng", + "切换到深色": "Chuyển sang màu tối", + "切换对话保留位置": "Giữ nguyên vị trí cuộn trên các chủ đề", + "点击建议后自动发送;关闭时只填入输入框。": "Tự động gửi sau khi nhấp vào gợi ý. Khi tắt, nó chỉ lấp đầy nhà soạn nhạc.", + "刚刚启动": "Vừa ra mắt", + "删除": "Xóa bỏ", + "删除会创建本地备份;如果 Codex App 正在使用该会话,建议先关闭对应会话窗口再操作。": "Việc xóa sẽ tạo bản sao lưu cục bộ; nếu ứng dụng Codex đang sử dụng phiên này, trước tiên hãy đóng cửa sổ phiên.", + "删除会话": "Xóa phiên", + "删除供应商": "Xóa nhà cung cấp", + "删除已选": "Xóa đã chọn", + "删除扩展项": "Xóa mục nhập", + "删除模型": "Xóa mô hình", + "刷新": "Làm cho khỏe lại", + "刷新会话": "Làm mới phiên", + "刷新列表": "Làm mới danh sách", + "刷新市场": "Làm mới thị trường", + "刷新当前页面": "Làm mới trang hiện tại", + "刷新推荐": "Làm mới đề xuất", + "刷新本地": "Làm mới cục bộ", + "刷新项目": "Làm mới dự án", + "加入当前工作区": "Thêm vào không gian làm việc hiện tại", + "包含版本、路径、设置和平台信息": "Bao gồm phiên bản, đường dẫn, cài đặt và thông tin nền tảng", + "协议": "Giao thức", + "卸载入口": "Gỡ cài đặt điểm vào", + "卸载时移除 Codex++ 托管数据": "Xóa dữ liệu được quản lý Codex++ khi gỡ cài đặt", + "历史会话修复": "Sửa chữa phiên lịch sử", + "历史会话修复失败,请查看错误提示后重试。": "Sửa chữa phiên lịch sử không thành công. Kiểm tra thông báo lỗi và thử lại.", + "历史会话修复进度": "Tiến trình sửa chữa phiên lịch sử", + "压缩上下文大小": "Kích thước bối cảnh nén", + "原生菜单栏位置": "Vị trí thanh menu gốc", + "原生菜单汉化": "Bản địa hóa menu gốc", + "反馈问题": "Báo cáo sự cố", + "发现": "Đã phát hiện", + "发送 hi 测试": "Gửi bài kiểm tra 'xin chào'", + "取消": "Hủy bỏ", + "只保留非 MCP、Skills、Plugins 的跨供应商配置;工具与插件在独立页面管理。": "Chỉ giữ cấu hình của nhiều nhà cung cấp không phải là MCP/Kỹ năng/Plugins; các công cụ và plugin được quản lý trên một trang riêng.", + "只填写表头下面的内容,例如:\ncommand = \"npx\"\nargs = [\"-y\", \"@upstash/context7-mcp\"]": "Chỉ điền nội dung dưới tiêu đề, ví dụ:\nlệnh = \"npx\"\nargs = [\"-y\", \"@upstash/context7-mcp\"]", + "只能勾选已填写 Base URL / Key 的 API 供应商,聚合供应商不会作为成员。": "Chỉ có thể chọn nhà cung cấp API có URL / Khóa cơ sở; nhà cung cấp tổng hợp không thể là thành viên.", + "可更新": "Đã có bản cập nhật", + "同一对话保持一个成员,不同对话依次分配。": "Giữ một thành viên cho mỗi cuộc trò chuyện; các cuộc trò chuyện khác nhau được chỉ định lần lượt.", + "同步 Zed settings": "Đồng bộ cài đặt Zed", + "同步目标": "Mục tiêu đồng bộ hóa", + "名称": "Tên", + "启动 Codex App 时追加到默认 CDP 参数后。留空则保持默认启动行为。": "Được thêm vào sau các đối số CDP mặc định khi khởi chạy ứng dụng Codex. Để trống để giữ hành vi khởi chạy mặc định.", + "启动 Codex++": "Khởi chạy Codex++", + "启动任务": "Khởi động nhiệm vụ", + "启动前自动修复历史会话": "Tự động sửa chữa các phiên lịch sử trước khi ra mắt", + "启动时通过本地主进程调试端口汉化 Codex 原生菜单;不修改安装包。需重启 Codex 才生效。": "Bản địa hóa menu gốc Codex thông qua cổng gỡ lỗi quy trình chính cục bộ khi khởi chạy; không sửa đổi gói cài đặt. Yêu cầu khởi động lại Codex.", + "启用": "Cho phép", + "启用 Codex 图片覆盖层": "Bật lớp phủ hình ảnh Codex", + "启用 Codex增强": "Kích hoạt các cải tiến của Codex", + "启用 Windows Computer Use Guard": "Kích hoạt tính năng bảo vệ sử dụng máy tính Windows", + "启用供应商配置切换": "Cho phép chuyển đổi cấu hình nhà cung cấp", + "启用此扩展项": "Kích hoạt mục này", + "启用目标功能": "Kích hoạt tính năng mục tiêu", + "图片": "Hình ảnh", + "图片覆盖层": "Lớp phủ hình ảnh", + "在会话列表悬停显示删除按钮,并支持撤销。": "Hiển thị nút xóa khi di chuột trong danh sách phiên, có hỗ trợ hoàn tác.", + "在会话列表显示导出按钮,导出带时间戳的 Markdown。": "Hiển thị nút xuất trong danh sách phiên để xuất Markdown có dấu thời gian.", + "在 Codex 页面显示可拖动的后续建议浮层;建议由单独配置的 Stepwise API 生成。": "Hiển thị lớp phủ đề xuất tiếp theo có thể kéo được trong trang Codex. Các đề xuất được tạo bởi API Stepwise riêng biệt.", + "在侧边栏会话标题前显示短 ID 和 UUIDv7 创建时间,方便定位历史会话。": "Hiển thị thời gian tạo ID và UUIDv7 ngắn trước tiêu đề phiên trong thanh bên để giúp xác định các phiên trước đây.", + "基础设置": "Cài đặt cơ bản", + "填写中转服务 Base URL": "Nhập URL cơ sở của dịch vụ chuyển tiếp", + "复制": "Sao chép", + "复制 ssh:// URL": "Sao chép URL ssh://", + "复制失败": "Sao chép không thành công", + "复制报告": "Sao chép báo cáo", + "复用窗口": "Cửa sổ tái sử dụng", + "多选": "Chọn nhiều lần", + "失败": "Thất bại", + "失败切换": "Chuyển đổi dự phòng", + "失败切换会保留成员顺序,优先使用第一个可用供应商。": "Chuyển đổi dự phòng duy trì trật tự thành viên, ưu tiên nhà cung cấp sẵn có đầu tiên.", + "如果使用官方模式或官方混入 API 模式,通常不需要开启插件市场解锁。": "Nếu bạn sử dụng chế độ chính thức hoặc chế độ API hỗn hợp chính thức, bạn thường không cần mở khóa thị trường plugin.", + "安装": "Cài đặt", + "安装 watcher": "cài đặt trình theo dõi", + "安装入口": "Cài đặt điểm vào", + "安装包更新失败,请查看错误提示后重试。": "Cập nhật trình cài đặt không thành công. Kiểm tra thông báo lỗi và thử lại.", + "安装包更新进度": "Tiến trình cập nhật trình cài đặt", + "安装维护": "Cài đặt & bảo trì", + "完整增强": "Nâng cao đầy đủ", + "官方": "Chính thức", + "官方中转站": "Tiếp sức chính thức", + "官方混入 API Key": "Khóa API hỗn hợp chính thức", + "官方混合 API 不应在 auth.json 中保存 OPENAI_API_KEY。请清理此供应商的 auth.json 后再切换。": "API hỗn hợp chính thức không được lưu trữ OPENAI_API_KEY trong auth.json. Xóa auth.json của nhà cung cấp này trước khi chuyển đổi.", + "官方登录": "Đăng nhập chính thức", + "官方登录模式": "Chế độ đăng nhập chính thức", + "对话居中宽度": "Chiều rộng cuộc trò chuyện được căn giữa", + "导入 Codex++ 供应商": "Nhập nhà cung cấp Codex++", + "尚未刷新": "Chưa được làm mới", + "尚未加载推荐内容": "Đề xuất chưa được tải", + "尚未检查 Codex 应用路径。": "Đường dẫn ứng dụng Codex chưa được kiểm tra.", + "尚未检查 GitHub Release;更新会下载并启动安装包。": "Bản phát hành GitHub chưa được kiểm tra; cập nhật các bản tải xuống và khởi chạy trình cài đặt.", + "尚未生成诊断报告。": "Chưa có báo cáo chẩn đoán nào được tạo.", + "尚未运行安装包更新。": "Bản cập nhật trình cài đặt chưa chạy.", + "尚未运行历史会话修复。": "Sửa chữa phiên lịch sử chưa chạy.", + "尚未运行插件市场修复。": "Sửa chữa thị trường plugin chưa chạy.", + "尚未检查官方远端插件缓存。": "Bộ đệm plugin từ xa chính thức chưa được kiểm tra.", + "正在检查内置官方远端插件缓存…": "Đang kiểm tra bộ nhớ đệm của plugin từ xa chính thức được nhúng…", + "正在释放内置远端插件快照…": "Đang trích xuất ảnh chụp nhanh plugin từ xa được nhúng…", + "正在注册官方远端插件市场…": "Đăng ký thị trường plugin từ xa chính thức…", + "正在刷新官方远端插件缓存状态…": "Đang làm mới trạng thái bộ đệm của plugin từ xa chính thức…", + "正在处理…": "Xử lý…", + "工具与插件": "Công cụ & plugin", + "已切回官方登录;Codex增强已设为兼容增强。": "Chuyển về đăng nhập chính thức; Các cải tiến của Codex được đặt thành tương thích.", + "已切换到纯 API;Codex增强已设为完整增强。": "Đã chuyển sang API thuần túy; Các cải tiến của Codex được đặt ở mức đầy đủ.", + "已切换到聚合供应商;真实对话会按所选策略轮转成员。": "Chuyển sang nhà cung cấp tổng hợp; các cuộc trò chuyện thực sự xoay vòng thành viên theo chiến lược đã chọn.", + "已刷新 Codex 应用、入口和 Watcher 状态。": "Ứng dụng Codex, điểm truy cập và trạng thái người theo dõi được làm mới.", + "已受理": "Đã chấp nhận", + "已安装": "Đã cài đặt", + "已归档": "Đã lưu trữ", + "已打开聚合供应商详情;请先添加或完善至少 1 个普通 API 供应商的 Base URL / Key,再勾选为成员。": "Đã mở chi tiết nhà cung cấp tổng hợp; trước tiên hãy thêm hoặc hoàn thành URL cơ sở/Khóa của ít nhất 1 nhà cung cấp API thông thường, sau đó chọn nhà cung cấp đó làm thành viên.", + "已找到": "Thành lập", + "已按此供应商使用官方登录,并混入 API Key;Codex增强已设为兼容增强。": "Sử dụng thông tin đăng nhập chính thức cho nhà cung cấp này có kèm theo Khóa API; Các cải tiến của Codex được đặt thành tương thích.", + "已按此供应商切回官方登录;Codex增强已设为兼容增强。": "Chuyển về đăng nhập chính thức cho nhà cung cấp này; Các cải tiến của Codex được đặt thành tương thích.", + "已按此供应商切换到纯 API;Codex增强已设为完整增强。": "Đã chuyển sang API thuần túy cho nhà cung cấp này; Các cải tiến của Codex được đặt ở mức đầy đủ.", + "已检测": "Đã phát hiện", + "已注册": "Đăng ký", + "已缓存未注册": "Đã lưu vào bộ nhớ đệm, chưa được đăng ký", + "已清除保存路径,后续启动会回到自动探测。": "Đã xóa đường dẫn đã lưu; các lần phóng trong tương lai sẽ quay trở lại chế độ tự động phát hiện.", + "已登录": "Đã đăng nhập", + "已禁用": "Tàn tật", + "已选择": "Đã chọn", + "市场状态": "trạng thái thị trường", + "市场脚本": "Tập lệnh Marketplace", + "序列化字段": "Các trường được tuần tự hóa", + "应用路径已保存,之后启动会自动复用。": "Đã lưu đường dẫn ứng dụng; những lần ra mắt trong tương lai sẽ tự động tái sử dụng nó.", + "应用路径留空时使用已保存路径;没有保存路径时使用自动探测": "Khi đường dẫn ứng dụng trống, đường dẫn đã lưu sẽ được sử dụng; không có đường dẫn đã lưu, tính năng tự động phát hiện sẽ được sử dụng", + "应用路径覆盖": "Ghi đè đường dẫn ứng dụng", + "开启": "TRÊN", + "开启后,通过 Codex++ 启动 Codex 前自动整理一次旧对话的归属标记。": "Khi bật, hãy dọn dẹp các thẻ quyền sở hữu của các cuộc hội thoại cũ một lần trước khi khởi chạy Codex thông qua Codex++.", + "强制中文界面": "Buộc giao diện người dùng Trung Quốc", + "强制启用 Codex App 内置 zh-CN 语言包,避免 Statsig/VPN 不通时回退英文。需重启 Codex 才能完整生效。": "Buộc gói ngôn ngữ zh-CN tích hợp của ứng dụng Codex tránh chuyển về tiếng Anh khi không thể truy cập Statsig/VPN. Yêu cầu khởi động lại Codex để áp dụng đầy đủ.", + "当前": "Hiện hành", + "当前 provider": "nhà cung cấp hiện tại", + "当前不会写入 Codex config.toml / auth.json。打开供应商配置总开关后再切换。": "Codex config.toml / auth.json sẽ không được viết ngay bây giờ. Bật công tắc chính cấu hình nhà cung cấp trước khi chuyển đổi.", + "当前为": "Hiện nay", + "当前为兼容增强模式,插件市场解锁不会启用;其他页面功能仍可用。": "Hiện đang ở chế độ nâng cao tương thích; tính năng mở khóa thị trường plugin chưa được bật nhưng các tính năng khác của trang vẫn hoạt động.", + "当前会话": "Phiên hiện tại", + "当前使用中:打开时从 ~/.codex/auth.json 回填,保存后会作为此供应商 auth 存档": "Hiện đang được sử dụng: được chèn lấp từ ~/.codex/auth.json khi mở; lưu trữ nó dưới dạng kho lưu trữ xác thực của nhà cung cấp này", + "当前供应商 config.toml 里没有可提取的通用配置。": "config.toml của nhà cung cấp hiện tại không có cấu hình chung có thể trích xuất được.", + "当前供应商切换后会写入的预览;上下文开关变化会立即反映": "Xem trước những gì nhà cung cấp hiện tại viết sau khi chuyển đổi; chuyển đổi bối cảnh được phản ánh ngay lập tức", + "当前供应商还没有完整 config.toml / API Key 存档。": "Nhà cung cấp hiện tại chưa có kho lưu trữ config.toml/API Key hoàn chỉnh.", + "当前共有": "Hiện tại có", + "当前未登录官方账号,也未配置混入 API 的 Base URL / Key。": "Chưa đăng nhập vào tài khoản chính thức và không có URL/Khóa cơ sở API hỗn hợp được định cấu hình.", + "当前未登录官方账号;切到官方登录模式后仍需要先在 Codex/ChatGPT 登录。": "Chưa đăng nhập vào tài khoản chính thức; sau khi chuyển sang chế độ đăng nhập chính thức bạn vẫn cần đăng nhập qua Codex/ChatGPT trước.", + "当前未登录官方账号;官方登录混入 API Key 需要先登录官方账号。": "Chưa đăng nhập vào tài khoản chính thức; đăng nhập chính thức bằng Khóa API hỗn hợp yêu cầu đăng nhập vào tài khoản chính thức trước.", + "当前正在使用": "Hiện đang được sử dụng", + "当前识别": "Hiện đã phát hiện", + "当前还没有填写混入 API 的 Base URL / Key。": "URL/Khóa cơ sở API hỗn hợp chưa được điền vào.", + "当前进程": "Quá trình hiện tại", + "当前配置 provider": "Nhà cung cấp được cấu hình hiện tại", + "快捷方式修复": "Sửa chữa lối tắt", + "快捷方式写入系统实际桌面位置,不使用写死桌面路径": "Các phím tắt được ghi vào vị trí màn hình thực tế của hệ thống, không phải đường dẫn màn hình được mã hóa cứng", + "快速启动": "Khởi động nhanh", + "总权重": "Tổng trọng lượng", + "成员供应商": "Nhà cung cấp thành viên", + "成员数量": "Số thành viên", + "或": "hoặc", + "手动": "Thủ công", + "手动启动": "Khởi chạy thủ công", + "打开": "Mở", + "打开 JOJO Code": "Mở mã JOJO", + "打开关于": "Mở Giới thiệu", + "打开链接": "Mở liên kết", + "打开项目主页": "Mở trang chủ dự án", + "扩展项": "lối vào", + "批量删除会话": "Xóa phiên hàng loạt", + "把 Codex++ 菜单插入 Codex 顶部原生菜单栏。": "Chèn menu Codex++ vào thanh menu gốc trên cùng của Codex.", + "把主对话和输入框限制到固定最大宽度,适合大屏阅读。": "Giới hạn cuộc trò chuyện chính và hộp nhập liệu ở độ rộng tối đa cố định, phù hợp để đọc trên màn hình lớn.", + "把会话移动到普通对话或其他本地项目。": "Chuyển phiên sang cuộc trò chuyện thông thường hoặc dự án địa phương khác.", + "投稿": "Nộp", + "拖动排序": "Kéo để sắp xếp lại", + "按对话轮转": "Vòng tròn cho mỗi cuộc trò chuyện", + "按对话轮转会让同一对话尽量保持固定成员,降低上下文漂移。": "Vòng tròn trên mỗi cuộc trò chuyện sẽ giữ cùng một cuộc trò chuyện với một thành viên cố định nếu có thể, giúp giảm tình trạng lệch ngữ cảnh.", + "按成员权重分配请求,权重越高承担越多。": "Phân phối yêu cầu theo trọng lượng thành viên; trọng lượng cao hơn mất nhiều hơn.", + "按成员顺序请求,失败后切到下一个供应商。": "Yêu cầu các thành viên theo thứ tự, chuyển sang nhà cung cấp tiếp theo nếu không thành công.", + "按更新时间倒序显示": "Sắp xếp theo thời gian cập nhật, mới nhất xếp trước", + "按请求轮转": "Vòng tròn theo yêu cầu", + "按请求轮转会逐请求切换成员,适合供应商能力接近的场景。": "Theo yêu cầu, chuyển đổi vòng tròn các thành viên theo yêu cầu, phù hợp với các nhà cung cấp có khả năng tương tự.", + "接入模式": "Chế độ truy cập", + "推荐内容": "Khuyến nghị", + "提取当前供应商配置": "Trích xuất cấu hình nhà cung cấp hiện tại", + "插件": "Plugin", + "插件列表全量展示": "Hiển thị danh sách plugin đầy đủ", + "插件市场修复": "Sửa chữa thị trường plugin", + "插件市场修复失败,请查看错误提示后重试。": "Sửa chữa thị trường plugin không thành công. Kiểm tra thông báo lỗi và thử lại.", + "插件市场修复进度": "Tiến trình sửa chữa thị trường plugin", + "插件市场解锁": "Mở khóa thị trường plugin", + "官方远端插件缓存": "Bộ đệm plugin từ xa chính thức", + "官方远端插件缓存进度": "Tiến trình bộ nhớ đệm plugin từ xa chính thức", + "官方远端插件缓存修复失败,请查看错误提示后重试。": "Sửa chữa bộ nhớ đệm plugin từ xa chính thức không thành công. Kiểm tra thông báo lỗi và thử lại.", + "使用 Codex++ 内置快照补齐远端插件,API 模式也可显示和安装 Product Design 插件。": "Sử dụng ảnh chụp nhanh Codex++ được nhúng để thêm plugin từ xa, nhờ đó chế độ API cũng có thể hiển thị và cài đặt plugin Thiết kế Sản phẩm.", + "搜索供应商…": "Tìm kiếm nhà cung cấp…", + "数据库": "Cơ sở dữ liệu", + "新增": "Thêm vào", + "新建供应商需要先保存到列表": "Nhà cung cấp mới phải được lưu vào danh sách trước", + "新机器没有本地插件市场时,可从 openai/plugins 初始化到当前 CODEX_HOME。": "Trên máy mới không có thị trường plugin cục bộ, hãy khởi tạo nó thành CODEX_HOME hiện tại từ openai/plugin.", + "新窗口": "Cửa sổ mới", + "日志已刷新": "Đã làm mới nhật ký", + "日志已复制。": "Đã sao chép nhật ký.", + "时间": "Thời gian", + "释放并注册内置缓存": "Trích xuất và đăng ký bộ nhớ đệm nhúng", + "显示服务模式切换按钮;Fast 仅支持 gpt-5.4 / gpt-5.5,其他模型按 Standard 发送。": "Hiển thị nút chuyển đổi cấp dịch vụ; Fast chỉ hỗ trợ gpt-5.4 / gpt-5.5, các mẫu khác được gửi dưới dạng Tiêu chuẩn.", + "普通推荐": "Khuyến nghị thường xuyên", + "暂无": "Không có", + "暂无启动状态。": "Chưa có trạng thái khởi chạy.", + "暂无描述。": "Không có mô tả.", + "暂无日志。": "Không có nhật ký.", + "暂无普通推荐。": "Không có khuyến nghị thường xuyên.", + "暂无状态文件": "Không có tập tin trạng thái", + "暂无赞助商推荐。": "Không có đề xuất nhà tài trợ.", + "暂无项目。": "Không có dự án.", + "更多选项": "Nhiều lựa chọn hơn", + "更新": "Cập nhật", + "更新安装": "Cập nhật cài đặt", + "最新版本": "Phiên bản mới nhất", + "最近启动": "Ra mắt gần đây", + "最近打开": "Mới mở gần đây", + "最近日志": "Nhật ký gần đây", + "未发现可导入供应商": "Không tìm thấy nhà cung cấp có thể nhập khẩu nào", + "未发现用户脚本。": "Không tìm thấy tập lệnh người dùng.", + "未命名会话": "Phiên không có tiêu đề", + "未命名供应商": "Nhà cung cấp không có tiêu đề", + "未命名聚合供应商": "Nhà cung cấp tổng hợp không có tiêu đề", + "未填写": "Không điền vào", + "未填写 URL": "Không có URL nào được điền vào", + "未安装": "Chưa được cài đặt", + "未实现": "Chưa được triển khai", + "未归档": "Chưa được lưu trữ", + "未检查": "Chưa được kiểm tra", + "未检测到": "Không được phát hiện", + "未检测到 Codex 应用版本。": "Phiên bản ứng dụng Codex không được phát hiện.", + "未发现缓存": "Không tìm thấy bộ đệm", + "未发现本地缓存;点击按钮会从 Codex++ 内置快照释放并注册,无需官方账号预缓存。": "Không tìm thấy bộ đệm cục bộ. Nhấp vào nút để trích xuất và đăng ký ảnh chụp nhanh Codex++ được nhúng; không yêu cầu bộ nhớ đệm trước của tài khoản chính thức.", + "未知": "Không xác định", + "未知作者": "Tác giả không xác định", + "未知来源": "Nguồn không xác định", + "未知版本": "Phiên bản không xác định", + "未记录路径": "Không có đường dẫn nào được ghi lại", + "未记录项目路径": "Không có đường dẫn dự án nào được ghi lại", + "未读取到本地会话,或当前 SQLite 会话库不存在。": "Không có phiên cục bộ nào được đọc hoặc cơ sở dữ liệu phiên SQLite hiện tại không tồn tại.", + "本地 Codex 增强、管理工具和安装包维护": "Cải tiến Codex cục bộ, công cụ quản lý và bảo trì gói cài đặt", + "本地会话": "Phiên cục bộ", + "本地整体": "Tổng thể địa phương", + "本地脚本": "Tập lệnh cục bộ", + "权重": "Cân nặng", + "权重轮转": "Vòng tròn có trọng số", + "权重轮转会读取每个成员的权重值,权重越高的成员获得更多请求。": "Vòng tròn có trọng số sẽ đọc cân nặng của từng thành viên; thành viên có trọng lượng cao hơn nhận được nhiều yêu cầu hơn.", + "查看、删除和修复 Codex 本地会话": "Xem, xóa và sửa chữa các phiên cục bộ Codex", + "检查": "Kiểm tra", + "检查与修复": "Kiểm tra & sửa chữa", + "检查入口、Codex 应用和 Watcher 状态": "Kiểm tra điểm vào, ứng dụng Codex và trạng thái người theo dõi", + "检查配置、模型列表和一次真实请求,定位供应商不可用原因。": "Kiểm tra cấu hình, danh sách model và một yêu cầu thực tế để tìm hiểu lý do tại sao không có nhà cung cấp.", + "检查完成": "Kiểm tra hoàn tất", + "检查更新": "Kiểm tra các bản cập nhật", + "检查问题、启动与快速修复": "Kiểm tra sự cố, khởi chạy và khắc phục nhanh", + "诊断供应商": "Nhà cung cấp chẩn đoán", + "检测": "Phát hiện", + "检测到 OPENAI 环境变量": "Đã phát hiện các biến môi trường OPENAI", + "检测到来自网页的供应商配置导入请求,确认后会写入本机 Codex++ 管理工具。": "Đã phát hiện yêu cầu nhập cấu hình nhà cung cấp từ web; xác nhận ghi nó vào trình quản lý Codex++ của máy này.", + "概览": "Tổng quan", + "概览只展示关键问题,具体配置在对应页面处理": "Phần tổng quan chỉ hiển thị các vấn đề chính; cấu hình cụ thể được xử lý trên các trang có liên quan", + "概览已检查": "Đã kiểm tra tổng quan", + "模型列表": "Danh sách người mẫu", + "模型名称": "Tên mẫu", + "模型白名单解锁": "Mở khóa danh sách trắng mô hình", + "模式": "Cách thức", + "模式。": "cách thức.", + "正在下载安装包…": "Đang tải xuống trình cài đặt…", + "正在下载插件市场快照…": "Đang tải xuống ảnh chụp nhanh thị trường plugin…", + "正在修复…": "Đang sửa chữa…", + "正在修复历史会话": "Sửa chữa các phiên lịch sử", + "正在准备安装包下载…": "Đang chuẩn bị tải xuống trình cài đặt…", + "正在启动安装包…": "Đang khởi chạy trình cài đặt…", + "正在写入 Codex 配置…": "Đang viết cấu hình Codex…", + "正在写入修复与备份…": "Viết sửa chữa và sao lưu…", + "正在删除…": "Đang xóa…", + "正在扫描历史会话与索引…": "Đang quét các phiên và chỉ mục lịch sử…", + "正在检查会话 provider 标记…": "Đang kiểm tra thẻ nhà cung cấp phiên…", + "正在检查本地插件市场…": "Đang kiểm tra thị trường plugin địa phương…", + "正在获取 GitHub Release 信息…": "Đang tìm nạp thông tin phát hành GitHub…", + "正在解压并校验插件文件…": "Đang trích xuất và xác minh các tệp plugin…", + "正在连接 openai/plugins…": "Đang kết nối với openai/plugin…", + "正常": "ĐƯỢC RỒI", + "此上游会通过本地 127.0.0.1:57321 转成 Responses API,需要从 Codex++ 启动 Codex。": "Luồng ngược dòng này được chuyển đổi thành API phản hồi thông qua 127.0.0.1:57321 cục bộ, yêu cầu khởi chạy Codex từ Codex++.", + "此供应商会保留官方登录模式,并把请求混入当前 API Key;Codex增强仍使用兼容模式。": "Nhà cung cấp này giữ chế độ đăng nhập chính thức và trộn Khóa API hiện tại vào các yêu cầu; Các cải tiến của Codex vẫn ở chế độ tương thích.", + "此供应商会切回官方登录模式,使用 ChatGPT 官方账号,不写入 API Key。": "Nhà cung cấp này chuyển về chế độ đăng nhập chính thức bằng tài khoản ChatGPT chính thức mà không cần viết Khóa API.", + "此供应商会同时写入 config.toml 和 auth.json;API Key 也会注入到 provider bearer token。": "Nhà cung cấp này viết cả config.toml và auth.json; Khóa API cũng được đưa vào mã thông báo mang của nhà cung cấp.", + "每次请求按成员顺序切换,适合均匀摊请求量。": "Chuyển đổi thành viên theo thứ tự trên mỗi yêu cầu, tốt cho việc phân bổ đều khối lượng yêu cầu.", + "每行一个参数,例如 --force_high_performance_gpu。不需要填写 open 或 --args。": "Một đối số trên mỗi dòng, ví dụ: --force_high_performance_gpu. Không cần bao gồm open hoặc --args.", + "每行一个模型;上下文窗口可填": "Một mô hình trên mỗi dòng; cửa sổ ngữ cảnh có thể", + "没有匹配「": "Không có nhà cung cấp nào phù hợp với “", + "浅色": "Ánh sáng", + "测试模型": "Mô hình thử nghiệm", + "消息": "Tin nhắn", + "深色": "Tối tăm", + "混入 API": "API hỗn hợp", + "混入 API KEY": "Trộn vào API KEY", + "混入 API Key": "Kết hợp khóa API", + "添加供应商": "Thêm nhà cung cấp", + "添加模型": "Thêm mô hình", + "添加聚合供应商": "Thêm nhà cung cấp tổng hợp", + "清空选择": "Xóa lựa chọn", + "清除保存路径": "Xóa đường dẫn đã lưu", + "点击刷新会话读取本地数据库": "Bấm vào Làm mới phiên để đọc cơ sở dữ liệu cục bộ", + "点击刷新市场加载远程脚本。": "Nhấp vào Làm mới Marketplace để tải tập lệnh từ xa.", + "版本信息、项目链接、GitHub Release 更新、日志与诊断": "Thông tin phiên bản, liên kết dự án, Cập nhật phát hành GitHub, nhật ký và chẩn đoán", + "状态": "Trạng thái", + "独立管理 Codex 的 MCP、Skills、Plugins;切换任意供应商都会带上。": "Quản lý MCP, Kỹ năng và Plugin của Codex một cách độc lập; họ sẽ chuyển tiếp bất cứ khi nào bạn chuyển đổi nhà cung cấp.", + "独立管理 MCP、Skills、Plugins": "Quản lý độc lập MCP, Kỹ năng và Plugin", + "环境变量": "Biến môi trường", + "环境变量检测": "Phát hiện biến môi trường", + "环境变量清理": "Dọn dẹp biến môi trường", + "用户": "người dùng", + "用户环境": "Môi trường người dùng", + "界面主题": "chủ đề giao diện người dùng", + "留空不改写,例如 160000": "Để trống để giữ nguyên, ví dụ: 160000", + "留空不改写,例如 200000": "Để trống để giữ nguyên, ví dụ: 200000", + "留空使用默认值": "Để trống để sử dụng mặc định", + "登录状态": "Trạng thái đăng nhập", + "确认删除": "Xác nhận xóa", + "确认导入": "Xác nhận nhập", + "禁用": "Vô hiệu hóa", + "禁用此扩展项": "Vô hiệu hóa mục này", + "移除 watcher": "Xóa người theo dõi", + "移除最近记录": "Xóa khỏi gần đây", + "立刻修复历史会话": "Sửa chữa các phiên lịch sử ngay bây giờ", + "第三方": "Bên thứ ba", + "策略": "Chiến lược", + "管理 API 供应商、协议、Key 与配置文件": "Quản lý nhà cung cấp API, giao thức, khóa và tệp cấu hình", + "管理 Codex SSH 项目并加入 Zed workspace": "Quản lý các dự án Codex SSH và thêm chúng vào không gian làm việc của Zed", + "管理工具入口": "Điểm vào của người quản lý", + "管理控制台": "Bảng điều khiển quản lý", + "管理控制台入口": "Điểm vào bảng điều khiển quản lý", + "类型": "Kiểu", + "粘贴修复": "Dán sửa lỗi", + "索引": "chỉ mục", + "纯 API": "API thuần túy", + "纯 API 就绪:会同时写入 config.toml 和 auth.json。": "API thuần túy đã sẵn sàng: ghi cả config.toml và auth.json.", + "纯 API 模式": "Chế độ API thuần túy", + "纯 API 配置未完整写入:请检查此供应商是否有 OPENAI_API_KEY,且 config.toml 是否包含 model_provider / provider / base_url。": "Cấu hình API thuần túy chưa đầy đủ: hãy kiểm tra xem nhà cung cấp này có OPENAI_API_KEY hay không và config.toml có bao gồm model_provider/nhà cung cấp/base_url hay không.", + "维护 Codex++ 自己的远程项目最近列表。": "Duy trì danh sách các dự án từ xa gần đây của Codex++.", + "编辑": "Biên tập", + "编辑后保存列表,再切换模式时会使用新配置": "Sau khi chỉnh sửa, lưu danh sách; cấu hình mới sẽ được sử dụng vào lần tiếp theo bạn chuyển chế độ", + "编辑扩展项": "Chỉnh sửa mục nhập", + "缺失": "Mất tích", + "缺少 Codex++ 静默启动快捷方式时可在安装维护页修复。": "Khi thiếu phím tắt khởi chạy im lặng Codex++, hãy sửa nó trên trang Cài đặt & Bảo trì.", + "缺少管理工具快捷方式时可在安装维护页修复。": "Khi thiếu phím tắt trình quản lý, hãy sửa nó trên trang Cài đặt & Bảo trì.", + "聚合": "Tổng hợp", + "聚合/中转": "Tổng hợp / chuyển tiếp", + "聚合供应商": "Nhà cung cấp tổng hợp", + "聚合供应商会在真实对话中轮转成员,请测试成员供应商": "Các nhà cung cấp tổng hợp luân chuyển thành viên trong các cuộc trò chuyện thực tế; thay vào đó hãy kiểm tra các nhà cung cấp thành viên", + "聚合供应商只保存成员和策略配置,成员来自已有 API 供应商;切为当前后会通过本地协议代理轮转请求。": "Các nhà cung cấp tổng hợp chỉ lưu trữ cấu hình chiến lược và thành viên được lấy từ các nhà cung cấp API hiện có; sau khi hoạt động, các yêu cầu sẽ chuyển qua proxy giao thức cục bộ.", + "聚合供应商至少需要勾选 1 个已填写 Base URL / Key 的 API 供应商。": "Một nhà cung cấp tổng hợp cần có ít nhất 1 nhà cung cấp API được chọn có điền URL/Khóa cơ sở.", + "聚合策略": "Chiến lược tổng hợp", + "聚合配置只引用已有供应商,不复制 Key 和配置文件": "Cấu hình tổng hợp chỉ tham chiếu các nhà cung cấp hiện có; nó không sao chép khóa hoặc tập tin cấu hình", + "脚本市场": "Chợ kịch bản", + "自动接管": "Tự động tiếp quản", + "覆盖图片": "Hình ảnh lớp phủ", + "背景适配方式": "Chế độ phù hợp với nền", + "填充": "Đổ đầy", + "适应": "Phù hợp", + "拉伸": "Kéo dài", + "平铺": "Ngói", + "居中": "Trung tâm", + "记录最近打开": "Bản ghi được mở gần đây", + "设为当前": "Đặt như hiện tại", + "设置": "Cài đặt", + "设置保存": "Đã lưu cài đặt", + "设置已加载": "Đã tải cài đặt", + "设置重置": "Đặt lại cài đặt", + "诊断已生成": "Đã tạo chẩn đoán", + "诊断报告": "Báo cáo chẩn đoán", + "诊断报告已复制。": "Đã sao chép báo cáo chẩn đoán.", + "点击后会打开诊断弹框,按步骤检查供应商。": "Nhấp để mở hộp thoại chẩn đoán và kiểm tra nhà cung cấp từng bước.", + "正在诊断供应商,请稍候。": "Nhà cung cấp dịch vụ chẩn đoán, vui lòng đợi.", + "诊断已完成。": "Chẩn đoán hoàn tất.", + "诊断中": "Chẩn đoán", + "异常": "Lỗi", + "完成": "Hoàn thành", + "配置完整性": "Tính toàn vẹn của cấu hình", + "等待检查 Base URL / API Key。": "Đang chờ kiểm tra URL cơ sở/Khóa API.", + "等待检查 /v1/models。": "Đang chờ kiểm tra /v1/models.", + "真实请求": "Yêu cầu thực sự", + "等待发送一次测试请求。": "Đang chờ gửi một yêu cầu kiểm tra.", + "处理建议": "Sự giới thiệu", + "等待生成建议。": "Đang chờ để tạo đề xuất.", + "正在检查配置完整性…": "Đang kiểm tra tính toàn vẹn của cấu hình…", + "该步骤未执行。": "Bước này không được chạy.", + "请先填写或选择应用路径。": "Trước tiên hãy điền hoặc chọn đường dẫn ứng dụng.", + "请先选择要删除的会话。": "Chọn các phiên để xóa đầu tiên.", + "读取 Codex 本地 SQLite 会话库,会删除数据库记录和对应 rollout 文件": "Đọc cơ sở dữ liệu phiên SQLite cục bộ của Codex; xóa các bản ghi cơ sở dữ liệu và các tập tin triển khai tương ứng", + "读取 cc-switch 供应商失败。": "Không đọc được nhà cung cấp cc-switch.", + "读取 ~/.cc-switch/cc-switch.db": "Đọc ~/.cc-switch/cc-switch.db", + "调用失败": "Cuộc gọi không thành công", + "资源": "Tài sản", + "赞助商推荐": "Khuyến nghị của nhà tài trợ", + "赞助商推荐与普通推荐": "Nhà tài trợ và khuyến nghị thường xuyên", + "输入中转服务的 API Key": "Nhập Khóa API dịch vụ chuyển tiếp", + "运行中": "Đang chạy", + "返回列表": "Quay lại danh sách", + "这些变量可能覆盖当前供应商写入的 config.toml / auth.json;CODEX_HOME 不会被清理。": "Các biến này có thể ghi đè config.toml/auth.json do nhà cung cấp hiện tại viết; CODEX_HOME sẽ không được dọn sạch.", + "进入插件页后自动连续展开“更多”,尽量一次显示完整插件列表。": "Tự động tiếp tục mở rộng 'Thêm' trên trang plugin để hiển thị danh sách plugin đầy đủ cùng một lúc.", + "进度": "Tiến triển", + "远程 SSH 文件引用可直接用 Zed Remote Development 打开。": "Các tham chiếu tệp SSH từ xa có thể được mở trực tiếp bằng Zed Remote Development.", + "远程脚本": "Tập lệnh từ xa", + "适合官方登录或官方混入 API Key;保留会话删除、导出、项目移动和用户脚本,关闭插件市场相关增强。": "Phù hợp với đăng nhập chính thức hoặc Khóa API hỗn hợp chính thức; tiếp tục xóa phiên, xuất, di chuyển dự án và tập lệnh người dùng, đồng thời vô hiệu hóa các cải tiến liên quan đến thị trường plugin.", + "适合纯 API;启用插件市场、会话删除导出、项目移动等全部页面能力。": "Phù hợp với API thuần túy; cho phép tất cả các khả năng của trang bao gồm thị trường plugin, xóa/xuất phiên và di chuyển dự án.", + "选择 Codex 应用目录": "Chọn thư mục ứng dụng Codex", + "选择 Codex.exe": "Chọn Codex.exe", + "选择 Codex.exe 或 Codex.app": "Chọn Codex.exe hoặc Codex.app", + "选择 Codex.exe、Codex.app、app 目录或解包目录": "Chọn Codex.exe, Codex.app, thư mục ứng dụng hoặc thư mục đã giải nén", + "选择 png / jpg / webp / gif / bmp": "Chọn png/jpg/webp/gif/bmp", + "选择会话": "Chọn phiên", + "选择图片": "Chọn hình ảnh", + "选择已有供应商作为成员,保存后写入 settings payload": "Chọn các nhà cung cấp hiện có làm thành viên; lưu ghi chúng vào tải trọng cài đặt", + "选择应用目录": "Chọn thư mục ứng dụng", + "选择覆盖图片": "Chọn hình ảnh lớp phủ", + "通用配置文件": "Tệp cấu hình chung", + "配置": "Cấu hình", + "配置文件": "Tập tin cấu hình", + "配置模型": "Mô hình cấu hình", + "重启 Codex++": "Khởi động lại Codex++", + "重新安装": "Cài đặt lại", + "重新生成": "tái sinh", + "重置背景": "Đặt lại nền", + "静默启动入口": "Điểm khởi động im lặng", + "项目地址": "URL dự án", + "额外参数": "Đối số bổ sung", + "高级选项,默认关闭;当前实现不主动改写 Zed settings。": "Tùy chọn nâng cao, tắt theo mặc định; việc triển khai hiện tại không tích cực sửa đổi cài đặt Zed.", + "默认中转": "Rơle mặc định", + "默认关闭;开启后启动 Codex 时会自动保留官方 Computer Use 插件所需的 config.toml、bundled 插件和 notify 配置。": "Tắt theo mặc định; khi được bật, việc khởi chạy Codex sẽ tự động lưu giữ config.toml, các plugin đi kèm và cấu hình thông báo theo yêu cầu của plugin Sử dụng Máy tính chính thức.", + "默认启动 Codex 时使用的模型名,请勿带后缀;上下文窗口请在下方「模型列表」中按模型单独配置。": "Tên model được sử dụng theo mặc định khi khởi chạy Codex; không bao gồm một hậu tố. Định cấu hình cửa sổ ngữ cảnh cho mỗi mô hình trong “Danh sách mô hình” bên dưới.", + "默认关闭;无 VPN 时可开启,让 Statsig 初始化快速失败,减少启动时长。需重启 Codex 才生效。": "Tắt theo mặc định; kích hoạt nó mà không cần VPN để quá trình khởi tạo Statsig không thành công nhanh chóng và giảm thời gian khởi chạy. Yêu cầu khởi động lại Codex.", + "默认打开策略": "Chiến lược mở mặc định", + "(": " (", + ")": ")", + ",可以从通用配置文件或这里新增。": "; bạn có thể thêm chúng từ tệp cấu hình chung hoặc tại đây.", + ",留空表示使用 Codex 默认长度。": "; để trống để sử dụng độ dài mặc định của Codex.", + ";这些条目独立于供应商保存,会写入所有供应商切换后的 config.toml。": "; các mục này được lưu độc lập với nhà cung cấp và được ghi vào config.toml sau khi chuyển sang bất kỳ nhà cung cấp nào.", +}; + +export const VI_TEMPLATE: Record = { + "\n...以及另外 {0} 个会话": "...và {0} phiên khác", + "{0} · {1} 个成员": "{0} · {1} thành viên", + "{0} 个": "{0}", + "{0} 个 Codex++ 可识别项目,默认策略:{1}": "{0} (các) dự án được Codex++ công nhận, chiến lược mặc định: {1}", + "{0} 个供应商": "{0} nhà cung cấp", + "{0} 个供应商配置;可拖动排序,点编辑进入详情": "{0} (các) cấu hình nhà cung cấp; kéo để sắp xếp lại, bấm chỉnh sửa để biết chi tiết", + "{0} 个市场脚本,已安装 {1} 个,本地整体 {2}": "{0} (các) tập lệnh thị trường, {1} đã cài đặt, tổng thể cục bộ {2}", + "{0} 个项目": "{0} dự án", + "{0} 副本": "{0} bản sao", + "{0} 条": "{0} mặt hàng", + "供应商 {0}": "Nhà cung cấp {0}", + "供应商「{0}」缺少独立 config.toml,已停止切换,避免继续显示上一套配置文件。请先在该供应商详情里保存 config.toml。": "Nhà cung cấp “{0}” thiếu config.toml riêng nên quá trình chuyển đổi đã bị dừng để tránh hiển thị các tệp cấu hình trước đó. Trước tiên hãy lưu config.toml trong thông tin chi tiết của nhà cung cấp này.", + "删除会话“{0}”?此操作会删除本地数据库记录和 rollout 文件,并创建备份。": "Xóa phiên “{0}”? Thao tác này sẽ xóa bản ghi cơ sở dữ liệu cục bộ và tệp triển khai, đồng thời tạo bản sao lưu.", + "删除脚本“{0}”?此操作会移除本地脚本文件。": "Xóa tập lệnh “{0}”? Thao tác này sẽ xóa tệp tập lệnh cục bộ.", + "删除这些环境变量?\n\n{0}\n\n删除前会写入备份。": "Xóa các biến môi trường này?\n\n{0}\n\nMột bản sao lưu được viết trước khi xóa.", + "删除选中的 {0} 个会话?此操作会删除本地数据库记录和 rollout 文件,并为每个会话创建备份。\n\n{1}{2}": "Xóa {0} phiên đã chọn? Thao tác này sẽ xóa các bản ghi cơ sở dữ liệu cục bộ và các tệp triển khai, đồng thời tạo bản sao lưu cho mỗi phiên.\n\n{1}{2}", + "发现 {0} 个 Codex 供应商": "Đã tìm thấy {0} nhà cung cấp Codex", + "发现新版本 {0}": "Đã có phiên bản mới {0}", + "官方登录已就绪:{0},会混入当前 API Key。": "Đăng nhập chính thức đã sẵn sàng: {0}, Khóa API hiện tại sẽ được trộn vào.", + "官方账号已登录:{0}。": "Tài khoản chính thức đã đăng nhập: {0}.", + "已删除 {0} 个会话。": "Đã xóa {0} phiên.", + "已删除 {0} 个,失败 {1} 个:{2}": "Đã xóa {0}, không thành công {1}: {2}", + "已加载 {0} 条推荐": "Đã tải {0} đề xuất", + "已同步到 {0}:修复 {1} 个会话文件,更新 {2} 行索引{3}。": "Đã đồng bộ hóa với {0}: đã sửa chữa {1} tệp phiên, đã cập nhật {2} hàng chỉ mục{3}.", + "已安装 {0}": "Đã cài đặt {0}", + "已缓存 {0} 个插件 / {1} 个技能。": "Đã lưu vào bộ nhớ đệm {0} plugin / {1} kỹ năng.", + "已运行 {0} 分钟": "Chạy được {0} phút", + "已运行 {0} 小时 {1} 分钟": "Chạy trong {0} giờ {1} phút", + "市场 · {0}": "Chợ · {0}", + "当前版本 {0}": "Phiên bản hiện tại {0}", + "打开选择器失败:{0}": "Không mở được bộ chọn: {0}", + "正在同步到 {0}…": "Đang đồng bộ hóa với {0}…", + "清单更新时间:{0}": "Bản kê khai được cập nhật lúc: {0}", + "留空使用默认:{0}": "Để trống để sử dụng mặc định: {0}", + "聚合供应商 {0}": "Nhà cung cấp tổng hợp {0}", + "聚合供应商已配置为{0},包含 {1} 个成员;真实对话会走本地代理轮转。": "Nhà cung cấp tổng hợp được định cấu hình là {0} với {1} thành viên; các cuộc hội thoại thực sự xoay vòng qua proxy cục bộ.", + "进程状态:{0}。是否要重新启动?": "Trạng thái xử lý: {0}. Khởi động lại?", + "选择会话 {0}": "Chọn phiên {0}", + "透明度 {0}%": "Độ mờ {0}%", + ",跳过 {0} 个占用文件": ", đã bỏ qua {0} tệp bị khóa", +}; + +export const VI_BACKEND: Record = { + "后端版本已读取。": "Đã tải phiên bản phụ trợ.", + "启动参数已读取。": "Khởi chạy các đối số đã được tải.", + "概览后台任务失败。": "Nhiệm vụ nền tổng quan không thành công.", + "概览已加载。": "Đã tải tổng quan.", + "启动任务已在后台开始,可稍后查看概览状态。": "Nhiệm vụ khởi động bắt đầu ở chế độ nền. Kiểm tra trạng thái tổng quan sau.", + "设置已加载。": "Đã tải cài đặt.", + "设置读取失败": "Không đọc được cài đặt", + "设置保存后重新读取失败": "Không thể tải lại cài đặt sau khi lưu", + "没有新的 cc-switch 供应商配置需要导入。": "Không có cấu hình nhà cung cấp cc-switch mới nào để nhập.", + "导入供应商配置后重新读取设置失败": "Không thể tải lại cài đặt sau khi nhập cấu hình nhà cung cấp", + "待确认供应商导入已读取。": "Đang chờ nhập nhà cung cấp.", + "没有待确认的供应商导入。": "Không có nhà cung cấp đang chờ nhập.", + "已取消供应商导入。": "Đã hủy nhập nhà cung cấp.", + "会话 ID 不能为空。": "ID phiên không được để trống.", + "推荐内容已加载。": "Đã tải đề xuất.", + "脚本市场已刷新。": "Thị trường tập lệnh được làm mới.", + "脚本 id 不能为空。": "ID tập lệnh không được để trống.", + "市场清单中未找到该脚本。": "Không tìm thấy tập lệnh trong bảng kê khai thị trường.", + "脚本已安装。": "Đã cài đặt tập lệnh.", + "脚本 key 不能为空。": "Khóa tập lệnh không được để trống.", + "脚本已启用。": "Đã bật tập lệnh.", + "脚本已禁用。": "Tập lệnh bị vô hiệu hóa.", + "脚本启停失败": "Không thể chuyển đổi tập lệnh", + "脚本已删除。": "Kịch bản đã bị xóa.", + "脚本删除失败": "Không xóa được tập lệnh", + "只允许打开 http 或 https 链接。": "Chỉ có thể mở liên kết http hoặc https.", + "已在系统浏览器打开链接。": "Liên kết được mở trong trình duyệt hệ thống.", + "安装入口": "Cài đặt điểm vào", + "卸载入口": "Gỡ cài đặt điểm vào", + "修复快捷方式": "Sửa chữa phím tắt", + "修复后重新读取设置失败": "Không thể tải lại cài đặt sau khi sửa chữa", + "插件市场需要初始化或注册。": "Thị trường plugin cần khởi tạo hoặc đăng ký.", + "插件市场已可用。": "Thị trường plugin có sẵn.", + "插件市场已从 openai/plugins 初始化并注册。": "Thị trường plugin được khởi tạo và đăng ký từ openai/plugin.", + "已注册本地插件市场。": "Đã đăng ký thị trường plugin địa phương.", + "插件市场已可用,无需修复。": "Thị trường plugin có sẵn; không cần sửa chữa.", + "官方远端插件缓存需要释放或注册。": "Bộ đệm plugin từ xa chính thức cần trích xuất hoặc đăng ký.", + "官方远端插件缓存已可用。": "Bộ đệm plugin từ xa chính thức có sẵn.", + "已释放并注册内置官方远端插件缓存。": "Đã trích xuất và đăng ký bộ nhớ đệm plugin từ xa chính thức được nhúng.", + "已注册官方远端插件缓存。": "Đã đăng ký bộ nhớ đệm plugin từ xa chính thức.", + "官方远端插件缓存已可用,无需修复。": "Bộ đệm plugin từ xa chính thức có sẵn; không cần sửa chữa.", + "发现可用更新。": "Cập nhật có sẵn.", + "当前已是最新版本。": "Đã có trên phiên bản mới nhất.", + "请先检查更新并选择可下载的 Release asset。": "Trước tiên hãy kiểm tra các bản cập nhật và chọn nội dung phát hành có thể tải xuống.", + "安装包已下载并启动,请按安装向导完成更新。": "Trình cài đặt đã được tải xuống và khởi chạy. Làm theo trình hướng dẫn thiết lập để hoàn tất cập nhật.", + "日志已读取。": "Đã tải nhật ký.", + "诊断报告已生成。": "Đã tạo báo cáo chẩn đoán.", + "设置已重置为默认值。": "Cài đặt lại về mặc định.", + "设置重置后重新读取失败": "Không thể tải lại cài đặt sau khi đặt lại", + "图片覆盖层设置已重置。": "Đặt lại cài đặt lớp phủ hình ảnh.", + "图片覆盖层重置后重新读取失败": "Không thể tải lại cài đặt sau khi đặt lại lớp phủ hình ảnh", + "已检测到 ChatGPT 登录状态。": "Đã phát hiện đăng nhập ChatGPT.", + "未检测到 ChatGPT 登录状态,请先在 Codex/ChatGPT 中正常登录。": "Không phát hiện đăng nhập ChatGPT. Vui lòng đăng nhập vào Codex/ChatGPT trước.", + "配置文件内容已读取。": "Đã tải nội dung tệp cấu hình.", + "未检测到会覆盖 Codex 供应商配置的 OPENAI 环境变量。": "Không tìm thấy biến môi trường OPENAI nào sẽ ghi đè cấu hình nhà cung cấp Codex.", + "检测到可能覆盖 Codex 供应商配置的 OPENAI 环境变量。": "Các biến môi trường OPENAI được phát hiện có thể ghi đè cấu hình nhà cung cấp Codex.", + "环境变量已按确认项删除;重新启动 Codex 后生效。": "Các biến môi trường đã bị xóa như đã được xác nhận. Khởi động lại Codex để các thay đổi có hiệu lực.", + "配置文件已保存。": "Đã lưu tập tin cấu hình.", + "供应商切换锁已损坏,请重启管理器后再试。": "Khóa chuyển đổi nhà cung cấp bị hỏng. Vui lòng khởi động lại trình quản lý và thử lại.", + "供应商已切换。": "Nhà cung cấp đã chuyển đổi.", + "当前供应商已不在配置列表中,已停止切换以避免覆盖用户改动。": "Nhà cung cấp hiện tại không còn trong danh sách cấu hình. Việc chuyển đổi đã dừng lại để tránh ghi đè những thay đổi của người dùng.", + "当前供应商配置已从 live 文件回填。": "Cấu hình nhà cung cấp hiện tại được chèn lấp từ các tệp trực tiếp.", + "工具与插件列表已读取。": "Đã tải danh sách công cụ và plugin.", + "通用配置已按兼容切换规则提取。": "Cấu hình chung được trích xuất bằng quy tắc chuyển đổi tương thích.", + "供应商配置总开关已关闭,未写入 config.toml / auth.json。": "Công tắc chính của cấu hình nhà cung cấp đang tắt. Không có gì được ghi vào config.toml/auth.json.", + "已按兼容切换规则切换供应商。": "Nhà cung cấp đã chuyển đổi bằng cách sử dụng các quy tắc tương thích.", + "未检测到 ChatGPT 登录状态": "Không phát hiện đăng nhập ChatGPT", + "未检测到 ChatGPT 登录状态,已停止写入中转配置。": "Không phát hiện đăng nhập ChatGPT. Cấu hình chuyển tiếp không được ghi.", + "中转配置已写入,密钥未在界面明文显示。": "Cấu hình chuyển tiếp được viết. Các khóa không được hiển thị dưới dạng văn bản gốc trong giao diện người dùng.", + "聚合供应商配置已写入,真实请求会由本地代理按策略轮转。": "Đã viết cấu hình nhà cung cấp tổng hợp. Các yêu cầu thực sự sẽ được proxy cục bộ luân chuyển theo chiến lược.", + "已清除 custom 中转 API 模式,并切换到官方 ChatGPT 登录模式。": "Đã xóa chế độ API chuyển tiếp tùy chỉnh. Đã chuyển sang chế độ đăng nhập ChatGPT chính thức.", + "纯 API 模式已写入:config.toml 已写入 custom provider,auth.json 已切换为当前供应商。": "Chế độ API thuần túy được viết: config.toml hiện có nhà cung cấp tùy chỉnh, auth.json đã chuyển sang nhà cung cấp hiện tại.", + "纯 API 配置写入后未检测到完整 custom provider,请检查 config.toml 和供应商 API Key。": "Sau khi viết cấu hình API thuần túy, không phát hiện thấy nhà cung cấp tùy chỉnh hoàn chỉnh nào. Vui lòng kiểm tra config.toml và Khóa API của nhà cung cấp.", + "供应商导入后重新读取设置失败": "Không thể tải lại cài đặt sau khi nhập nhà cung cấp", + "诊断日志已写入。": "Nhật ký chẩn đoán được viết.", + "未命名供应商": "Nhà cung cấp ẩn danh", + "响应内容为空": "Phản hồi trống", +}; + +export const VI_BACKEND_PATTERNS: Array<[RegExp, string]> = [ + [new RegExp("^启动静默入口失败:(.+)$"), "Không thể khởi chạy điểm vào im lặng: $1"], + [new RegExp("^无法启动 (.+?):(.+)$"), "Không thể khởi chạy $1: $2"], + [new RegExp("^设置已保存。(.*)$"), "Đã lưu cài đặt.$1"], + [new RegExp("^保存设置失败:(.+)$"), "Không lưu được cài đặt: $1"], + [new RegExp("^已读取 cc-switch Codex 供应商配置:(\\d+) 个。$"), "Đọc (các) cấu hình nhà cung cấp Codex $1 cc-switch."], + [new RegExp("^读取 cc-switch 供应商配置失败:(.+)$"), "Không đọc được cấu hình nhà cung cấp cc-switch: $1"], + [new RegExp("^已从 cc-switch 导入供应商配置:(\\d+) 个。$"), "Đã nhập (các) cấu hình nhà cung cấp cc-switch $1."], + [new RegExp("^保存 cc-switch 供应商配置失败:(.+)$"), "Không lưu được cấu hình nhà cung cấp cc-switch: $1"], + [new RegExp("^已导入供应商配置:(.+)。$"), "Cấu hình nhà cung cấp đã nhập: $1."], + [new RegExp("^供应商配置已存在:(.+)。$"), "Cấu hình nhà cung cấp đã tồn tại: $1."], + [new RegExp("^导入供应商配置失败:(.+)$"), "Không thể nhập cấu hình nhà cung cấp: $1"], + [new RegExp("^取消供应商导入失败:(.+)$"), "Không hủy được việc nhập nhà cung cấp: $1"], + [new RegExp("^已读取 (\\d+) 个本地会话。$"), "Đọc (các) phiên địa phương $1."], + [new RegExp("^读取部分本地会话失败:(.+)$"), "Không đọc được một số phiên địa phương: $1"], + [new RegExp("^已读取 (\\d+) 个 Zed 远程项目。$"), "Đọc (các) dự án từ xa $1 của Zed."], + [new RegExp("^读取 Zed 远程项目失败。$"), "Không đọc được các dự án từ xa của Zed."], + [new RegExp("^已在 Zed Remote 打开项目。$"), "Dự án đã mở trong Zed Remote."], + [new RegExp("^无法在 Zed Remote 打开项目。$"), "Không thể mở dự án trong Zed Remote."], + [new RegExp("^移除 Zed 远程项目失败。$"), "Không thể xóa dự án từ xa Zed."], + [new RegExp("^供应商已同步一次:(\\d+) 个会话文件,(\\d+) 行索引,跳过 (\\d+) 个占用文件。$"), "Nhà cung cấp đã đồng bộ hóa: $1 tệp phiên, $2 hàng chỉ mục, bỏ qua $3 tệp bị khóa."], + [new RegExp("^供应商同步失败:(.+)$"), "Đồng bộ hóa nhà cung cấp không thành công: $1"], + [new RegExp("^推荐内容加载失败:(.+)$"), "Không tải được đề xuất: $1"], + [new RegExp("^脚本市场加载失败:(.+)$"), "Không thể tải thị trường tập lệnh: $1"], + [new RegExp("^安装脚本失败:(.+)$"), "Không cài đặt được tập lệnh: $1"], + [new RegExp("^脚本启停失败:(.+)$"), "Không thể chuyển đổi tập lệnh: $1"], + [new RegExp("^脚本删除失败:(.+)$"), "Không xóa được tập lệnh: $1"], + [new RegExp("^打开链接失败:(.+)$"), "Không mở được liên kết: $1"], + [new RegExp("^插件市场修复失败:(.+)$"), "Sửa chữa thị trường plugin không thành công: $1"], + [new RegExp("^官方远端插件缓存修复失败:(.+)$"), "Sửa chữa bộ nhớ đệm plugin từ xa chính thức không thành công: $1"], + [new RegExp("^检查更新失败:(.+)$"), "Không kiểm tra được bản cập nhật: $1"], + [new RegExp("^安装更新失败:(.+)$"), "Không cài đặt được bản cập nhật: $1"], + [new RegExp("^安装 watcher 失败:(.+)$"), "Không cài đặt được trình theo dõi: $1"], + [new RegExp("^移除 watcher 失败:(.+)$"), "Không xóa được người theo dõi: $1"], + [new RegExp("^启用 watcher 失败:(.+)$"), "Không bật được trình theo dõi: $1"], + [new RegExp("^禁用 watcher 失败:(.+)$"), "Không tắt được trình theo dõi: $1"], + [new RegExp("^读取日志失败:(.+)$"), "Không đọc được nhật ký: $1"], + [new RegExp("^重置设置失败:(.+)$"), "Không thể đặt lại cài đặt: $1"], + [new RegExp("^重置图片覆盖层失败:(.+)$"), "Không thể đặt lại lớp phủ hình ảnh: $1"], + [new RegExp("^读取配置文件失败:(.+)$"), "Không đọc được tệp cấu hình: $1"], + [new RegExp("^删除环境变量失败:(.+)$"), "Không thể xóa các biến môi trường: $1"], + [new RegExp("^保存配置文件失败:(.+)$"), "Không lưu được tập tin cấu hình: $1"], + [new RegExp("^供应商切换失败:(.+)$"), "Chuyển đổi nhà cung cấp không thành công: $1"], + [new RegExp("^写入诊断日志失败:(.+)$"), "Không thể ghi nhật ký chẩn đoán: $1"], + [new RegExp("^回填当前供应商配置失败:(.+)$"), "Không thể chèn lấp cấu hình nhà cung cấp hiện tại: $1"], + [new RegExp("^读取工具与插件列表失败:(.+)$"), "Không đọc được danh sách công cụ và plugin: $1"], + [new RegExp("^读取 live 工具与插件失败:(.+)$"), "Không đọc được các công cụ và plugin trực tiếp: $1"], + [new RegExp("^保存工具与插件失败:(.+)$"), "Không lưu được công cụ và plugin: $1"], + [new RegExp("^读取 live config.toml 失败:(.+)$"), "Không thể đọc config.toml trực tiếp: $1"], + [new RegExp("^同步 live 工具与插件失败:(.+)$"), "Không thể đồng bộ hóa các công cụ và plugin trực tiếp: $1"], + [new RegExp("^创建 Codex 配置目录失败:(.+)$"), "Không tạo được thư mục cấu hình Codex: $1"], + [new RegExp("^写入 live config.toml 失败:(.+)$"), "Không thể ghi config.toml trực tiếp: $1"], + [new RegExp("^读取同步后的 live 工具与插件失败:(.+)$"), "Không thể đọc các công cụ và plugin trực tiếp sau khi đồng bộ hóa: $1"], + [new RegExp("^删除工具与插件失败:(.+)$"), "Không xóa được công cụ và plugin: $1"], + [new RegExp("^提取通用配置失败:(.+)$"), "Không trích xuất được cấu hình chung: $1"], + [new RegExp("^已向「(.+?)」用模型「(.+?)」发送 hi,HTTP (\\d+)。(.*)$"), "Đã gửi lời chào tới \"$1\" bằng mô hình \"$2\", HTTP $3.$4"], + [new RegExp("^测试「(.+?)」失败:(.+)$"), "Kiểm tra \"$1\" không thành công: $2"], + [new RegExp("^已从「(.+?)」获取 (\\d+) 个模型。$"), "Đã tìm nạp (các) mẫu $2 từ \"$1\"."], + [new RegExp("^从「(.+?)」获取模型失败:(.+)$"), "Không tìm nạp được mô hình từ \"$1\": $2"], + [new RegExp("^切换完整中转配置失败:(.+)$"), "Không thể chuyển đổi cấu hình chuyển tiếp đầy đủ: $1"], + [new RegExp("^写入中转配置失败:(.+)$"), "Không thể ghi cấu hình chuyển tiếp: $1"], + [new RegExp("^写入聚合供应商配置失败:(.+)$"), "Không thể ghi cấu hình nhà cung cấp tổng hợp: $1"], + [new RegExp("^切换纯 API 配置失败:(.+)$"), "Không thể chuyển sang cấu hình API thuần túy: $1"], + [new RegExp("^写入纯 API 模式失败:(.+)$"), "Không thể ghi chế độ API thuần túy: $1"], + [new RegExp("^清除中转配置失败:(.+)$"), "Không xóa được cấu hình chuyển tiếp: $1"], + [new RegExp("^未知配置文件类型:(.+)$"), "Loại tệp cấu hình không xác định: $1"], + [new RegExp("^启动系统浏览器失败:(.+)$"), "Không khởi chạy được trình duyệt hệ thống: $1"], + [new RegExp("^诊断报告序列化失败:(.+)$"), "Không thể tuần tự hóa báo cáo chẩn đoán: $1"], + [new RegExp("^响应:(.+)$"), "Phản hồi: $1"], +]; diff --git a/apps/codex-plus-manager/src/i18n.ts b/apps/codex-plus-manager/src/i18n.ts index b9efa2d76..0e1d70b30 100644 --- a/apps/codex-plus-manager/src/i18n.ts +++ b/apps/codex-plus-manager/src/i18n.ts @@ -2,75 +2,84 @@ // // The app is authored in Chinese. Every user-facing Chinese literal is wrapped // with `t("中文")` (plain strings) or `tf("前缀 {0}", [expr])` (interpolated -// strings) by tools/i18n-codemod.mjs. When the active language is English we -// look the source text up in the English dictionary; otherwise we return the -// original Chinese, so Chinese stays the zero-overhead default. +// strings). Dictionaries: i18n-en.ts, i18n-vi.ts. // -// Language is resolved once at module load. Many Chinese literals live in -// module-level constants (route tables, preset labels, …) that evaluate a -// single time at import, so a live in-place swap can't reach them. Switching -// language therefore persists the choice and reloads the webview, which is -// instant for a local Tauri window and guarantees every literal — module-level -// or render-level — re-evaluates under the new language. +// Language is resolved once at module load. Switching language persists and +// reloads the webview so module-level literals re-evaluate. import { EN_BACKEND, EN_BACKEND_PATTERNS, EN_PLAIN, EN_TEMPLATE } from "@/i18n-en"; +import { VI_BACKEND, VI_BACKEND_PATTERNS, VI_PLAIN, VI_TEMPLATE } from "@/i18n-vi"; -export type Language = "zh" | "en"; +export type Language = "zh" | "en" | "vi"; const STORAGE_KEY = "codex-plus-lang"; function resolveInitialLanguage(): Language { try { - return window.localStorage.getItem(STORAGE_KEY) === "en" ? "en" : "zh"; + const stored = window.localStorage.getItem(STORAGE_KEY); + if (stored === "en" || stored === "vi") return stored; } catch { - return "zh"; + // ignore } + return "zh"; } -// Resolved once per webview load. Do not mutate at runtime — use setLanguage, -// which persists and reloads so module-level literals pick up the change. const LANG: Language = resolveInitialLanguage(); export function getLanguage(): Language { return LANG; } -/** Translate a plain Chinese literal. Falls back to the source text. */ -export function t(zh: string): string { - if (LANG !== "en") return zh; - const plain = EN_PLAIN[zh] ?? EN_BACKEND[zh]; +function translatePlain(zh: string): string { + if (LANG === "zh") return zh; + const plain = LANG === "en" ? EN_PLAIN[zh] ?? EN_BACKEND[zh] : VI_PLAIN[zh] ?? VI_BACKEND[zh]; if (plain) return plain; - for (const [re, replacement] of EN_BACKEND_PATTERNS) { + const patterns = LANG === "en" ? EN_BACKEND_PATTERNS : VI_BACKEND_PATTERNS; + for (const [re, replacement] of patterns) { if (re.test(zh)) return zh.replace(re, replacement); } return zh; } -/** - * Translate an interpolated literal. `key` carries `{0}`,`{1}`… placeholders in - * the original (Chinese) order; `args` are the runtime values for each. In - * Chinese we substitute into the key itself; in English we substitute into the - * looked-up template (also falling back to the key). - */ +/** Translate a plain Chinese literal. Falls back to the source text. */ +export function t(zh: string): string { + return translatePlain(zh); +} + export function tf(key: string, args: Array): string { - const template = LANG === "en" ? EN_TEMPLATE[key] ?? key : key; + const dict = LANG === "en" ? EN_TEMPLATE : LANG === "vi" ? VI_TEMPLATE : null; + const template = dict ? dict[key] ?? key : key; return template.replace(/\{(\d+)\}/g, (match, index) => { const value = args[Number(index)]; return value === undefined || value === null ? match : String(value); }); } -/** Persist a new language and reload so every literal re-evaluates under it. */ export function setLanguage(language: Language): void { try { window.localStorage.setItem(STORAGE_KEY, language); } catch { - // Ignore storage failures; the reload below simply keeps the old value. + // ignore } window.location.reload(); } -/** Flip between Chinese and English. */ +const LANG_CYCLE: Language[] = ["zh", "vi", "en"]; + +/** Cycle UI language: 中文 → Tiếng Việt → English. */ +export function cycleLanguage(): void { + const idx = LANG_CYCLE.indexOf(LANG); + const next = LANG_CYCLE[(idx + 1) % LANG_CYCLE.length]; + setLanguage(next); +} + +/** @deprecated Use cycleLanguage() */ export function toggleLanguage(): void { - setLanguage(LANG === "en" ? "zh" : "en"); + cycleLanguage(); +} + +export function languageButtonTitle(): string { + if (LANG === "zh") return translatePlain("切换到越南语"); + if (LANG === "vi") return translatePlain("切换到英文"); + return translatePlain("切换到中文"); } diff --git a/apps/codex-plus-manager/src/i18n.ts.bak b/apps/codex-plus-manager/src/i18n.ts.bak new file mode 100644 index 000000000..b9efa2d76 --- /dev/null +++ b/apps/codex-plus-manager/src/i18n.ts.bak @@ -0,0 +1,76 @@ +// Lightweight source-text-keyed i18n for the Codex++ manager UI. +// +// The app is authored in Chinese. Every user-facing Chinese literal is wrapped +// with `t("中文")` (plain strings) or `tf("前缀 {0}", [expr])` (interpolated +// strings) by tools/i18n-codemod.mjs. When the active language is English we +// look the source text up in the English dictionary; otherwise we return the +// original Chinese, so Chinese stays the zero-overhead default. +// +// Language is resolved once at module load. Many Chinese literals live in +// module-level constants (route tables, preset labels, …) that evaluate a +// single time at import, so a live in-place swap can't reach them. Switching +// language therefore persists the choice and reloads the webview, which is +// instant for a local Tauri window and guarantees every literal — module-level +// or render-level — re-evaluates under the new language. + +import { EN_BACKEND, EN_BACKEND_PATTERNS, EN_PLAIN, EN_TEMPLATE } from "@/i18n-en"; + +export type Language = "zh" | "en"; + +const STORAGE_KEY = "codex-plus-lang"; + +function resolveInitialLanguage(): Language { + try { + return window.localStorage.getItem(STORAGE_KEY) === "en" ? "en" : "zh"; + } catch { + return "zh"; + } +} + +// Resolved once per webview load. Do not mutate at runtime — use setLanguage, +// which persists and reloads so module-level literals pick up the change. +const LANG: Language = resolveInitialLanguage(); + +export function getLanguage(): Language { + return LANG; +} + +/** Translate a plain Chinese literal. Falls back to the source text. */ +export function t(zh: string): string { + if (LANG !== "en") return zh; + const plain = EN_PLAIN[zh] ?? EN_BACKEND[zh]; + if (plain) return plain; + for (const [re, replacement] of EN_BACKEND_PATTERNS) { + if (re.test(zh)) return zh.replace(re, replacement); + } + return zh; +} + +/** + * Translate an interpolated literal. `key` carries `{0}`,`{1}`… placeholders in + * the original (Chinese) order; `args` are the runtime values for each. In + * Chinese we substitute into the key itself; in English we substitute into the + * looked-up template (also falling back to the key). + */ +export function tf(key: string, args: Array): string { + const template = LANG === "en" ? EN_TEMPLATE[key] ?? key : key; + return template.replace(/\{(\d+)\}/g, (match, index) => { + const value = args[Number(index)]; + return value === undefined || value === null ? match : String(value); + }); +} + +/** Persist a new language and reload so every literal re-evaluates under it. */ +export function setLanguage(language: Language): void { + try { + window.localStorage.setItem(STORAGE_KEY, language); + } catch { + // Ignore storage failures; the reload below simply keeps the old value. + } + window.location.reload(); +} + +/** Flip between Chinese and English. */ +export function toggleLanguage(): void { + setLanguage(LANG === "en" ? "zh" : "en"); +} diff --git a/apps/codex-plus-manager/src/model-windows.ts b/apps/codex-plus-manager/src/model-windows.ts index 3dd46a7cc..a2b82f367 100644 --- a/apps/codex-plus-manager/src/model-windows.ts +++ b/apps/codex-plus-manager/src/model-windows.ts @@ -90,7 +90,7 @@ export function buildModelWindows(modelList: string, modelWindowsText: string): if (models.length !== windows.length) { return { ok: false, - error: `模型名称有 ${models.length} 行,上下文窗口有 ${windows.length} 行,请保持行数一致。`, + error: `Danh sách model có ${models.length} dòng, cửa sổ ngữ cảnh có ${windows.length} dòng; hãy giữ số dòng khớp nhau.`, }; } return { ok: true, modelWindows: modelWindowsTextToMap(modelList, modelWindowsText) }; diff --git a/assets/inject/renderer-inject.js b/assets/inject/renderer-inject.js index 4bd8a8e5a..c0844665d 100644 --- a/assets/inject/renderer-inject.js +++ b/assets/inject/renderer-inject.js @@ -1031,7 +1031,7 @@ .codex-plus-toggle:disabled { cursor: not-allowed; opacity: .55; } .codex-plus-toggle[data-relay-unneeded="true"] { width: 72px; cursor: default; background: rgba(16,163,127,.16); color: #6ee7b7; } .codex-plus-toggle[data-relay-unneeded="true"] span { display: none; } - .codex-plus-toggle[data-relay-unneeded="true"]::after { content: "无需开启"; font-size: 12px; font-weight: 650; line-height: 1; } + .codex-plus-toggle[data-relay-unneeded="true"]::after { content: "Không cần bật"; font-size: 12px; font-weight: 650; line-height: 1; } .codex-plus-width-control { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 176px; align-self: center; } .codex-plus-width-input { width: 78px; @@ -1387,7 +1387,7 @@ let codexServiceTierState = { status: "loading", serviceTier: null, - message: "正在读取…", + message: "Đang đọc…", fastTierValue: "priority", controlMode: "inherit", defaultMode: "inherit", @@ -1434,7 +1434,7 @@ if (!codexServiceTierModulePromises.has(namePart)) { const promise = Promise.resolve().then(async () => { const url = codexAppAssetUrl(namePart) || await codexAppAssetUrlFromScriptText(namePart); - if (!url) throw new Error(`未找到 Codex App asset: ${namePart}`); + if (!url) throw new Error(`Không tìm thấy Codex App asset: ${namePart}`); return await import(url); }).catch((error) => { codexServiceTierModulePromises.delete(namePart); @@ -1511,8 +1511,8 @@ } function codexServiceTierFastUnsupportedMessage(modelName = codexServiceTierCurrentModelName()) { - const modelText = modelName ? `当前模型 ${modelName} 不支持` : "当前模型未读取"; - return `Fast 仅支持 ${codexServiceTierFastModelListLabel()},${modelText}`; + const modelText = modelName ? `model hiện tại ${modelName} không hỗ trợ` : "chưa đọc model hiện tại"; + return `Fast chỉ hỗ trợ ${codexServiceTierFastModelListLabel()}, ${modelText}`; } function codexServiceTierMaybeLoadModelCatalog(force = false) { @@ -1581,9 +1581,9 @@ } function serviceTierGlobalStatusMessage(serviceTier) { - if (isFastServiceTierValue(serviceTier)) return "Fast 已开启"; - if (!serviceTier) return "默认服务模式"; - return `当前:${serviceTier}`; + if (isFastServiceTierValue(serviceTier)) return "Fast đã bật"; + if (!serviceTier) return "Chế độ dịch vụ mặc định"; + return `Hiện tại: ${serviceTier}`; } function serviceTierStatusMessage( @@ -1592,13 +1592,13 @@ effectiveMode = codexServiceTierState.effectiveMode || "standard", defaultMode = codexServiceTierState.defaultMode || "inherit" ) { - if (codexServiceTierState.status === "loading") return "正在读取…"; - if (codexServiceTierState.status === "failed") return "读取失败"; - if (controlMode === "inherit") return `继承 config.toml:${effectiveMode}`; - if (controlMode === "global-standard") return "全局 Standard"; - if (controlMode === "global-fast") return "全局 Fast"; - if (threadMode === "inherit") return `自定义:默认 ${defaultMode}`; - return `自定义:当前 thread ${threadMode}`; + if (codexServiceTierState.status === "loading") return "Đang đọc…"; + if (codexServiceTierState.status === "failed") return "Đọc thất bại"; + if (controlMode === "inherit") return `Kế thừa config.toml: ${effectiveMode}`; + if (controlMode === "global-standard") return "Standard toàn cục"; + if (controlMode === "global-fast") return "Fast toàn cục"; + if (threadMode === "inherit") return `Tùy chỉnh: mặc định ${defaultMode}`; + return `Tùy chỉnh: thread hiện tại ${threadMode}`; } function readThreadServiceTierState() { @@ -1716,7 +1716,7 @@ function setCodexServiceTierControlMode(mode) { if (codexPlusBackendStatus.status !== "ok") { - showToast("后端未连接,无法切换服务模式", null); + showToast("Backend chưa kết nối, không thể đổi chế độ dịch vụ", null); refreshCodexServiceTierControls(); return; } @@ -1742,12 +1742,12 @@ writeThreadServiceTierState(state); refreshCodexServiceTierControls(); const labels = { - inherit: "继承 config.toml", - "global-standard": "全局 Standard", - "global-fast": "全局 Fast", - custom: "自定义", + inherit: "Kế thừa config.toml", + "global-standard": "Standard toàn cục", + "global-fast": "Fast toàn cục", + custom: "Tùy chỉnh", }; - showToast(`服务模式:${labels[normalizedMode] || normalizedMode}`, null); + showToast(`Chế độ dịch vụ: ${labels[normalizedMode] || normalizedMode}`, null); } function syncCodexServiceTierEffectiveState() { @@ -1758,7 +1758,7 @@ threadMode: "inherit", effectiveServiceTier: codexServiceTierState.serviceTier || null, effectiveMode: codexServiceTierEffectiveMode(codexServiceTierState.serviceTier), - message: "未启用", + message: "Chưa bật", }; return; } @@ -1790,22 +1790,22 @@ } function codexServiceTierBadgeState() { - if (codexPlusBackendStatus.status === "checking") return { tier: "loading", label: "...", disabled: true, title: "服务模式:正在检查后端连接" }; - if (codexPlusBackendStatus.status && codexPlusBackendStatus.status !== "ok") return { tier: "failed", label: "未连接", disabled: true, title: "服务模式:后端未连接,无法切换" }; - if (codexServiceTierState.status === "loading") return { tier: "loading", label: "...", title: "服务模式:正在读取" }; - if (codexServiceTierState.status === "failed") return { tier: "failed", label: "?", title: "服务模式:读取失败" }; + if (codexPlusBackendStatus.status === "checking") return { tier: "loading", label: "...", disabled: true, title: "Chế độ dịch vụ: đang kiểm tra kết nối backend" }; + if (codexPlusBackendStatus.status && codexPlusBackendStatus.status !== "ok") return { tier: "failed", label: "Chưa kết nối", disabled: true, title: "Chế độ dịch vụ: backend chưa kết nối, không thể đổi" }; + if (codexServiceTierState.status === "loading") return { tier: "loading", label: "...", title: "Chế độ dịch vụ: đang đọc" }; + if (codexServiceTierState.status === "failed") return { tier: "failed", label: "?", title: "Chế độ dịch vụ: đọc thất bại" }; const fastAvailability = codexServiceTierFastAvailability(); const effectiveMode = codexServiceTierState.effectiveMode || "standard"; const scope = codexServiceTierState.controlMode === "custom" && codexServiceTierState.threadMode !== "inherit" - ? `当前 thread:${codexServiceTierState.threadMode}` + ? `Thread hiện tại:${codexServiceTierState.threadMode}` : serviceTierStatusMessage(codexServiceTierState.controlMode, codexServiceTierState.threadMode, effectiveMode, codexServiceTierState.defaultMode); const title = [ - `服务模式:${scope}`, - "Standard:使用标准处理;不在请求上设置 priority。", - `Fast:仅支持 ${codexServiceTierFastModelListLabel()};对支持模型使用 service_tier=\"priority\",官方说明其延迟更低且更一致,但会按更高价格计费;rate limit 与 Standard 共享,流量快速上涨时可能回落到 Standard。`, + `Chế độ dịch vụ: ${scope}`, + "Standard: dùng xử lý tiêu chuẩn; không đặt priority trong request.", + `Fast: chỉ hỗ trợ ${codexServiceTierFastModelListLabel()}; model hỗ trợ sẽ dùng service_tier=\"priority\", độ trễ thấp và ổn định hơn nhưng giá cao hơn; rate limit dùng chung với Standard và có thể rơi về Standard khi lưu lượng tăng nhanh.`, ].join("\n"); if (effectiveMode === "fast" && !fastAvailability.supported) { - return { tier: "unsupported", label: "不支持", title: `${title}\n${codexServiceTierFastUnsupportedMessage(fastAvailability.modelName)};当前请求会按 Standard 发送。` }; + return { tier: "unsupported", label: "Không hỗ trợ", title: `${title}\n${codexServiceTierFastUnsupportedMessage(fastAvailability.modelName)};request hiện tại sẽ gửi theo Standard。` }; } if (effectiveMode === "fast") return { tier: "fast", label: "fast", title }; return { tier: "standard", label: "standard", title }; @@ -1840,8 +1840,8 @@ document.querySelectorAll("[data-codex-service-tier-status]").forEach((node) => { node.dataset.status = fastUnsupportedActive ? "unsupported" : (featureEnabled && backendConnected ? (codexServiceTierState.status || "loading") : (backendChecking ? "loading" : "failed")); node.textContent = featureEnabled - ? (backendConnected ? (codexServiceTierState.message || "未读取") : (backendChecking ? "正在检查后端…" : "未连接")) - : "未启用"; + ? (backendConnected ? (codexServiceTierState.message || "Chưa đọc") : (backendChecking ? "Đang kiểm tra backend…" : "Chưa kết nối")) + : "Chưa bật"; }); document.querySelectorAll("[data-codex-service-tier-inherit]").forEach((button) => { button.disabled = !featureEnabled || !backendConnected || codexServiceTierState.status === "loading"; @@ -1863,7 +1863,7 @@ document.querySelectorAll("[data-codex-service-tier-thread-inherit]").forEach((button) => { button.disabled = !featureEnabled || !backendConnected || codexServiceTierState.status === "loading"; button.dataset.active = String(codexServiceTierState.controlMode === "custom" && codexServiceTierState.threadMode === "inherit"); - button.title = `当前 thread 不单独覆盖,继承自定义默认 ${codexServiceTierState.defaultMode || "inherit"}`; + button.title = `Thread hiện tại không ghi đè riêng, kế thừa mặc định tùy chỉnh ${codexServiceTierState.defaultMode || "inherit"}`; }); document.querySelectorAll("[data-codex-service-tier-thread-standard]").forEach((button) => { button.disabled = !featureEnabled || !backendConnected || codexServiceTierState.status === "loading"; @@ -1879,11 +1879,11 @@ async function loadCodexServiceTierState() { if (!codexPlusSettings().serviceTierControls) { - codexServiceTierState = { ...codexServiceTierState, status: "idle", message: "未启用" }; + codexServiceTierState = { ...codexServiceTierState, status: "idle", message: "Chưa bật" }; refreshCodexServiceTierControls(); return; } - codexServiceTierState = { ...codexServiceTierState, status: "loading", message: "正在读取…" }; + codexServiceTierState = { ...codexServiceTierState, status: "loading", message: "Đang đọc…" }; refreshCodexServiceTierControls(); try { const serviceTier = await getCodexServiceTierSetting(); @@ -1897,7 +1897,7 @@ codexServiceTierState = { ...codexServiceTierState, status: "failed", - message: "读取失败", + message: "Đọc thất bại", }; sendCodexPlusDiagnostic("service_tier_read_failed", { errorName: error?.name || "", @@ -1910,7 +1910,7 @@ function setCodexThreadServiceTierMode(mode) { if (codexPlusBackendStatus.status !== "ok") { - showToast("后端未连接,无法切换服务模式", null); + showToast("Backend chưa kết nối, không thể đổi chế độ dịch vụ", null); refreshCodexServiceTierControls(); return; } @@ -1927,13 +1927,13 @@ const threadId = validThreadScrollSessionKey(currentSessionRef().session_id); setCodexThreadServiceTierOverride(threadId, normalizedMode); refreshCodexServiceTierControls(); - const target = threadId ? "当前 thread" : "新 thread 草稿"; - showToast(`${target}服务模式:${normalizedMode === "inherit" ? "继承" : normalizedMode}`, null); + const target = threadId ? "Thread hiện tại" : "Bản nháp thread mới"; + showToast(`${target} - chế độ dịch vụ: ${normalizedMode === "inherit" ? "Kế thừa" : normalizedMode}`, null); } function toggleCodexServiceTierFromBadge() { if (codexPlusBackendStatus.status !== "ok") { - showToast("后端未连接,无法切换服务模式", null); + showToast("Backend chưa kết nối, không thể đổi chế độ dịch vụ", null); refreshCodexServiceTierControls(); return; } @@ -2163,7 +2163,7 @@ } let codexPlusUserScripts = { enabled: true, builtin_dir: "", user_dir: "", scripts: [] }; - let codexPlusBackendStatus = { status: "checking", message: "正在检查后端…" }; + let codexPlusBackendStatus = { status: "checking", message: "Đang kiểm tra backend…" }; let codexPlusBackendCheckSeq = 0; function setCodexPlusTriggerLabel(trigger) { @@ -2189,6 +2189,35 @@ return indicator; } + function codexPlusViMessage(message) { + if (!message) return message; + const text = String(message); + const exact = { + "后端已连接": "Backend đã kết nối", + "未连接": "Chưa kết nối", + "正在检查后端…": "Đang kiểm tra backend…", + "正在检查后端": "Đang kiểm tra backend", + "后端检查超时": "Kiểm tra backend quá thời gian", + "未启用": "Chưa bật", + "未读取": "Chưa đọc", + "读取失败": "Đọc thất bại", + "管理工具已打开": "Đã mở công cụ quản lý", + "打开管理工具失败": "Mở công cụ quản lý thất bại", + }; + return exact[text] || text + .replaceAll("后端已连接", "Backend đã kết nối") + .replaceAll("后端未连接,无法切换服务模式", "Backend chưa kết nối, không thể đổi chế độ dịch vụ") + .replaceAll("未连接", "Chưa kết nối") + .replaceAll("正在检查后端", "Đang kiểm tra backend") + .replaceAll("服务模式", "Chế độ dịch vụ") + .replaceAll("读取失败", "Đọc thất bại") + .replaceAll("正在读取", "Đang đọc") + .replaceAll("未启用", "Chưa bật") + .replaceAll("继承", "Kế thừa") + .replaceAll("当前 thread", "Thread hiện tại") + .replaceAll("自定义", "Tùy chỉnh"); + } + function renderBackendStatus() { const status = codexPlusBackendStatus.status || "failed"; if (codexPlusBackendStatus.version) { @@ -2201,11 +2230,11 @@ const label = document.querySelector("[data-codex-backend-status]"); if (label) { label.dataset.status = status; - label.textContent = codexPlusBackendStatus.message || (status === "ok" ? "后端已连接" : "未连接"); + label.textContent = codexPlusViMessage(codexPlusBackendStatus.message) || (status === "ok" ? "Backend đã kết nối" : "Chưa kết nối"); } document.querySelectorAll("[data-codex-backend-indicator]").forEach((indicator) => { indicator.dataset.status = status; - indicator.title = status === "ok" ? "后端已连接" : status === "checking" ? "正在检查后端" : "未连接"; + indicator.title = status === "ok" ? "Backend đã kết nối" : status === "checking" ? "Đang kiểm tra backend" : "Chưa kết nối"; }); refreshCodexServiceTierControls(); } @@ -2213,7 +2242,7 @@ function withBackendTimeout(request) { return Promise.race([ request, - new Promise((resolve) => setTimeout(() => resolve({ status: "failed", message: "后端检查超时", timeout: true }), 2000)), + new Promise((resolve) => setTimeout(() => resolve({ status: "failed", message: "Kiểm tra backend quá thời gian", timeout: true }), 2000)), ]); } @@ -2235,9 +2264,9 @@ async function openManagerFromCodex() { const result = await postJson("/manager/open", {}); if (result.status === "ok") { - showToast("管理工具已打开", null); + showToast("Đã mở công cụ quản lý", null); } else { - showToast(result.message || "打开管理工具失败", null); + showToast(codexPlusViMessage(result.message) || "Mở công cụ quản lý thất bại", null); } } @@ -2248,25 +2277,25 @@ } function userScriptStatusLabel(status) { - return { loaded: "已加载", failed: "失败", disabled: "已禁用", not_loaded: "未加载", loading: "加载中" }[status] || status || "未知"; + return { loaded: "Đã tải", failed: "Lỗi", disabled: "Đã tắt", not_loaded: "Chưa tải", loading: "Đang tải" }[status] || status || "Không rõ"; } function renderUserScripts() { const enabledToggle = document.querySelector("[data-codex-user-scripts-enabled]"); if (enabledToggle) enabledToggle.dataset.enabled = String(!!codexPlusUserScripts.enabled); const dirs = document.querySelector("[data-codex-user-script-dirs]"); - if (dirs) dirs.textContent = `内置:${codexPlusUserScripts.builtin_dir || "未找到"} 用户:${codexPlusUserScripts.user_dir || "未找到"}`; + if (dirs) dirs.textContent = `Tích hợp: ${codexPlusUserScripts.builtin_dir || "Không tìm thấy"} Người dùng: ${codexPlusUserScripts.user_dir || "Không tìm thấy"}`; const list = document.querySelector("[data-codex-user-script-list]"); if (!list) return; if (!codexPlusUserScripts.scripts?.length) { - list.textContent = "未发现用户脚本。"; + list.textContent = "Không tìm thấy script người dùng."; return; } list.innerHTML = codexPlusUserScripts.scripts.map((script) => `
${escapeHtml(script.name || script.key)}
-
${script.source === "builtin" ? "内置" : "用户"} · ${userScriptStatusLabel(script.status)}
+
${script.source === "builtin" ? "Tích hợp" : "Người dùng"} · ${userScriptStatusLabel(script.status)}
${script.error ? `
${escapeHtml(script.error)}
` : ""}
@@ -2320,23 +2349,23 @@
${ad.highlights.map((item) => `${escapeHtml(item)}`).join("")}
- 访问 ${escapeHtml(new URL(ad.url).hostname)} + Truy cập ${escapeHtml(new URL(ad.url).hostname)}
`).join(""); } function renderCodexPlusAds() { - if (!codexPlusAdsLoaded) return `
推荐内容加载中…
`; - if (!codexPlusAds.length) return `
暂无推荐内容。
`; + if (!codexPlusAdsLoaded) return `
推荐内容Đang tải…
`; + if (!codexPlusAds.length) return `
Chưa có nội dung đề xuất.
`; return `
-

赞助商推荐

-
${renderCodexPlusAdGroup("sponsor", "暂无赞助商推荐。")}
+

Đề xuất tài trợ

+
${renderCodexPlusAdGroup("sponsor", "暂无Đề xuất tài trợ。")}
-

普通推荐

-
${renderCodexPlusAdGroup("normal", "暂无普通推荐。")}
+

Đề xuất thường

+
${renderCodexPlusAdGroup("normal", "暂无Đề xuất thường。")}
`; } @@ -2407,89 +2436,89 @@