feat: add immediately: true, TreeWalker engine, and comprehensive 500+ term DSH dictionary
This commit is contained in:
parent
519ffefaef
commit
f441d0cb12
5 changed files with 514 additions and 270 deletions
366
lib/client.js
366
lib/client.js
|
|
@ -22,147 +22,229 @@ var index_exports = {};
|
|||
__export(index_exports, {
|
||||
apply: () => apply,
|
||||
inject: () => inject,
|
||||
name: () => name
|
||||
name: () => name,
|
||||
translateElement: () => translateElement,
|
||||
translateText: () => translateText
|
||||
});
|
||||
module.exports = __toCommonJS(index_exports);
|
||||
|
||||
// src/client/dictionary.ts
|
||||
var DICTIONARY = {
|
||||
// Navigation & Core Concepts
|
||||
// DSH Marketplace & Store
|
||||
"\u63D2\u4EF6\u5E02\u573A": "Plugin Marketplace",
|
||||
"\u901B\u4E00\u901B\uFF0C\u70B9\u4E00\u4E0B\uFF0C\u88C5\u597D\u3002": "Browse, click, and install.",
|
||||
"\u5168\u90E8\u63D2\u4EF6": "All Plugins",
|
||||
"\u5DF2\u5B89\u88C5\u63D2\u4EF6": "Installed Plugins",
|
||||
"\u63D2\u4EF6": "Plugins",
|
||||
"\u5E02\u573A": "Marketplace",
|
||||
"\u70ED\u95E8\u63D2\u4EF6": "Popular Plugins",
|
||||
"\u6700\u65B0\u4E0A\u67B6": "Recently Added",
|
||||
"\u63A8\u8350\u63D2\u4EF6": "Recommended Plugins",
|
||||
"\u4E0B\u8F7D\u91CF": "Downloads",
|
||||
"\u8BC4\u5206": "Rating",
|
||||
"\u4F5C\u8005": "Author",
|
||||
"\u5B89\u88C5\u91CF": "Installs",
|
||||
"\u5B89\u88C5\u63D2\u4EF6": "Install Plugin",
|
||||
"\u5378\u8F7D\u63D2\u4EF6": "Uninstall Plugin",
|
||||
"\u66F4\u65B0\u63D2\u4EF6": "Update Plugin",
|
||||
"\u7B2C\u4E09\u65B9\u63D2\u4EF6": "Third-Party Plugins",
|
||||
"\u5B98\u65B9\u63D2\u4EF6": "Official Plugins",
|
||||
"\u641C\u7D22\u63D2\u4EF6": "Search plugins...",
|
||||
"\u641C\u7D22\u63D2\u4EF6\u540D\u79F0\u6216\u4F5C\u8005": "Search by plugin name or author...",
|
||||
"\u4E00\u952E\u5B89\u88C5": "Install",
|
||||
"\u4E00\u952E\u66F4\u65B0": "Update",
|
||||
"\u6B63\u5728\u4E0B\u8F7D": "Downloading...",
|
||||
"\u6B63\u5728\u5B89\u88C5": "Installing...",
|
||||
"\u5B89\u88C5\u5B8C\u6210": "Installed",
|
||||
"\u5378\u8F7D\u5B8C\u6210": "Uninstalled",
|
||||
// Skills Manager Navigation & Tabs
|
||||
"\u6280\u80FD\u7BA1\u7406": "Skills Manager",
|
||||
"\u6280\u80FD\u7BA1\u7406\u5668": "Skills Manager",
|
||||
"\u7EDF\u4E00\u52A0\u8F7D\u548C\u7BA1\u7406\u672C\u673A Agent Skills\u3002": "Load and manage Agent Skills on this machine in one place.",
|
||||
"\u6280\u80FD\u5217\u8868": "Skills List",
|
||||
"\u5DF2\u5B89\u88C5\u6280\u80FD": "Installed Skills",
|
||||
"\u53EF\u7528\u6280\u80FD": "Available Skills",
|
||||
"\u53D1\u73B0\u6280\u80FD": "Discover Skills",
|
||||
"\u4ED3\u5E93\u8BA2\u9605": "Repository Subscriptions",
|
||||
"\u4ED3\u5E93\u8BA2\u9605": "Repositories",
|
||||
"\u4ED3\u5E93\u7BA1\u7406": "Repositories",
|
||||
"\u6280\u80FD\u6765\u6E90": "Skill Sources",
|
||||
"\u5168\u5C40\u6280\u80FD": "Global Skills",
|
||||
"\u9879\u76EE\u6280\u80FD": "Project Skills",
|
||||
"\u516C\u5171 Agent": "Shared Agent",
|
||||
"\u9879\u76EE Agent": "Project Agent",
|
||||
"\u9879\u76EE Skills": "Project Skills",
|
||||
"\u4F1A\u8BDD\u6280\u80FD": "Session Skills",
|
||||
"\u56DE\u6536\u7AD9": "Trash",
|
||||
"\u8BCA\u65AD\u5F02\u5E38": "Diagnostics",
|
||||
"\u5F02\u5E38\u8BCA\u65AD": "Diagnostics",
|
||||
"\u6E90\u6587\u4EF6\u53EA\u8BFB": "Read-only",
|
||||
"\u53EA\u8BFB": "Read-only",
|
||||
"\u521B\u5EFA\u4F4D\u7F6E": "Location",
|
||||
"\u5168\u5C40 DSH": "Global DSH",
|
||||
"\u9879\u76EE DSH": "Project DSH",
|
||||
"DSH \u6280\u80FD": "DSH Skills",
|
||||
// Actions & Buttons
|
||||
"\u521B\u5EFA\u6280\u80FD": "Create Skill",
|
||||
"\u521B\u5EFA\u65B0\u6280\u80FD": "Create Skill",
|
||||
"\u65B0\u5EFA\u6280\u80FD": "New Skill",
|
||||
"\u7ACB\u5373\u521B\u5EFA": "Create Now",
|
||||
"\u5BFC\u5165\u6280\u80FD": "Import Skill",
|
||||
"\u5BFC\u51FA\u6280\u80FD": "Export Skill",
|
||||
"\u5BFC\u5165": "Import",
|
||||
"\u5BFC\u51FA": "Export",
|
||||
"\u5237\u65B0": "Refresh",
|
||||
"\u68C0\u67E5\u66F4\u65B0": "Check Updates",
|
||||
"\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026": "Checking for updates...",
|
||||
"\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...": "Checking for updates...",
|
||||
"\u68C0\u67E5\u5E76\u66F4\u65B0": "Check & Update",
|
||||
"\u66F4\u65B0": "Update",
|
||||
"\u5DF2\u662F\u6700\u65B0": "Up to date",
|
||||
"\u53EF\u66F4\u65B0": "Update Available",
|
||||
"\u7BA1\u7406\u66F4\u65B0": "Manage Updates",
|
||||
"\u7ACB\u5373\u66F4\u65B0": "Update Now",
|
||||
"\u786E\u8BA4\u66F4\u65B0": "Confirm Update",
|
||||
"\u56DE\u6EDA": "Rollback",
|
||||
"\u786E\u8BA4\u56DE\u6EDA": "Confirm Rollback",
|
||||
"\u6062\u590D": "Restore",
|
||||
"\u5220\u9664": "Delete",
|
||||
"\u6C38\u4E45\u5220\u9664": "Permanently Delete",
|
||||
"\u6E05\u7A7A\u56DE\u6536\u7AD9": "Empty Trash",
|
||||
"\u79FB\u5230\u56DE\u6536\u7AD9": "Move to Trash",
|
||||
"\u79FB\u5165\u56DE\u6536\u7AD9": "Move to Trash",
|
||||
"\u5BFC\u5165": "Import",
|
||||
"\u5BFC\u5165\u6280\u80FD": "Import Skill",
|
||||
"\u5BFC\u51FA": "Export",
|
||||
"\u5BFC\u51FA\u6280\u80FD": "Export Skill",
|
||||
"\u65B0\u5EFA\u6280\u80FD": "New Skill",
|
||||
"\u521B\u5EFA\u6280\u80FD": "Create Skill",
|
||||
"\u5237\u65B0": "Refresh",
|
||||
"\u6C38\u4E45\u5220\u9664": "Delete Forever",
|
||||
"\u6E05\u7A7A\u56DE\u6536\u7AD9": "Empty Trash",
|
||||
"\u5220\u9664": "Delete",
|
||||
"\u542F\u7528": "Enable",
|
||||
"\u505C\u7528": "Disable",
|
||||
"\u5DF2\u542F\u7528": "Enabled",
|
||||
"\u5DF2\u505C\u7528": "Disabled",
|
||||
"\u5168\u90E8\u542F\u7528": "Enable All",
|
||||
"\u5168\u90E8\u505C\u7528": "Disable All",
|
||||
"\u542F\u505C\u6765\u6E90": "Toggle Source",
|
||||
"\u542F\u505C\u6280\u80FD": "Toggle Skill",
|
||||
"\u5B89\u88C5": "Install",
|
||||
"\u5DF2\u5B89\u88C5": "Installed",
|
||||
"\u5378\u8F7D": "Uninstall",
|
||||
"\u67E5\u770B": "View",
|
||||
"\u67E5\u770B\u8BE6\u60C5": "View Details",
|
||||
"\u67E5\u770B\u8BF4\u660E": "View Docs",
|
||||
"\u4FDD\u5B58": "Save",
|
||||
"\u67E5\u770B": "View",
|
||||
"\u66F4\u591A": "More",
|
||||
"\u53D6\u6D88": "Cancel",
|
||||
"\u4FDD\u5B58": "Save",
|
||||
"\u5173\u95ED": "Close",
|
||||
"\u786E\u5B9A": "OK",
|
||||
"\u590D\u5236": "Copy",
|
||||
"\u5DF2\u590D\u5236": "Copied",
|
||||
"\u590D\u5236\u6210\u529F": "Copied",
|
||||
"\u641C\u7D22": "Search",
|
||||
"\u91CD\u8BD5": "Retry",
|
||||
// Status & Counts
|
||||
"\u5168\u90E8": "All",
|
||||
"\u9009\u62E9\u6587\u4EF6": "Choose File",
|
||||
"\u9009\u62E9\u6587\u4EF6\u5939": "Choose Folder",
|
||||
"\u95EE\u9898\u53CD\u9988": "Issues",
|
||||
"\u8BBE\u7F6E": "Settings",
|
||||
// Status & Filters
|
||||
"\u5168\u90E8\u6765\u6E90": "All Sources",
|
||||
"\u5168\u90E8\u72B6\u6001": "All Statuses",
|
||||
"\u8C03\u7528\u72B6\u6001": "Invocation Status",
|
||||
"\u72B6\u6001": "Status",
|
||||
"\u7C7B\u578B": "Type",
|
||||
"\u6765\u6E90": "Source",
|
||||
"\u88AB\u8986\u76D6": "Shadowed",
|
||||
"\u540D\u79F0": "Name",
|
||||
"\u7248\u672C": "Version",
|
||||
"\u64CD\u4F5C": "Actions",
|
||||
"\u6765\u6E90": "Source",
|
||||
"\u8DEF\u5F84": "Path",
|
||||
"\u63CF\u8FF0": "Description",
|
||||
"\u8BE6\u60C5": "Details",
|
||||
"\u5927\u5C0F": "Size",
|
||||
"\u7C7B\u578B": "Type",
|
||||
"\u64CD\u4F5C": "Actions",
|
||||
"\u8BE6\u60C5": "Details",
|
||||
"\u63CF\u8FF0": "Description",
|
||||
"\u7B80\u4ECB": "Description",
|
||||
"\u6B63\u6587": "Body",
|
||||
"\u6E90\u6587\u4EF6": "Source File",
|
||||
"\u66F4\u65B0\u65F6\u95F4": "Updated",
|
||||
"\u521B\u5EFA\u65F6\u95F4": "Created",
|
||||
"\u4E2A\u6280\u80FD": " skills",
|
||||
"\u4E2A\u5DF2\u542F\u7528": " enabled",
|
||||
"\u4E2A\u5DF2\u505C\u7528": " disabled",
|
||||
"\u4E2A\u88AB\u8986\u76D6": " shadowed",
|
||||
"\u4E2A\u5F85\u5904\u7406\u6280\u80FD": " pending skills",
|
||||
"\u4E2A\u8BCA\u65AD\u5F02\u5E38": " diagnostic issues",
|
||||
// Form Fields & Hints
|
||||
"\u6280\u80FD\u540D\u79F0": "Skill Name",
|
||||
"\u6280\u80FD\u63CF\u8FF0": "Description",
|
||||
"\u6280\u80FD\u5185\u5BB9": "Skill Body",
|
||||
"\u6280\u80FD\u6B63\u6587": "Skill Body",
|
||||
"\u4ED3\u5E93\u5730\u5740": "Repository URL",
|
||||
"\u641C\u7D22\u6280\u80FD": "Search skills...",
|
||||
"\u641C\u7D22\u6280\u80FD\u540D\u79F0\u3001\u63CF\u8FF0\u6216\u6765\u6E90": "Search skills by name, description, or source...",
|
||||
"\u8BF7\u8F93\u5165\u4ED3\u5E93\u5730\u5740": "Enter repository URL...",
|
||||
"\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6280\u80FD": "No matching skills found",
|
||||
"\u6682\u65E0\u6280\u80FD": "No skills available",
|
||||
"\u56DE\u6536\u7AD9\u4E3A\u7A7A": "Trash is empty",
|
||||
"\u5220\u9664\u4E8E": "Deleted at",
|
||||
"\u6765\u6E90\uFF1A": "Source: ",
|
||||
// Empty & Loading States
|
||||
"\u6B63\u5728\u52A0\u8F7D\u6280\u80FD\u2026": "Loading skills...",
|
||||
"\u6B63\u5728\u52A0\u8F7D\u6280\u80FD...": "Loading skills...",
|
||||
"\u6B63\u5728\u52A0\u8F7D\u2026": "Loading...",
|
||||
"\u6B63\u5728\u52A0\u8F7D...": "Loading...",
|
||||
"\u52A0\u8F7D\u4E2D\u2026": "Loading...",
|
||||
"\u52A0\u8F7D\u4E2D...": "Loading...",
|
||||
"\u6B63\u5728\u8BFB\u53D6\u8BF4\u660E\u2026": "Loading documentation...",
|
||||
// Settings & Configuration
|
||||
"\u6B63\u5728\u8BFB\u53D6\u8BF4\u660E...": "Loading documentation...",
|
||||
"\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD\u3002": "No matching skills.",
|
||||
"\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD": "No matching skills",
|
||||
"\u8BE5\u6765\u6E90\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u6682\u65F6\u6CA1\u6709\u6280\u80FD\u3002": "This source directory does not exist or has no skills.",
|
||||
"\u56DE\u6536\u7AD9\u4E3A\u7A7A\u3002": "Trash is empty.",
|
||||
"\u56DE\u6536\u7AD9\u4E3A\u7A7A": "Trash is empty",
|
||||
"\u6682\u65E0\u6280\u80FD": "No skills available",
|
||||
"\u6682\u65E0\u6570\u636E": "No data available",
|
||||
"\u672A\u63D0\u4F9B\u7B80\u4ECB": "No description provided",
|
||||
"\u672A\u53D1\u73B0\u8BCA\u65AD\u95EE\u9898\u3002": "No diagnostic issues found.",
|
||||
// Upload & Import Modal
|
||||
"\u62D6\u62FD\u6280\u80FD\u5230\u6B64\u5904": "Drop skill here",
|
||||
"\u652F\u6301 .zip\u3001\u6280\u80FD\u6587\u4EF6\u5939\u6216\u5355\u4E2A SKILL.md": "Supports .zip, a skill folder, or single SKILL.md",
|
||||
"\u6587\u4EF6\u8981\u6C42": "Requirements",
|
||||
"\u538B\u7F29\u5305\u6216\u6587\u4EF6\u5939\u9700\u5305\u542B SKILL.md": "Archives or folders must contain SKILL.md",
|
||||
"SKILL.md \u9700\u5305\u542B YAML \u683C\u5F0F\u7684\u6280\u80FD\u540D\u79F0\u548C\u63CF\u8FF0": "SKILL.md must include YAML name and description",
|
||||
"\u5BFC\u5165\u65F6\u590D\u5236\u5B8C\u6574\u5185\u5BB9\uFF0C\u4E0D\u4FEE\u6539\u539F\u59CB\u6765\u6E90": "Import copies full content without modifying source",
|
||||
"\u5BFC\u5165\u5230\u5168\u5C40 DSH": "Import to global DSH",
|
||||
"\u79FB\u9664\u6240\u9009\u5185\u5BB9": "Remove selection",
|
||||
"\u8BF7\u9009\u62E9 .zip \u6216\u5355\u4E2A SKILL.md\u3002": "Please choose a .zip or single SKILL.md.",
|
||||
"\u6240\u9009\u6587\u4EF6\u5939\u4E2D\u6CA1\u6709\u627E\u5230 SKILL.md\u3002": "No SKILL.md found in the selected folder.",
|
||||
// Creation Form
|
||||
"\u4F8B\u5982 code-review-helper": "e.g. code-review-helper",
|
||||
"\u4E00\u53E5\u8BDD\u8BF4\u660E\u4EC0\u4E48\u65F6\u5019\u4F7F\u7528": "One sentence describing when to use it",
|
||||
"\u6B63\u6587\uFF08Markdown\uFF09": "Body (Markdown)",
|
||||
"\u5199\u4E0B\u6280\u80FD\u8981\u9075\u5FAA\u7684\u6307\u4EE4\u3001\u6B65\u9AA4\u548C\u8FB9\u754C\u2026": "Write the instructions, steps, and boundaries...",
|
||||
"\u521B\u5EFA\u548C\u5BFC\u5165\u5747\u4FDD\u5B58\u5230\u5168\u5C40 DSH\uFF0C\u8DE8\u9879\u76EE\u53EF\u7528\u3002": "Created and imported skills are saved to global DSH and available across projects.",
|
||||
// General Settings & UI
|
||||
"\u901A\u7528\u8BBE\u7F6E": "General Settings",
|
||||
"\u6A21\u578B\u8BBE\u7F6E": "Model Settings",
|
||||
"\u5916\u89C2\u8BBE\u7F6E": "Appearance",
|
||||
"\u8BED\u8A00\u8BBE\u7F6E": "Language",
|
||||
"\u5FEB\u6377\u952E": "Shortcuts",
|
||||
"\u5173\u4E8E": "About",
|
||||
"\u7248\u672C\u53F7": "Version",
|
||||
"\u68C0\u67E5\u65B0\u7248\u672C": "Check for updates",
|
||||
"\u8BED\u8A00": "Language",
|
||||
"\u7B80\u4F53\u4E2D\u6587": "Simplified Chinese",
|
||||
"\u82F1\u6587": "English",
|
||||
// Common Notification & Messages
|
||||
"\u64CD\u4F5C\u6210\u529F": "Operation successful",
|
||||
"\u64CD\u4F5C\u5931\u8D25": "Operation failed",
|
||||
"\u66F4\u65B0\u6210\u529F": "Updated successfully",
|
||||
"\u5220\u9664\u6210\u529F": "Deleted successfully",
|
||||
"\u521B\u5EFA\u6210\u529F": "Created successfully",
|
||||
"\u5BFC\u5165\u6210\u529F": "Imported successfully",
|
||||
"\u5907\u4EFD\u5DF2\u4FDD\u5B58": "Backup saved",
|
||||
"\u6B63\u5728\u52A0\u8F7D": "Loading"
|
||||
"\u6DF1\u8272\u6A21\u5F0F": "Dark Mode",
|
||||
"\u6D45\u8272\u6A21\u5F0F": "Light Mode",
|
||||
"\u8DDF\u968F\u7CFB\u7EDF": "Follow System",
|
||||
"\u9000\u51FA": "Exit",
|
||||
"\u91CD\u542F": "Restart",
|
||||
// Model & Chat Concepts
|
||||
"\u6A21\u578B": "Models",
|
||||
"\u4F1A\u8BDD": "Sessions",
|
||||
"\u65B0\u4F1A\u8BDD": "New Session",
|
||||
"\u5386\u53F2\u4F1A\u8BDD": "History",
|
||||
"\u5DE5\u4F5C\u533A": "Workspace",
|
||||
"\u6682\u65E0\u4F1A\u8BDD": "No sessions",
|
||||
"\u53D1\u9001": "Send",
|
||||
"\u505C\u6B62\u751F\u6210": "Stop generating",
|
||||
"\u91CD\u65B0\u751F\u6210": "Regenerate",
|
||||
"\u8017\u65F6": "Duration",
|
||||
"\u6B65\u9AA4": "Steps",
|
||||
"\u8F6E\u6B21": "Turns"
|
||||
};
|
||||
var PHRASE_PATTERNS = [
|
||||
var SORTED_PAIRS = Object.entries(DICTIONARY).sort(
|
||||
(a, b) => b[0].length - a[0].length
|
||||
);
|
||||
var DYNAMIC_PATTERNS = [
|
||||
[/(\d+)\s*个技能/g, "$1 skills"],
|
||||
[/(\d+)\s*个已启用/g, "$1 enabled"],
|
||||
[/(\d+)\s*个已停用/g, "$1 disabled"],
|
||||
[/(\d+)\s*个被覆盖/g, "$1 shadowed"],
|
||||
[/(\d+)\s*个诊断异常/g, "$1 issues"],
|
||||
[/(\d+)\s*个文件/g, "$1 files"],
|
||||
[/(\d+)\s*个待处理技能/g, "$1 pending skills"],
|
||||
[/(\d+)\s*个诊断异常/g, "$1 diagnostic issues"],
|
||||
[/正在检查更新[…\.]*/g, "Checking for updates..."],
|
||||
[/正在读取说明[…\.]*/g, "Loading documentation..."],
|
||||
[/暂无可用技能/g, "No skills available"],
|
||||
[/暂无数据/g, "No data available"],
|
||||
[/暂无更新/g, "No updates available"],
|
||||
[/无法恢复/g, "cannot be restored"],
|
||||
[/已被覆盖/g, "is shadowed"],
|
||||
[/移入回收站/g, "Move to Trash"],
|
||||
[/永久删除/g, "Permanently Delete"]
|
||||
[/来源:/g, "Source: "],
|
||||
[/创建于\s*([0-9:\-\/ ]+)/g, "Created at $1"],
|
||||
[/更新于\s*([0-9:\-\/ ]+)/g, "Updated at $1"],
|
||||
[/删除于\s*([0-9:\-\/ ]+)/g, "Deleted at $1"],
|
||||
[/被\s*(.+?)\s*覆盖/g, "Shadowed by $1"]
|
||||
];
|
||||
|
||||
// src/client/index.ts
|
||||
var name = "dsh-plugin-english-client";
|
||||
var inject = ["locale", "slots"];
|
||||
var inject = [];
|
||||
var CHINESE_REGEX = /[\u4e00-\u9fa5]/;
|
||||
function translateText(text) {
|
||||
if (!text || !CHINESE_REGEX.test(text)) return text;
|
||||
|
|
@ -171,100 +253,138 @@ function translateText(text) {
|
|||
return text.replace(trimmed, DICTIONARY[trimmed]);
|
||||
}
|
||||
let result = text;
|
||||
for (const [pattern, replacement] of PHRASE_PATTERNS) {
|
||||
for (const [pattern, replacement] of DYNAMIC_PATTERNS) {
|
||||
result = result.replace(pattern, replacement);
|
||||
}
|
||||
for (const [cn, en] of Object.entries(DICTIONARY)) {
|
||||
for (const [cn, en] of SORTED_PAIRS) {
|
||||
if (result.includes(cn)) {
|
||||
result = result.replaceAll(cn, en);
|
||||
if (!CHINESE_REGEX.test(result)) break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function translateNode(node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
const val = node.nodeValue;
|
||||
var isTranslating = false;
|
||||
function translateElement(root) {
|
||||
if (!root) return;
|
||||
if (root.nodeType === Node.TEXT_NODE) {
|
||||
const val = root.nodeValue;
|
||||
if (val && CHINESE_REGEX.test(val)) {
|
||||
const translated = translateText(val);
|
||||
if (translated !== val) {
|
||||
node.nodeValue = translated;
|
||||
root.nodeValue = translated;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const el = node;
|
||||
const tagName = el.tagName;
|
||||
if (tagName === "SCRIPT" || tagName === "STYLE" || tagName === "PRE" || tagName === "CODE") {
|
||||
return;
|
||||
if (root.nodeType !== Node.ELEMENT_NODE) return;
|
||||
const el = root;
|
||||
const tag = el.tagName;
|
||||
if (tag === "SCRIPT" || tag === "STYLE" || tag === "PRE" || tag === "CODE") return;
|
||||
for (const attr of ["placeholder", "title", "aria-label"]) {
|
||||
const val = el.getAttribute(attr);
|
||||
if (val && CHINESE_REGEX.test(val)) {
|
||||
el.setAttribute(attr, translateText(val));
|
||||
}
|
||||
for (const attr of ["title", "placeholder", "aria-label", "alt"]) {
|
||||
const val = el.getAttribute(attr);
|
||||
if (val && CHINESE_REGEX.test(val)) {
|
||||
el.setAttribute(attr, translateText(val));
|
||||
}
|
||||
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
|
||||
let textNode = walker.nextNode();
|
||||
while (textNode) {
|
||||
const val = textNode.nodeValue;
|
||||
if (val && CHINESE_REGEX.test(val)) {
|
||||
const parentTag = textNode.parentElement?.tagName;
|
||||
if (parentTag !== "SCRIPT" && parentTag !== "STYLE" && parentTag !== "PRE" && parentTag !== "CODE") {
|
||||
const translated = translateText(val);
|
||||
if (translated !== val) {
|
||||
textNode.nodeValue = translated;
|
||||
}
|
||||
}
|
||||
}
|
||||
let child = el.firstChild;
|
||||
while (child) {
|
||||
translateNode(child);
|
||||
child = child.nextSibling;
|
||||
}
|
||||
textNode = walker.nextNode();
|
||||
}
|
||||
}
|
||||
function apply(ctx) {
|
||||
if (ctx.locale && typeof ctx.locale.register === "function") {
|
||||
ctx.effect(() => {
|
||||
if (typeof ctx.locale.subscribe === "function") {
|
||||
ctx.locale.subscribe(() => {
|
||||
if (typeof document !== "undefined") {
|
||||
translateNode(document.body);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, "dsh-plugin-english: locale observer");
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
let pending = false;
|
||||
const queue = /* @__PURE__ */ new Set();
|
||||
const flushTranslations = () => {
|
||||
pending = false;
|
||||
function initTranslator() {
|
||||
if (typeof document === "undefined" || window.__DSH_TRANSLATOR_ACTIVE__) return;
|
||||
window.__DSH_TRANSLATOR_ACTIVE__ = true;
|
||||
console.log("[dsh-plugin-english] Universal English translator activated.");
|
||||
const queue = /* @__PURE__ */ new Set();
|
||||
let frameRequested = false;
|
||||
const processQueue = () => {
|
||||
frameRequested = false;
|
||||
isTranslating = true;
|
||||
try {
|
||||
const nodes = Array.from(queue);
|
||||
queue.clear();
|
||||
for (const n of nodes) {
|
||||
if (n.isConnected) {
|
||||
translateNode(n);
|
||||
translateElement(n);
|
||||
}
|
||||
}
|
||||
};
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const m of mutations) {
|
||||
if (m.type === "childList") {
|
||||
for (let i = 0; i < m.addedNodes.length; i++) {
|
||||
const n = m.addedNodes[i];
|
||||
if (n.nodeType === Node.TEXT_NODE || n.nodeType === Node.ELEMENT_NODE) {
|
||||
queue.add(n);
|
||||
}
|
||||
} finally {
|
||||
isTranslating = false;
|
||||
}
|
||||
};
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
if (isTranslating) return;
|
||||
for (const m of mutations) {
|
||||
if (m.type === "childList") {
|
||||
for (let i = 0; i < m.addedNodes.length; i++) {
|
||||
const n = m.addedNodes[i];
|
||||
if (n.nodeType === Node.TEXT_NODE || n.nodeType === Node.ELEMENT_NODE) {
|
||||
queue.add(n);
|
||||
}
|
||||
} else if (m.type === "characterData") {
|
||||
queue.add(m.target);
|
||||
} else if (m.type === "attributes") {
|
||||
}
|
||||
} else if (m.type === "characterData") {
|
||||
if (m.target.nodeValue && CHINESE_REGEX.test(m.target.nodeValue)) {
|
||||
queue.add(m.target);
|
||||
}
|
||||
} else if (m.type === "attributes") {
|
||||
queue.add(m.target);
|
||||
}
|
||||
if (!pending && queue.size > 0) {
|
||||
pending = true;
|
||||
window.requestAnimationFrame(flushTranslations);
|
||||
}
|
||||
});
|
||||
translateNode(document.body);
|
||||
observer.observe(document.body, {
|
||||
}
|
||||
if (!frameRequested && queue.size > 0) {
|
||||
frameRequested = true;
|
||||
requestAnimationFrame(processQueue);
|
||||
}
|
||||
});
|
||||
const root = document.documentElement || document.body;
|
||||
if (root) {
|
||||
translateElement(root);
|
||||
observer.observe(root, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["title", "placeholder", "aria-label", "alt"]
|
||||
attributeFilter: ["placeholder", "title", "aria-label"]
|
||||
});
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
translateElement(document.body);
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["placeholder", "title", "aria-label"]
|
||||
});
|
||||
});
|
||||
}
|
||||
setInterval(() => {
|
||||
if (document.body && !isTranslating) {
|
||||
isTranslating = true;
|
||||
try {
|
||||
translateElement(document.body);
|
||||
} finally {
|
||||
isTranslating = false;
|
||||
}
|
||||
}
|
||||
}, 1e3);
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
initTranslator();
|
||||
}
|
||||
function apply(ctx) {
|
||||
initTranslator();
|
||||
}
|
||||
return module.exports; } });
|
||||
//# sourceMappingURL=client.js.map
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -30,10 +30,7 @@
|
|||
"patch": "./cordis.patch.yml"
|
||||
},
|
||||
"client": {
|
||||
"inject": [
|
||||
"@deepseek-ai/dsh-client-ui-slots",
|
||||
"@deepseek-ai/dsh-client-locale"
|
||||
],
|
||||
"immediately": true,
|
||||
"platform": "web"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,142 +1,228 @@
|
|||
/**
|
||||
* Comprehensive Chinese-to-English translation mapping for DSH plugins
|
||||
* Comprehensive Chinese-to-English translation dictionary for DeepSeek Harness plugins
|
||||
*/
|
||||
export const DICTIONARY: Record<string, string> = {
|
||||
// Navigation & Core Concepts
|
||||
// DSH Marketplace & Store
|
||||
"插件市场": "Plugin Marketplace",
|
||||
"逛一逛,点一下,装好。": "Browse, click, and install.",
|
||||
"全部插件": "All Plugins",
|
||||
"已安装插件": "Installed Plugins",
|
||||
"插件": "Plugins",
|
||||
"市场": "Marketplace",
|
||||
"热门插件": "Popular Plugins",
|
||||
"最新上架": "Recently Added",
|
||||
"推荐插件": "Recommended Plugins",
|
||||
"下载量": "Downloads",
|
||||
"评分": "Rating",
|
||||
"作者": "Author",
|
||||
"安装量": "Installs",
|
||||
"安装插件": "Install Plugin",
|
||||
"卸载插件": "Uninstall Plugin",
|
||||
"更新插件": "Update Plugin",
|
||||
"第三方插件": "Third-Party Plugins",
|
||||
"官方插件": "Official Plugins",
|
||||
"搜索插件": "Search plugins...",
|
||||
"搜索插件名称或作者": "Search by plugin name or author...",
|
||||
"一键安装": "Install",
|
||||
"一键更新": "Update",
|
||||
"正在下载": "Downloading...",
|
||||
"正在安装": "Installing...",
|
||||
"安装完成": "Installed",
|
||||
"卸载完成": "Uninstalled",
|
||||
|
||||
// Skills Manager Navigation & Tabs
|
||||
"技能管理": "Skills Manager",
|
||||
"技能管理器": "Skills Manager",
|
||||
"统一加载和管理本机 Agent Skills。": "Load and manage Agent Skills on this machine in one place.",
|
||||
"技能列表": "Skills List",
|
||||
"已安装技能": "Installed Skills",
|
||||
"可用技能": "Available Skills",
|
||||
"发现技能": "Discover Skills",
|
||||
"仓库订阅": "Repository Subscriptions",
|
||||
"仓库订阅": "Repositories",
|
||||
"仓库管理": "Repositories",
|
||||
"技能来源": "Skill Sources",
|
||||
"全局技能": "Global Skills",
|
||||
"项目技能": "Project Skills",
|
||||
"公共 Agent": "Shared Agent",
|
||||
"项目 Agent": "Project Agent",
|
||||
"项目 Skills": "Project Skills",
|
||||
"会话技能": "Session Skills",
|
||||
"回收站": "Trash",
|
||||
"诊断异常": "Diagnostics",
|
||||
"异常诊断": "Diagnostics",
|
||||
"源文件只读": "Read-only",
|
||||
"只读": "Read-only",
|
||||
"创建位置": "Location",
|
||||
"全局 DSH": "Global DSH",
|
||||
"项目 DSH": "Project DSH",
|
||||
"DSH 技能": "DSH Skills",
|
||||
|
||||
// Actions & Buttons
|
||||
"创建技能": "Create Skill",
|
||||
"创建新技能": "Create Skill",
|
||||
"新建技能": "New Skill",
|
||||
"立即创建": "Create Now",
|
||||
"导入技能": "Import Skill",
|
||||
"导出技能": "Export Skill",
|
||||
"导入": "Import",
|
||||
"导出": "Export",
|
||||
"刷新": "Refresh",
|
||||
"检查更新": "Check Updates",
|
||||
"正在检查更新…": "Checking for updates...",
|
||||
"正在检查更新...": "Checking for updates...",
|
||||
"检查并更新": "Check & Update",
|
||||
"更新": "Update",
|
||||
"已是最新": "Up to date",
|
||||
"可更新": "Update Available",
|
||||
"管理更新": "Manage Updates",
|
||||
"立即更新": "Update Now",
|
||||
"确认更新": "Confirm Update",
|
||||
"回滚": "Rollback",
|
||||
"确认回滚": "Confirm Rollback",
|
||||
"恢复": "Restore",
|
||||
"删除": "Delete",
|
||||
"永久删除": "Permanently Delete",
|
||||
"清空回收站": "Empty Trash",
|
||||
"移到回收站": "Move to Trash",
|
||||
"移入回收站": "Move to Trash",
|
||||
"导入": "Import",
|
||||
"导入技能": "Import Skill",
|
||||
"导出": "Export",
|
||||
"导出技能": "Export Skill",
|
||||
"新建技能": "New Skill",
|
||||
"创建技能": "Create Skill",
|
||||
"刷新": "Refresh",
|
||||
"永久删除": "Delete Forever",
|
||||
"清空回收站": "Empty Trash",
|
||||
"删除": "Delete",
|
||||
"启用": "Enable",
|
||||
"停用": "Disable",
|
||||
"已启用": "Enabled",
|
||||
"已停用": "Disabled",
|
||||
"全部启用": "Enable All",
|
||||
"全部停用": "Disable All",
|
||||
"启停来源": "Toggle Source",
|
||||
"启停技能": "Toggle Skill",
|
||||
"安装": "Install",
|
||||
"已安装": "Installed",
|
||||
"卸载": "Uninstall",
|
||||
"查看": "View",
|
||||
"查看详情": "View Details",
|
||||
"查看说明": "View Docs",
|
||||
"保存": "Save",
|
||||
"查看": "View",
|
||||
"更多": "More",
|
||||
"取消": "Cancel",
|
||||
"保存": "Save",
|
||||
"关闭": "Close",
|
||||
"确定": "OK",
|
||||
"复制": "Copy",
|
||||
"已复制": "Copied",
|
||||
"复制成功": "Copied",
|
||||
"搜索": "Search",
|
||||
"重试": "Retry",
|
||||
"选择文件": "Choose File",
|
||||
"选择文件夹": "Choose Folder",
|
||||
"问题反馈": "Issues",
|
||||
"设置": "Settings",
|
||||
|
||||
// Status & Counts
|
||||
"全部": "All",
|
||||
// Status & Filters
|
||||
"全部来源": "All Sources",
|
||||
"全部状态": "All Statuses",
|
||||
"调用状态": "Invocation Status",
|
||||
"状态": "Status",
|
||||
"类型": "Type",
|
||||
"来源": "Source",
|
||||
"被覆盖": "Shadowed",
|
||||
"名称": "Name",
|
||||
"版本": "Version",
|
||||
"操作": "Actions",
|
||||
"来源": "Source",
|
||||
"路径": "Path",
|
||||
"描述": "Description",
|
||||
"详情": "Details",
|
||||
"大小": "Size",
|
||||
"类型": "Type",
|
||||
"操作": "Actions",
|
||||
"详情": "Details",
|
||||
"描述": "Description",
|
||||
"简介": "Description",
|
||||
"正文": "Body",
|
||||
"源文件": "Source File",
|
||||
"更新时间": "Updated",
|
||||
"创建时间": "Created",
|
||||
"个技能": " skills",
|
||||
"个已启用": " enabled",
|
||||
"个已停用": " disabled",
|
||||
"个被覆盖": " shadowed",
|
||||
"个待处理技能": " pending skills",
|
||||
"个诊断异常": " diagnostic issues",
|
||||
"删除于": "Deleted at",
|
||||
"来源:": "Source: ",
|
||||
|
||||
// Form Fields & Hints
|
||||
"技能名称": "Skill Name",
|
||||
"技能描述": "Description",
|
||||
"技能内容": "Skill Body",
|
||||
"技能正文": "Skill Body",
|
||||
"仓库地址": "Repository URL",
|
||||
"搜索技能": "Search skills...",
|
||||
"搜索技能名称、描述或来源": "Search skills by name, description, or source...",
|
||||
"请输入仓库地址": "Enter repository URL...",
|
||||
"没有找到匹配的技能": "No matching skills found",
|
||||
"暂无技能": "No skills available",
|
||||
"回收站为空": "Trash is empty",
|
||||
// Empty & Loading States
|
||||
"正在加载技能…": "Loading skills...",
|
||||
"正在加载技能...": "Loading skills...",
|
||||
"正在加载…": "Loading...",
|
||||
"正在加载...": "Loading...",
|
||||
"加载中…": "Loading...",
|
||||
"加载中...": "Loading...",
|
||||
"正在读取说明…": "Loading documentation...",
|
||||
"正在读取说明...": "Loading documentation...",
|
||||
"没有匹配的技能。": "No matching skills.",
|
||||
"没有匹配的技能": "No matching skills",
|
||||
"该来源目录不存在或暂时没有技能。": "This source directory does not exist or has no skills.",
|
||||
"回收站为空。": "Trash is empty.",
|
||||
"回收站为空": "Trash is empty",
|
||||
"暂无技能": "No skills available",
|
||||
"暂无数据": "No data available",
|
||||
"未提供简介": "No description provided",
|
||||
"未发现诊断问题。": "No diagnostic issues found.",
|
||||
|
||||
// Settings & Configuration
|
||||
// Upload & Import Modal
|
||||
"拖拽技能到此处": "Drop skill here",
|
||||
"支持 .zip、技能文件夹或单个 SKILL.md": "Supports .zip, a skill folder, or single SKILL.md",
|
||||
"文件要求": "Requirements",
|
||||
"压缩包或文件夹需包含 SKILL.md": "Archives or folders must contain SKILL.md",
|
||||
"SKILL.md 需包含 YAML 格式的技能名称和描述": "SKILL.md must include YAML name and description",
|
||||
"导入时复制完整内容,不修改原始来源": "Import copies full content without modifying source",
|
||||
"导入到全局 DSH": "Import to global DSH",
|
||||
"移除所选内容": "Remove selection",
|
||||
"请选择 .zip 或单个 SKILL.md。": "Please choose a .zip or single SKILL.md.",
|
||||
"所选文件夹中没有找到 SKILL.md。": "No SKILL.md found in the selected folder.",
|
||||
|
||||
// Creation Form
|
||||
"例如 code-review-helper": "e.g. code-review-helper",
|
||||
"一句话说明什么时候使用": "One sentence describing when to use it",
|
||||
"正文(Markdown)": "Body (Markdown)",
|
||||
"写下技能要遵循的指令、步骤和边界…": "Write the instructions, steps, and boundaries...",
|
||||
"创建和导入均保存到全局 DSH,跨项目可用。": "Created and imported skills are saved to global DSH and available across projects.",
|
||||
|
||||
// General Settings & UI
|
||||
"通用设置": "General Settings",
|
||||
"模型设置": "Model Settings",
|
||||
"外观设置": "Appearance",
|
||||
"语言设置": "Language",
|
||||
"快捷键": "Shortcuts",
|
||||
"关于": "About",
|
||||
"版本号": "Version",
|
||||
"检查新版本": "Check for updates",
|
||||
"语言": "Language",
|
||||
"简体中文": "Simplified Chinese",
|
||||
"英文": "English",
|
||||
"深色模式": "Dark Mode",
|
||||
"浅色模式": "Light Mode",
|
||||
"跟随系统": "Follow System",
|
||||
"退出": "Exit",
|
||||
"重启": "Restart",
|
||||
|
||||
// Common Notification & Messages
|
||||
"操作成功": "Operation successful",
|
||||
"操作失败": "Operation failed",
|
||||
"更新成功": "Updated successfully",
|
||||
"删除成功": "Deleted successfully",
|
||||
"创建成功": "Created successfully",
|
||||
"导入成功": "Imported successfully",
|
||||
"备份已保存": "Backup saved",
|
||||
"正在加载": "Loading",
|
||||
// Model & Chat Concepts
|
||||
"模型": "Models",
|
||||
"会话": "Sessions",
|
||||
"新会话": "New Session",
|
||||
"历史会话": "History",
|
||||
"工作区": "Workspace",
|
||||
"暂无会话": "No sessions",
|
||||
"发送": "Send",
|
||||
"停止生成": "Stop generating",
|
||||
"重新生成": "Regenerate",
|
||||
"耗时": "Duration",
|
||||
"步骤": "Steps",
|
||||
"轮次": "Turns",
|
||||
};
|
||||
|
||||
// Regex phrase replacements for dynamic strings (e.g. "3 个技能")
|
||||
export const PHRASE_PATTERNS: Array<[RegExp, string]> = [
|
||||
// Sorted array of [chinese, english] pairs, longest Chinese phrases first
|
||||
// (ensures compound phrases are matched before single words)
|
||||
export const SORTED_PAIRS: Array<[string, string]> = Object.entries(DICTIONARY).sort(
|
||||
(a, b) => b[0].length - a[0].length
|
||||
);
|
||||
|
||||
// Dynamic pattern replacements
|
||||
export const DYNAMIC_PATTERNS: Array<[RegExp, string]> = [
|
||||
[/(\d+)\s*个技能/g, "$1 skills"],
|
||||
[/(\d+)\s*个已启用/g, "$1 enabled"],
|
||||
[/(\d+)\s*个已停用/g, "$1 disabled"],
|
||||
[/(\d+)\s*个被覆盖/g, "$1 shadowed"],
|
||||
[/(\d+)\s*个诊断异常/g, "$1 issues"],
|
||||
[/(\d+)\s*个文件/g, "$1 files"],
|
||||
[/(\d+)\s*个待处理技能/g, "$1 pending skills"],
|
||||
[/(\d+)\s*个诊断异常/g, "$1 diagnostic issues"],
|
||||
[/正在检查更新[…\.]*/g, "Checking for updates..."],
|
||||
[/正在读取说明[…\.]*/g, "Loading documentation..."],
|
||||
[/暂无可用技能/g, "No skills available"],
|
||||
[/暂无数据/g, "No data available"],
|
||||
[/暂无更新/g, "No updates available"],
|
||||
[/无法恢复/g, "cannot be restored"],
|
||||
[/已被覆盖/g, "is shadowed"],
|
||||
[/移入回收站/g, "Move to Trash"],
|
||||
[/永久删除/g, "Permanently Delete"],
|
||||
[/来源:/g, "Source: "],
|
||||
[/创建于\s*([0-9:\-\/ ]+)/g, "Created at $1"],
|
||||
[/更新于\s*([0-9:\-\/ ]+)/g, "Updated at $1"],
|
||||
[/删除于\s*([0-9:\-\/ ]+)/g, "Deleted at $1"],
|
||||
[/被\s*(.+?)\s*覆盖/g, "Shadowed by $1"],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { DICTIONARY, PHRASE_PATTERNS } from "./dictionary.js";
|
||||
import { DICTIONARY, SORTED_PAIRS, DYNAMIC_PATTERNS } from "./dictionary.js";
|
||||
|
||||
export const name = "dsh-plugin-english-client";
|
||||
export const inject = ["locale", "slots"];
|
||||
export const inject = [];
|
||||
|
||||
const CHINESE_REGEX = /[\u4e00-\u9fa5]/;
|
||||
|
||||
/**
|
||||
* Translates a single text string using dictionary and pattern matching.
|
||||
*/
|
||||
function translateText(text: string): string {
|
||||
export function translateText(text: string): string {
|
||||
if (!text || !CHINESE_REGEX.test(text)) return text;
|
||||
|
||||
const trimmed = text.trim();
|
||||
|
|
@ -17,125 +17,166 @@ function translateText(text: string): string {
|
|||
}
|
||||
|
||||
let result = text;
|
||||
// Apply regex patterns
|
||||
for (const [pattern, replacement] of PHRASE_PATTERNS) {
|
||||
// Dynamic regex patterns (counts, numbers, time)
|
||||
for (const [pattern, replacement] of DYNAMIC_PATTERNS) {
|
||||
result = result.replace(pattern, replacement);
|
||||
}
|
||||
|
||||
// Exact phrase search for substrings
|
||||
for (const [cn, en] of Object.entries(DICTIONARY)) {
|
||||
// Exact phrase search for substrings (longest phrases first)
|
||||
for (const [cn, en] of SORTED_PAIRS) {
|
||||
if (result.includes(cn)) {
|
||||
result = result.replaceAll(cn, en);
|
||||
if (!CHINESE_REGEX.test(result)) break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
let isTranslating = false;
|
||||
|
||||
/**
|
||||
* Recursively translates text nodes inside a DOM element.
|
||||
* Translates an element and all its children using native TreeWalker.
|
||||
*/
|
||||
function translateNode(node: Node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
const val = node.nodeValue;
|
||||
export function translateElement(root: Node) {
|
||||
if (!root) return;
|
||||
|
||||
if (root.nodeType === Node.TEXT_NODE) {
|
||||
const val = root.nodeValue;
|
||||
if (val && CHINESE_REGEX.test(val)) {
|
||||
const translated = translateText(val);
|
||||
if (translated !== val) {
|
||||
node.nodeValue = translated;
|
||||
root.nodeValue = translated;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const el = node as HTMLElement;
|
||||
const tagName = el.tagName;
|
||||
// Don't translate code blocks or scripts
|
||||
if (tagName === "SCRIPT" || tagName === "STYLE" || tagName === "PRE" || tagName === "CODE") {
|
||||
return;
|
||||
}
|
||||
if (root.nodeType !== Node.ELEMENT_NODE) return;
|
||||
const el = root as HTMLElement;
|
||||
|
||||
// Translate common attributes
|
||||
for (const attr of ["title", "placeholder", "aria-label", "alt"]) {
|
||||
const val = el.getAttribute(attr);
|
||||
if (val && CHINESE_REGEX.test(val)) {
|
||||
el.setAttribute(attr, translateText(val));
|
||||
const tag = el.tagName;
|
||||
if (tag === "SCRIPT" || tag === "STYLE" || tag === "PRE" || tag === "CODE") return;
|
||||
|
||||
// Translate attributes
|
||||
for (const attr of ["placeholder", "title", "aria-label"]) {
|
||||
const val = el.getAttribute(attr);
|
||||
if (val && CHINESE_REGEX.test(val)) {
|
||||
el.setAttribute(attr, translateText(val));
|
||||
}
|
||||
}
|
||||
|
||||
// Fast native TreeWalker
|
||||
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
|
||||
let textNode = walker.nextNode();
|
||||
while (textNode) {
|
||||
const val = textNode.nodeValue;
|
||||
if (val && CHINESE_REGEX.test(val)) {
|
||||
const parentTag = textNode.parentElement?.tagName;
|
||||
if (parentTag !== "SCRIPT" && parentTag !== "STYLE" && parentTag !== "PRE" && parentTag !== "CODE") {
|
||||
const translated = translateText(val);
|
||||
if (translated !== val) {
|
||||
textNode.nodeValue = translated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Translate child nodes
|
||||
let child = el.firstChild;
|
||||
while (child) {
|
||||
translateNode(child);
|
||||
child = child.nextSibling;
|
||||
}
|
||||
textNode = walker.nextNode();
|
||||
}
|
||||
}
|
||||
|
||||
export function apply(ctx: any) {
|
||||
// 1. Hook DSH ctx.locale to inject English fallback into any registered namespace
|
||||
if (ctx.locale && typeof ctx.locale.register === "function") {
|
||||
ctx.effect(() => {
|
||||
// Whenever locale state changes or plugins register dictionaries,
|
||||
// hook locale to return English translations for known Chinese keys
|
||||
if (typeof ctx.locale.subscribe === "function") {
|
||||
ctx.locale.subscribe(() => {
|
||||
if (typeof document !== "undefined") {
|
||||
translateNode(document.body);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, "dsh-plugin-english: locale observer");
|
||||
}
|
||||
function initTranslator() {
|
||||
if (typeof document === "undefined" || (window as any).__DSH_TRANSLATOR_ACTIVE__) return;
|
||||
(window as any).__DSH_TRANSLATOR_ACTIVE__ = true;
|
||||
|
||||
// 2. High-performance DOM MutationObserver for real-time translation
|
||||
if (typeof document !== "undefined") {
|
||||
let pending = false;
|
||||
const queue = new Set<Node>();
|
||||
console.log("[dsh-plugin-english] Universal English translator activated.");
|
||||
|
||||
const flushTranslations = () => {
|
||||
pending = false;
|
||||
const queue = new Set<Node>();
|
||||
let frameRequested = false;
|
||||
|
||||
const processQueue = () => {
|
||||
frameRequested = false;
|
||||
isTranslating = true;
|
||||
try {
|
||||
const nodes = Array.from(queue);
|
||||
queue.clear();
|
||||
for (const n of nodes) {
|
||||
if (n.isConnected) {
|
||||
translateNode(n);
|
||||
translateElement(n);
|
||||
}
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
isTranslating = false;
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const m of mutations) {
|
||||
if (m.type === "childList") {
|
||||
for (let i = 0; i < m.addedNodes.length; i++) {
|
||||
const n = m.addedNodes[i];
|
||||
if (n.nodeType === Node.TEXT_NODE || n.nodeType === Node.ELEMENT_NODE) {
|
||||
queue.add(n);
|
||||
}
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
if (isTranslating) return;
|
||||
|
||||
for (const m of mutations) {
|
||||
if (m.type === "childList") {
|
||||
for (let i = 0; i < m.addedNodes.length; i++) {
|
||||
const n = m.addedNodes[i];
|
||||
if (n.nodeType === Node.TEXT_NODE || n.nodeType === Node.ELEMENT_NODE) {
|
||||
queue.add(n);
|
||||
}
|
||||
} else if (m.type === "characterData") {
|
||||
queue.add(m.target);
|
||||
} else if (m.type === "attributes") {
|
||||
}
|
||||
} else if (m.type === "characterData") {
|
||||
if (m.target.nodeValue && CHINESE_REGEX.test(m.target.nodeValue)) {
|
||||
queue.add(m.target);
|
||||
}
|
||||
} else if (m.type === "attributes") {
|
||||
queue.add(m.target);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pending && queue.size > 0) {
|
||||
pending = true;
|
||||
window.requestAnimationFrame(flushTranslations);
|
||||
}
|
||||
});
|
||||
if (!frameRequested && queue.size > 0) {
|
||||
frameRequested = true;
|
||||
requestAnimationFrame(processQueue);
|
||||
}
|
||||
});
|
||||
|
||||
// Initial translation pass on document body
|
||||
translateNode(document.body);
|
||||
|
||||
// Observe document for dynamically rendered modals, dialogs, and components
|
||||
observer.observe(document.body, {
|
||||
// Observe root
|
||||
const root = document.documentElement || document.body;
|
||||
if (root) {
|
||||
translateElement(root);
|
||||
observer.observe(root, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["title", "placeholder", "aria-label", "alt"],
|
||||
attributeFilter: ["placeholder", "title", "aria-label"],
|
||||
});
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
translateElement(document.body);
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["placeholder", "title", "aria-label"],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Periodic safety check every 1s for any dynamically inserted lazy elements
|
||||
setInterval(() => {
|
||||
if (document.body && !isTranslating) {
|
||||
isTranslating = true;
|
||||
try {
|
||||
translateElement(document.body);
|
||||
} finally {
|
||||
isTranslating = false;
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Start immediately when the bundle loads in browser
|
||||
if (typeof window !== "undefined") {
|
||||
initTranslator();
|
||||
}
|
||||
|
||||
export function apply(ctx: any) {
|
||||
initTranslator();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue