我写了一个 hindsight 的翻译脚本,分享一下

hindsight 作为一款开源的 agent 记忆库实现方案,但是我部署以后发现他的 web-ui 没有中文版,想到未来估计会支持,就叫 AI 做了一个翻译的猴油脚本,有用这个记忆框架的可以先用着,等以后官方支持中文。 // ==UserScript== // @name Hindsight 中文...
我写了一个 hindsight 的翻译脚本,分享一下
我写了一个 hindsight 的翻译脚本,分享一下

hindsight 作为一款开源的 agent 记忆库实现方案,但是我部署以后发现他的 web-ui 没有中文版,想到未来估计会支持,就叫 AI 做了一个翻译的猴油脚本,有用这个记忆框架的可以先用着,等以后官方支持中文。

// ==UserScript==
// @name         Hindsight 中文界面增强
// @namespace    https://forest.example.local
// @version      0.6.1
// @description  将 Hindsight Control Plane 的常用英文界面翻译为更自然的简体中文。
// @author       OpenClaw
// @match        *://192.168.0.99:9999/*
// @match        *://192.168.0.99:9999
// @match        *://localhost:9999/*
// @match        *://localhost:9999
// @match        *://127.0.0.1:9999/*
// @match        *://127.0.0.1:9999
// @include      /^https?:\/\/(192\.168\.0\.99|localhost|127\.0\.0\.1):9999(\/.*)?$/
// @run-at       document-start
// @grant        unsafeWindow
// @noframes
// ==/UserScript==

(function () {
  'use strict';

  const DEBUG = false;
  const ROOT_ATTR = 'data-hindsight-zh-active';
  const ROOT_CLASS = 'hindsight-zh-ready';
  const processedText = new WeakMap();
  const processedElements = new WeakMap();
  let scheduled = false;
  let observerStarted = false;

  const TEXT_MAP = new Map([
    ['Actions', '操作'],
    ['Add Document', '添加文档'],
    ['Add New Document', '添加新文档'],
    ['Add a new document to memory bank:', '向记忆库添加新文档:'],
    ['Add strategy', '添加策略'],
    ['Add Webhook', '添加 Webhook'],
    ['Add', '添加'],
    ['Audit Logs', '审计日志'],
    ['All actions', '全部操作'],
    ['All transports', '全部传输方式'],
    ['All time', '全部时间'],
    ['Any (incl. untagged)', '任意(含未分类)'],
    ['Any (strict)', '任意(严格)'],
    ['All (incl. untagged)', '全部(含未分类)'],
    ['All (strict)', '全部(严格)'],
    ['Background Operations', '后台操作'],
    ['Bank Configuration', '记忆库配置'],
    ['Applied automatically when no strategy is specified on a request.', '请求未指定策略时,自动使用此默认策略。'],
    ['All links between visible nodes are shown', '显示所有可见节点之间的关联'],
    ['Analyze memory recall with detailed trace information and retrieval methods.', '通过详细调用链路和检索方式,分析记忆召回过程。'],
    ['Are you sure you want to delete', '确定要删除'],
    ['Are you sure you want to delete the memory bank', '确定要删除记忆库'],
    ['Are you sure you want to reset all configuration overrides for', '确认要重置该记忆库的所有配置覆盖项吗?'],
    ['Attempts', '尝试次数'],
    ['Auto-refresh after consolidation', '整合完成后自动刷新'],
    ['Click a node to see details', '点击节点查看详情'],
    ['connections. Click a memory to view', '关联关系。点击记忆可查看'],
    ['Audit Logs', '审计日志'],
    ['Auto Refresh', '自动刷新'],
    ['Bank Configuration', '记忆库配置'],
    ['Bank configuration is disabled', '记忆库配置功能未启用'],
    ['causal', '因果关联'],
    ['Budget', '预算'],
    ['Cancel', '取消'],
    ['Canvas-rendered memory map with', '基于 Canvas 渲染的记忆地图,支持'],
    ['Chunk Text', '分块文本'],
    ['Chunk Size', '分块大小'],
    ['chunks', '仅分块(无 LLM)'],
    ['Clear', '清空'],
    ['Clear Observations', '清空观察结论'],
    ['Clearing...', '清空中...'],
    ['click to filter', '点击筛选'],
    ['Close', '关闭'],
    ['Collapse', '收起'],
    ['Consolidating...', '整合中...'],
    ['Consolidation', '整合'],
    ['Configuration', '详细配置'],
    ['connections. Click a memory to view', '关联关系。点击记忆可查看'],
    ['Constellation', '星图视图'],
    ['Constellation View', '星图视图'],
    ['Control how facts are synthesized into durable observations', '控制事实如何整合为可长期复用的观察结论。'],
    ['Created', '创建时间'],
    ['Created At', '创建时间'],
    ['Content *', '内容 *'],
    ['Context', '上下文'],
    ['Copy Directive', '复制指令'],
    ['Create a mental model by running a query. The content will be auto-generated and can be refreshed later.', '通过执行查询创建心智模型,内容自动生成,之后可随时刷新。'],
    ['Create Mental Model', '创建心智模型'],
    ['Create Webhook', '创建 Webhook'],
    ['Created', '创建时间'],
    ['Created At', '创建时间'],
    ['custom', '自定义规则'],
    ['Custom Headers', '自定义请求头'],
    ['Default (all_strict when tags set)', '默认(设置标签时采用 all_strict)'],
    ['Default (inherit)', '默认(继承)'],
    ['Delta mode applies minimal changes to the existing content. Falls back to a full rewrite on the first refresh and whenever the source query changes.', 'Delta 模式对现有内容进行最小化修改。首次刷新或源查询变更时,将回退为完全重写。'],
    ['Default extraction settings and named strategies. Pass a strategy name on retain requests to override defaults per-item.', '配置默认提取规则与命名策略。调用 retain 时可按策略名覆盖单条请求的默认设置。'],
    ['Default strategy', '默认策略'],
    ['Delete', '删除'],
    ['Directive Name', '指令名称'],
    ['Directive content', '指令内容'],
    ['Directive text', '指令文本'],
    ['Duration', '耗时'],
    ['Delete Bank', '删除记忆库'],
    ['Delete Directive', '删除指令'],
    ['Delete Document', '删除文档'],
    ['Delete Memory Bank', '删除记忆库'],
    ['Delete webhook', '删除 Webhook'],
    ['Directives', '指令'],
    ['Directive Details', '指令详情'],
    ['Discard', '放弃'],
    ['Display', '显示'],
    ['Document Deleted', '文档已删除'],
    ['Document ID', '文档 ID'],
    ['Documents', '文档'],
    ['drag to pan, hover to explore entity', '拖拽可平移,悬停可查看实体'],
    ['Duration', '耗时'],
    ['Edit webhook', '编辑 Webhook'],
    ['Enable automatic consolidation of facts into observations', '开启后,系统会自动把事实整合成观察结论。'],
    ['Enable Observations', '启用观察结论'],
    ['Enabled', '启用'],
    ['Empathetic', '更共情'],
    ['Empathy', '共情程度'],
    ['Entities', '实体'],
    ['Enter a question above to query the memory bank and generate a disposition-aware response.', '在上方输入问题,查询记忆库并生成符合记忆库画像的回复。'],
    ['Entity details', '实体详情'],
    ['entries', '条记录'],
    ['Error', '错误'],
    ['Event Date', '事件日期'],
    ['Event Types', '事件类型'],
    ['Experience', '经验经历'],
    ['Export Template', '导出模板'],
    ['Extraction Mode', '提取模式'],
    ['Failed at', '失败时间'],
    ['Full — regenerate from scratch each refresh', '完整模式——每次刷新都从头重新生成'],
    ['Delta — surgical edits, preserve unchanged content', 'Delta 模式——精细化编辑,保留未更改的内容'],
    ['Failed to copy:', '复制失败:'],
    ['Failed to export template', '导出模板失败'],
    ['Failed to fetch memory details:', '获取记忆详情失败:'],
    ['Filter mental models by name, query, or content...', '按名称、查询或内容筛选心智模型...'],
    ['First Seen', '首次出现'],
    ['Flexible', '更灵活'],
    ['General', '概览'],
    ['Cancel', '取消'],
    ['Create', '创建'],
    ['Upload Files', '上传文件'],
    ['Click to select files or drag and drop', '点击选择文件,或拖拽上传'],
    ['Upload ', '上传 '],
    ['Uploading...', '上传中...'],
    ['Adding...', '添加中...'],
    ['Add Document', '添加文档'],
    ['Override the bank\'s default extraction strategy for this document.', '覆盖此记忆库的默认提取策略。'],
    ['Strategy name (optional)...', '策略名称(可选)...'],
    ['Optional document identifier...', '可选文档标识...'],
    ['Optional context...', '上下文(可选)...'],
    ['Optional context about this document...', '文档上下文(可选)...'],
    ['Enter the document content...', '输入文档内容...'],
    ['Overview', '概览'],
    ['Options', '选项'],
    ['any — OR matching, includes untagged', 'any——或匹配,含无标签'],
    ['all — AND matching, includes untagged', 'all——与匹配,含无标签'],
    ['any_strict — OR matching, strict', 'any_strict——或匹配,严格'],
    ['all_strict — AND matching, strict', 'all_strict——与匹配,严格'],
    ['Graph', '关系图'],
    ['Hide panel', '隐藏侧栏'],
    ['High', '高'],
    ['ID', '标识符'],
    ['Source', '来源'],
    ['Tags', '标签'],
    ['Strategy', '策略'],
    ['Low', '低'],
    ['Mid', '中'],
    ['High-level purpose that guides what the bank should optimize for in reflect.', '定义记忆库在 reflect 时应优先追求的高层目标。'],
    ['How aggressively to extract facts. concise = selective, verbose = capture everything, verbatim = store chunks as-is (still extract entities/time), chunks = no LLM, custom = write your own rules.', '控制事实提取强度。concise = 选择性提取,verbose = 尽量全面提取,verbatim = 原样存储分块(仍提取实体/时间),chunks = 不使用 LLM,custom = 自定义提取规则。'],
    ['How detached vs empathic the agent should sound', '控制表达风格是更偏向克制理性,还是更富有共情。'],
    ['How literally vs flexibly to interpret instructions and evidence', '面对指令与证据时,是更按字面理解,还是更灵活解释。'],
    ['How skeptical vs trusting when evaluating claims', '在评估信息时,更偏向审慎质疑还是倾向相信。'],
    ['HTTP', 'HTTP'],
    ['ID', 'ID'],
    ['Index', '序号'],
    ['Items', '条目数'],
    ['Last Seen', '最近出现'],
    ['Links', '关联'],
    ['Literal', '更字面'],
    ['Literalism', '字面倾向'],
    ['LLM Batch Size', 'LLM 批处理大小'],
    ['Limited to 50 nodes for performance. Total:', '为保障性能,最多显示 50 个节点。总计:'],
    ['Loading...', '加载中...'],
    ['Loading memories...', '正在加载记忆...'],
    ['Manage bank settings, profile, and operations.', '统一管理记忆库的设置、画像和后台操作。'],
    ['Manage documents and retain new memories.', '管理文档内容,并将新信息写入记忆库。'],
    ['Manage webhook endpoints to receive event notifications from this memory bank.', '管理 Webhook 接收地址,用于订阅这个记忆库的事件通知。'],
    ['Match all selected tags', '匹配全部已选标签'],
    ['Match any selected tag', '匹配任意已选标签'],
    ['Max nodes', '最大节点数'],
    ['Max Observations Per Scope', '每个作用域的观察上限'],
    ['Memory Composition', '记忆构成'],
    ['Memory Details', '记忆详情'],
    ['Memory Units', '记忆单元数'],
    ['Mental model refreshed', '心智模型已刷新'],
    ['Mental Models', '心智模型'],
    ['Mentions', '提及次数'],
    ['Mentioned', '提及时间'],
    ['Metadata', '元数据'],
    ['Method', '方法'],
    ['memories without dates in Table View.', '条记忆在表格视图中无日期。'],
    ['Mission', '使命说明'],
    ['No memories have occurred_at dates.', '暂无设置发生时间的记忆。'],
    ['Other Mental Models', '其他心智模型'],
    ['No data for this period', '该时段暂无数据'],
    ['No data available', '暂无可用数据'],
    ['No documents found', '未找到文档'],
    ['No mental models match your filter', '没有符合筛选条件的心智模型'],
    ['No mental models yet. Create a mental model to generate and save a summary from your memories.', '暂无心智模型。创建后可从记忆中生成并保存可复用的摘要。'],
    ['No raw payload stored for this operation.', '该操作没有保存原始负载。'],
    ['No Timeline Data', '暂无时间轴数据'],
    ['No webhooks configured. Add a webhook to receive event notifications.', '尚未配置 Webhook。请添加以接收事件通知。'],
    ['No audit logs found', '未找到审计日志'],
    ['No bank selected', '尚未选择记忆库'],
    ['Nodes', '节点'],
    ['No — skip chunks (smaller prompt)', '否——跳过分块文本(提示词更短)'],
    ['Objective facts about the world received from external sources.', '来自外部来源的客观事实与背景信息。'],
    ['Observations', '观察结论'],
    ['Observations cleared successfully', '观察结论已成功清空'],
    ['Observations feature is not enabled', '观察结论功能未启用'],
    ['Observations Not Enabled', '观察结论功能未开启'],
    ['Observations consolidation is disabled on this server. Set', '当前服务器未开启观察结论整合能力。请设置'],
    ['Off', '关闭'],
    ['Occurred (end)', '发生时间(结束)'],
    ['Occurred (start)', '发生时间(开始)'],
    ['Original Text', '原始文本'],
    ['Overview statistics and background operations for this memory bank.', '查看记忆库的概览统计、后台任务与画像信息。'],
    ['Performance', '性能'],
    ['Please select a memory bank from the dropdown above to view its profile.', '请先从上方下拉框选择一个记忆库,再查看其画像信息。'],
    ['Query Parameters', '查询参数'],
    ['Recovering...', '恢复中...'],
    ['Refetch Entities', '重新获取实体'],
    ['Refine', '优化'],
    ['Recover Consolidation', '恢复整合'],
    ['Reflect', '深度思考'],
    ['Refresh timeout', '刷新超时'],
    ['Refresh webhooks', '刷新 Webhook'],
    ['Request Volume', '请求量'],
    ['Reset Configuration', '重置配置'],
    ['Resetting...', '重置中...'],
    ['Retain', '写入记忆'],
    ['Run Consolidation', '运行整合'],
    ["Run an agentic loop that autonomously gathers evidence and reasons through the lens of the bank's disposition to generate contextual responses.", '运行带自主搜证能力的思考流程,结合记忆库画像生成更贴合上下文的回答。'],
    ['Save Changes', '保存更改'],
    ['Saving...', '保存中...'],
    ['Select a memory bank from the dropdown above to get started.', '请先从上方的下拉列表中选择记忆库。'],
    ['Select a memory bank to view mental models.', '请先选择记忆库再查看心智模型。'],
    ['Show labels', '显示标签'],
    ['Show panel', '显示侧栏'],
    ['Signing secret', '签名密钥'],
    ['Size', '大小'],
    ['Size of text chunks for processing (characters)', '用于处理的文本分块大小(字符数)'],
    ['Skeptical', '更倾向审慎'],
    ['Skepticism', '怀疑程度'],
    ['Source Facts Max Tokens', '来源事实最大 Token 数'],
    ['Source Facts Max Tokens Per Observation', '每条观察的来源事实 Token 上限'],
    ['Source Query', '源查询语句'],
    ['Sources', '来源'],
    ['spatial label deconfliction. Scroll to zoom,', '空间标签避让。滚轮可缩放,'],
    ['Status', '状态'],
    ['Success', '成功'],
    ['Table', '列表视图'],
    ['Tags Match', '标签匹配方式'],
    ['Tag Groups', '标签分组'],
    ["The bank's own actions, interactions, and first-person experiences.", '记忆库自身的行为、互动过程及第一视角经历。'],
    ['This action cannot be undone. All memories, entities, documents, and the bank profile will be permanently deleted.', '此操作无法撤销。记忆、实体、文档以及记忆库画像都会被永久删除。'],
    ['This is a parent operation — the raw payload is stored on each sub-batch. Open a child operation to inspect its payload.', '这是一个父级操作;原始负载保存在各个子批次中,请打开子操作查看。'],
    ['This will delete all consolidated knowledge. Observations will be regenerated the next time consolidation runs.', '将清除所有已整合的观察结论;下次整合时会重新生成。'],
    ['Text', '文本'],
    ['Time', '时间'],
    ['Timeline', '时间轴'],
    ['Transport', '传输方式'],
    ['Upload Files', '上传文件'],
    ['Timeout (seconds)', '超时时间(秒)'],
    ['to enable per-bank configuration.', '后即可启用按记忆库单独配置。'],
    ['to enable.', '后启用。'],
    ['total memories', '记忆总数'],
    ['Traits that shape the reasoning and perspective', '这些特质共同影响推理方式和表达视角。'],
    ['Transport', '传输方式'],
    ['Trusting', '更倾向相信'],
    ['Skeptical', '更倾向怀疑'],
    ['Flexible', '更灵活'],
    ['Literal', '更字面'],
    ['Detached', '更疏离'],
    ['Empathetic', '更共情'],
    ['How skeptical vs trusting when evaluating claims', '评估信息时的怀疑与信任倾向'],
    ['How literally to interpret information', '对信息的解读方式(字面 vs 灵活)'],
    ['How much to weight emotional context', '对情绪背景的重视程度'],
    ['Restrict which MCP tools this bank exposes to agents', '限制此记忆库向 Agent 暴露的 MCP 工具'],
    ['When off, all tools are available. When on, only the selected tools can be invoked for this bank.', '关闭时所有工具可用。开启时仅可调用此处选择的工具。'],
    ['Provider-specific model settings', '提供商专属模型配置'],
    ['Gemini / Vertex AI', 'Gemini / Vertex AI'],
    ['Safety settings', '安全阈值配置'],
    ['When off, Gemini\'s default safety thresholds are used. When on, configure thresholds per harm category.', '关闭时使用 Gemini 默认安全阈值。开启后可按危害类别配置阈值。'],
    ['Learn more', '了解更多'],
    ['Models', '模型'],
    ['MCP Tools', 'MCP 工具'],
    ['Restrict tools', '限制工具'],
    ['Shape how the bank reasons and responds in reflect operations', '定义此记忆库在深度思考时的推理方式与响应风格'],
    ['Default', '默认'],
    ['Type', '类型'],
    ['Update Mental Model', '更新心智模型'],
    ['Update the webhook configuration.', '更新此 Webhook 的配置。'],
    ['Updated', '更新时间'],
    ['URL', '地址'],
    ['User-curated summaries generated from queries — reusable knowledge snapshots that can be refreshed as memories evolve.', '由查询生成并可人工维护的知识摘要快照,可随记忆变化反复刷新。'],
    ['View and explore different types of memories stored in this memory bank.', '浏览并深入查看记忆库中保存的各类记忆。'],
    ['View audit trail of all operations performed on this memory bank.', '查看记忆库全部操作的审计轨迹。'],
    ['View deliveries', '查看投递记录'],
    ['Webhook Deliveries', 'Webhook 投递记录'],
    ['Webhooks', 'Webhook'],
    ['Welcome to Hindsight', '欢迎使用 Hindsight'],
    ['World Facts', '世界事实'],
    ['Experience', '经验经历'],
    ['Observations', '观察结论'],
    ['Mental Models', '心智模型'],
    ['Filter by text or context (press Enter)...', '按文本或上下文筛选(回车确认)...'],
    ['Filter by tag…', '按标签筛选...'],
    ['total memories', '条记忆'],
    ['Mentioned', '提及次数'],
    ['Constellation', '星图视图'],
    ['Graph', '关系图'],
    ['Table', '表格视图'],
    ['Timeline', '时间轴'],
    ['Fullscreen', '全屏'],
    ['Hide panel', '收起面板'],
    ['Constellation View', '星图视图'],
    ['Canvas-rendered memory map with spatial label deconfliction. Scroll to zoom, drag to pan, hover to explore entity connections. Click a memory to view details.', '基于 Canvas 渲染的记忆地图,支持空间标签解冲突。滚轮缩放,拖拽平移,悬停查看实体关联。点击记忆查看详情。'],
    ['Color by', '颜色分类'],
    ['Link types', '关联类型'],
    ['semantic', '语义'],
    ['temporal', '时序'],
    ['entity', '实体'],
    ['causal', '因果'],
    ['Nodes:', '节点数:'],
    ['Links:', '连接数:'],
    ['Yes — include raw chunk text', '是——包含原始分块文本'],
    ['Ready to Recall', '准备召回'],
    ['Enter a query above to search through your memories. Use filters to narrow down by fact type, budget, and more.', '在上方输入查询词搜索记忆。使用筛选条件(类型、优先级等)缩小范围。'],
    ['What would you like to recall?', '你想要召回什么?'],
    ['Any (incl. untagged)', '任意(含未分类)'],
    ['All (incl. untagged)', '全部(含未分类)'],
    ['Any (strict)', '任意(严格)'],
    ['All (strict)', '全部(严格)'],
    ['Low', '低'],
    ['Mid', '中'],
    ['High', '高'],
    ['Ready to 深度思考', '准备深度思考'],
    ['Enter a question above to query the memory bank and generate a disposition-aware response.', '在上方输入问题,查询记忆库并生成符合记忆库画像的回复。'],
    ['Explore entities (people, organizations, places) mentioned in memories.', '探索记忆中提及的实体(人物、组织、地点等)。'],
    ['Relations', '关系图'],
    ['List', '列表'],
    ['No mental models', '暂无心智模型'],
    ['Memory store', '记忆存储'],
    ['Memories', '记忆'],
    ['Memory composition', '记忆构成'],
    ['Link types', '关联类型'],
    ['Temporal', '时序关联'],
    ['Semantic', '语义关联'],
    ['Entity', '实体关联'],
    ['Done', '已完成'],
    ['Pending', '等待中'],
    ['Failed', '已失败'],
    ['Last', '最近'],
    ['Activity', '活动'],
    ['Memories by ingested time', '按摄入时间排列的记忆'],
    ['All', '全部'],
    ['Processing', '处理中'],
    ['Completed', '已完成'],
    ['Cancelled', '已取消'],
    ['Showing', '显示'],
    ['Directives', '指令'],
    ['Hard rules that must be followed during reflect', '在深度思考时必须遵守的硬性规则'],
    ['No directives yet. Directives are hard rules that must be followed during reflect.', '暂无指令。指令是在深度思考时必须遵守的硬性规则。'],
    ['Delete Mental Model', '删除心智模型'],
    ['Consolidated knowledge synthesized from facts — patterns, preferences, and learnings that emerge from accumulated evidence.', '从事实中综合提炼的知识——从积累的证据中形成的规律、偏好与经验教训。'],
    ['Color by', '颜色分类'],
    ['Add Mental Model', '添加心智模型'],
    ['Dashboard', '仪表盘'],
    ['Chunks', '分块'],
    ['Budget', '优先级'],
    ['Include Source', '包含来源'],
    ['Include Tools', '包含工具'],
    ['Exclude mental models', '排除心智模型'],
    ['Exclude IDs', '排除 ID'],
    ['Observation', '观察结论'],
    ['Fact types:', '事实类型:'],
    ['World', '世界'],
    ['documents', '文档'],
    ['by ingested time', '按摄入时间'],
    ['Ingested', '摄入时间'],
    ['Occurred', '发生时间'],
    ['Operations', '操作'],
    ['Background Operations', '后台操作'],
    ['All types', '全部类型'],
    ['Override the bank\'s default extraction strategy for this document.', '覆盖此记忆库的默认提取策略。'],
    ['Process in background (async)', '后台异步处理'],
    ['Previous', '上一页'],
    ['What this bank should pay attention to during extraction. Steers the LLM without replacing the extraction rules.', '定义记忆库在提取时应关注的内容,用于引导 LLM 行为,但不替代提取规则本身。'],
    ['e.g. Always include technical decisions, API design choices, and architectural trade-offs.', '例如:始终关注技术决策、API 设计选择和架构权衡。'],
    ['Extract regular named entities (people, places, concepts) alongside entity labels. Disable to restrict extraction to entity labels only.', '提取常规命名实体(人物、地点、概念等)并附带实体标签。关闭后仅提取实体标签。'],
    ['Extracted per memory at retain time. Every field is optional — only filled when clearly applicable.', '在 retain 时为每条记忆提取。每个字段均为可选——仅在明确适用时填写。'],
    ['No entity labels defined.', '尚未定义实体标签。'],
    ['Add label', '添加标签'],
    ['What this bank should synthesise into durable observations. Replaces the built-in consolidation rules — leave blank to use the server default.', '定义记忆库应将哪些内容整合为可长期复用的观察结论。覆盖内置整合规则,留空则使用服务器默认值。'],
    ['Recall', '召回'],
    ['memories by ingested time', '按摄入时间的记忆'],
    ['memories by mentioned time', '按提及时间的记忆'],
    ['memories by occurred time', '按发生时间的记忆'],
    ['Create Directive', '创建指令'],
    ['Directive are hard rules that must be followed during reflect.', '指令是深度思考时必须遵守的硬性规则。'],
    ['Name *', '名称 *'],
    ['e.g., Competitor Policy', '例如:竞争策略'],
    ['Rule *', '规则 *'],
    ['e.g., Never mention competitor products directly.', '例如:不要直接提及竞品。'],
    ['Tags (optional)', '标签(可选)'],
    ['e.g., project-x, team-alpha (comma-separated)', '例如:项目-x, 团队-alpha(逗号分隔)'],
    ['Add Directive', '添加指令'],
    ['No Bank Selected', '尚未选择记忆库'],
    ['Select a memory bank to start reflecting.', '请先选择记忆库再开始深度思考。'],
    ['Reflecting...', '深度思考中...'],
    ['Budget:', '优先级:'],
    ['Tokens:', 'Token 数:'],
    ['Filter by tags (comma-separated)', '按标签筛选(用逗号分隔)'],
    ['Exclude mental models', '排除心智模型'],
    ['Exclude IDs:', '排除 ID:'],
    ['Reflecting on memories...', '正在从记忆中思考...'],
    ['Input tokens:', '输入 Token:'],
    ['Output tokens:', '输出 Token:'],
    ['Tool calls:', '工具调用:'],
    ['LLM calls:', 'LLM 调用:'],
    ['Answer', '回答'],
    ['Trace', '调用链'],
    ['JSON', 'JSON'],
    ['Hard rules injected into prompts that the agent must follow', '注入提示词中的硬性规则,Agent 必须遵守'],
    ['Directive saved to', '指令已保存至'],
    ['e.g., Always respond in formal English...', '例如:始终以正式英语回复...'],
    ['Observations Created', '观察结论已创建'],
    ['New observations learned during this reflection', '本次深度思考中新产生的观察结论'],
    ['Execution Trace', '执行调用链'],
    ['iteration', '次迭代'],
    ['Based on', '基于'],
    ['Directives', '指令'],
    ['Mental Models', '心智模型'],
    ['World', '世界'],
    ['Experience', '经验'],
    ['No observations created.', '未产生观察结论。'],
    ['No directives available for this bank.', '该记忆库暂无指令。'],
    ['No mental models match your filter.', '没有符合筛选条件的心智模型。'],
    ['Create your first mental model to synthesize knowledge from your memories.', '创建你的第一个心智模型,从记忆库中综合提炼知识。'],
    ['Token usage', 'Token 使用量'],
    ['per iteration', '每次迭代'],
    ['ms', '毫秒'],
    ['LLM calls', 'LLM 调用'],
    ['Tool calls', '工具调用'],
    ['Directives used', '使用的指令'],
    ['Directives created', '创建的指令'],
    ['No directives were used or created in this reflection.', '本次深度思考未使用或创建任何指令。'],
    ['Mental Models', '心智模型'],
    ['Observations', '观察结论'],
    ['World Facts', '世界事实'],
    ['Experience Facts', '经验事实'],
    ['Dispositional context', '画像上下文'],
    ['Based on facts retrieved from the memory bank.', '基于从记忆库中召回的事实。'],
    ['Loading...', '加载中...'],
    ['Close', '关闭'],
    ['Tokens', 'Token'],
    ['Max', '最大'],
    ['min', '分钟'],
    ['sec', '秒'],
    ['Reflecting', '深度思考中'],
    ['Save', '保存'],
    ['Cancel', '取消'],
    ['Delete', '删除'],
    ['Name', '名称'],
    ['Description', '描述'],
    ['Content', '内容'],
    ['Tags', '标签'],
    ['Created at', '创建时间'],
    ['Updated at', '更新时间'],
    ['Status', '状态'],
    ['Type', '类型'],
    ['ID', 'ID'],
    ['World fact', '世界事实'],
    ['Experience fact', '经验事实'],
    ['Memory', '记忆'],
    ['Entities', '实体'],
    ['Documents', '文档'],
    ['Operations', '操作'],
    ['Chunks', '分块'],
    ['Sources', '来源'],
    ['Metadata', '元数据'],
    ['Preview', '预览'],
    ['Copy', '复制'],
    ['Edit', '编辑'],
    ['View', '查看'],
    ['Delete', '删除'],
    ['Refresh', '刷新'],
    ['Reload', '重新加载'],
    ['Load more', '加载更多'],
    ['Show less', '收起更多'],
    ['Search', '搜索'],
    ['Filter', '筛选'],
    ['Sort by', '排序依据'],
    ['Date', '日期'],
    ['Relevance', '相关性'],
    ['Ascending', '升序'],
    ['Descending', '降序'],
    ['Previous', '上一页'],
    ['Next', '下一页'],
    ['Page', '页'],
    ['of', '/'],
    ['Results', '条结果'],
    ['No results found', '未找到结果'],
    ['Clear filters', '清除筛选'],
    ['Apply', '应用'],
    ['Reset', '重置'],
    ['Confirm', '确认'],
    ['Warning', '警告'],
    ['Info', '信息'],
    ['Success', '成功'],
    ['Error', '错误'],
    ['Required', '必填'],
    ['Optional', '可选'],
    ['Entities', '实体'],
    ['Relations', '关系'],
    ['Nodes', '节点'],
    ['Links', '链接'],
    ['Clusters', '聚类'],
    ['Timeline', '时间轴'],
    ['Entities by type', '按类型分类的实体'],
    ['Memory count', '记忆数量'],
    ['Fact types', '事实类型'],
    ['Document count', '文档数量'],
    ['Ingested', '摄入时间'],
    ['Occurred', '发生时间'],
    ['Mentioned', '提及时间'],
    ['by mentioned time', '按提及时间'],
    ['by occurred time', '按发生时间'],
    ['Memories over time', '记忆随时间变化'],
    ['Facts per day', '每日事实数'],
    ['Operations over time', '操作随时间变化'],
    ['World Facts', '世界事实'],
    ['Experience Facts', '经验事实'],
    ['All memories', '全部记忆'],
    ['Filtered', '已筛选'],
    ['Clear', '清除'],
    ['Memory details', '记忆详情'],
    ['Entity details', '实体详情'],
    ['Directive details', '指令详情'],
    ['Mental model details', '心智模型详情'],
    ['Observation details', '观察结论详情'],
    ['Document details', '文档详情'],
    ['No memory selected', '未选择记忆'],
    ['Select a memory to view its details.', '选择一个记忆以查看其详情。'],
    ['No entity selected', '未选择实体'],
    ['Select an entity to view its details.', '选择一个实体以查看其详情。'],
    ['Tags', '标签'],
    ['Fact type', '事实类型'],
    ['Occurred at', '发生于'],
    ['Ingested at', '摄入于'],
    ['Mentioned at', '提及于'],
    ['Source document', '来源文档'],
    ['Source chunk', '来源分块'],
    ['Related entities', '相关实体'],
    ['Related memories', '相关记忆'],
    ['Related documents', '相关文档'],
    ['First seen', '首次出现'],
    ['Last seen', '最近出现'],
    ['Mention count', '提及次数'],
    ['Entity type', '实体类型'],
    ['Label', '标签'],
    ['Value', '值'],
    ['Count', '数量'],
    ['Percentage', '百分比'],
    ['Total', '合计'],
    ['Average', '平均'],
    ['Min', '最小'],
    ['Max', '最大'],
    ['Sum', '总和'],
    ['Time range', '时间范围'],
    ['From', '从'],
    ['To', '到'],
    ['Today', '今天'],
    ['Yesterday', '昨天'],
    ['Last 7 days', '最近 7 天'],
    ['Last 30 days', '最近 30 天'],
    ['Last 90 days', '最近 90 天'],
    ['All time', '全部时间'],
    ['Custom range', '自定义范围'],
    ['January', '一月'],
    ['February', '二月'],
    ['March', '三月'],
    ['April', '四月'],
    ['May', '五月'],
    ['June', '六月'],
    ['July', '七月'],
    ['August', '八月'],
    ['September', '九月'],
    ['October', '十月'],
    ['November', '十一月'],
    ['December', '十二月'],
    ['Sun', '日'],
    ['Mon', '一'],
    ['Tue', '二'],
    ['Wed', '三'],
    ['Thu', '四'],
    ['Fri', '五'],
    ['Sat', '六'],
    ['Sunday', '星期日'],
    ['Monday', '星期一'],
    ['Tuesday', '星期二'],
    ['Wednesday', '星期三'],
    ['Thursday', '星期四'],
    ['Friday', '星期五'],
    ['Saturday', '星期六'],
    ['Jan', '1月'],
    ['Feb', '2月'],
    ['Mar', '3月'],
    ['Apr', '4月'],
    ['Jun', '6月'],
    ['Jul', '7月'],
    ['Aug', '8月'],
    ['Sep', '9月'],
    ['Oct', '10月'],
    ['Nov', '11月'],
    ['Dec', '12月'],
    ['Today', '今天'],
    ['Loading bank data...', '正在加载记忆库数据...'],
    ['No data available for this time range.', '该时间范围内暂无数据。'],
    ['Select a bank to view statistics.', '选择一个记忆库以查看统计信息。'],
    ['Total memories', '记忆总数'],
    ['Total entities', '实体总数'],
    ['Total documents', '文档总数'],
    ['Total operations', '操作总数'],
    ['Pending operations', '等待中的操作'],
    ['Failed operations', '失败的操作'],
    ['Completed operations', '已完成的操作'],
    ['Success rate', '成功率'],
    ['Average duration', '平均耗时'],
    ['Total duration', '总耗时'],
    ['Operations by status', '按状态分类的操作'],
    ['Operations by type', '按类型分类的操作'],
    ['Memories by fact type', '按事实类型分类的记忆'],
    ['Entities by type', '按类型分类的实体'],
    ['Memory timeline', '记忆时间线'],
    ['Activity feed', '活动动态'],
    ['Recent activity', '最近活动'],
    ['View all', '查看全部'],
    ['Show more', '显示更多'],
    ['Show less', '显示更少'],
    ['No recent activity', '暂无最近活动'],
    ['Load more', '加载更多'],
    ['End of results', '已加载全部'],
    ['Fetching more results...', '正在加载更多结果...'],
    ['Something went wrong', '出现错误'],
    ['Please try again', '请重试'],
    ['No more results', '没有更多结果了'],
    ['results per page', '条结果每页'],
    ['Go to page', '跳至页'],
    ['First page', '首页'],
    ['Last page', '末页'],
    ['Comma-separated — used to filter memories during recall/reflect', '逗号分隔——用于在召回/深度思考时过滤记忆'],
    ['Scopes', '作用域'],
    ['Combined', '合并'],
    ['Add tags above to preview observation scopes', '在上方添加标签以预览观察结论作用域'],
    ['source: slack', '来源: slack'],
    ['channel: engineering', '频道: engineering'],
    ['One key: value per line', '每行一个键值对'],
    ['Alice, Google, ML model', 'Alice, Google, ML 模型'],
    ['Comma-separated hints merged with auto-extracted entities', '逗号分隔的提示词,与自动提取的实体合并'],
    ['changes', '变更'],
    ['Number of facts sent to the LLM in a single consolidation call. Higher values reduce LLM calls at the cost of larger prompts. Leave blank to use the server default.', '单次整合调用中发送给 LLM 的事实数量。更高的数值可减少 LLM 调用次数,但代价是提示词更大。留空使用服务器默认值。'],
    ['Total token budget for source facts included with observations during consolidation. -1 = unlimited.', '整合时与观察结论一起包含的来源事实总 Token 预算。-1 = 无限制。'],
    ['Per-observation token cap for source facts during consolidation. Each observation gets at most this many tokens of source facts. -1 = unlimited.', '整合时每条观察的来源事实 Token 上限。每条观察最多获得此数量的来源事实 Token。-1 = 无限制。'],
    ['Maximum number of observations allowed per tag scope. When the limit is reached, only updates and deletes are allowed. Observations with no tags are not subject to this limit. -1 = unlimited.', '每个标签作用域允许的最大观察数量。达到上限后仅允许更新和删除。无标签的观察不受此限制。-1 = 无限制。'],
    ['Agent identity and purpose. Used as framing context in reflect.', '智能体身份与定位。作为深度思考时的框架上下文。'],
    ['Hermes Agent memory bank', 'Hermes Agent 记忆库'],
    ['consolidation', '整合'],
    ['Directives are hard rules that must be followed during reflect.', '指令是深度思考时必须遵守的硬性规则。'],
    ['Free Form Entities', '自由形式实体'],
    ['Entities are mentioned in your memories.', '记忆中被提及的实体。'],
    ['No entities found.', '未找到实体。'],
    ['Hindsight Control Plane', 'Hindsight 控制面板'],
    [' operations', ' 个操作'],
    ['memory bank', '记忆库'],
    ['Tag (optional)', '标签(可选)'],
    ['记忆 by ingested time', '按摄入时间的记忆'],
    ['Create Directive', '创建指令'],
  ]);

  const TITLE_MAP = new Map([
    ['Expand sidebar', '展开侧边栏'],
    ['Collapse sidebar', '收起侧边栏'],
    ['View on GitHub', '在 GitHub 上查看'],
    ['Switch to dark mode', '切换到深色模式'],
    ['Switch to light mode', '切换到浅色模式'],
    ['Logout', '退出登录'],
    ['Add document to current bank', '向当前记忆库添加文档'],
    ['Refresh observations', '刷新观察结论'],
    ['Hide panel', '隐藏面板'],
    ['Show panel', '显示面板'],
    ['Match any selected tag', '匹配任意已选标签'],
    ['Match all selected tags', '匹配全部已选标签']
  ]);

  const PLACEHOLDER_MAP = new Map([
    ['Select a memory bank...', '请选择记忆库...'],
    ['Search memory banks...', '搜索记忆库...'],
    ['Enter bank ID...', '输入记忆库 ID...'],
    ['Enter the document content...', '输入文档内容...'],
    ['What would you like to reflect on?', '你想围绕什么内容进行深度思考?'],
    ['Filter by tags (comma-separated)', '按标签筛选(用逗号分隔)'],
    ['Filter by text or context (press Enter)...', '按文本或上下文筛选(回车执行)...'],
    ['Search documents (ID)...', '搜索文档(按 ID)...'],
    ['Filter by tag…', '按标签筛选…'],
    ['Optional context...', '上下文(可选)...'],
    ['Optional context about this document...', '文档上下文(可选)...'],
    ['Optional ID...', '可选 ID...'],
    ['Optional document identifier...', '可选文档标识...'],
    ['Strategy name (optional)...', '策略名称(可选)...'],
    ['Enter access key', '输入访问密钥'],
    ['tag1, tag2...', '例如:标签1, 标签2...'],
    ['model-a, model-b', '例如:model-a, model-b'],
    ['e.g. Observations are stable facts about people and projects. Always include preferences, skills, and recurring patterns. Ignore one-off events and ephemeral state.', '例如:观察结论应聚焦人物和项目的稳定事实,优先保留偏好、技能和长期规律,忽略一次性事件与短暂状态。'],
    ['e.g. You are a senior engineering assistant. Always ground answers in documented decisions and rationale. Ignore speculation. Be direct and precise.', '例如:你是一名资深工程助手。回答必须基于已记录的决策与依据,忽略猜测,表达直接且准确。'],
    ['e.g. Always include technical decisions, API design choices, and architectural trade-offs.', '例如:始终记录技术决策、API 设计选择和架构权衡。'],
    ['e.g., I am a PM for the engineering team. I help coordinate sprints and track project progress...', '例如:我是工程团队的项目经理,负责协调冲刺并跟踪项目进度...'],
    ['e.g., Competitor Policy', '例如:竞品策略'],
    ['e.g., Never mention competitor products directly.', '例如:绝不直接提及竞品名称。'],
    ['e.g., team-communication', '例如:team-communication'],
    ['e.g., Team Communication Preferences', '例如:团队沟通偏好'],
    ['e.g., How does the team prefer to communicate?', '例如:团队偏好怎样的沟通方式?'],
    ['e.g., Always respond in formal English...', '例如:始终使用正式英语回答...'],
    ['e.g. { "or": [{ "tags": ["user:alice"], "match": "all_strict" }, { "tags": ["shared"] }] }', '例如:[{ "or": [{ "tags": ["user:alice"], "match": "all_strict" }, { "tags": ["shared"] }] }]'],
    ['Exclude all mental models', '排除所有心智模型'],
    ['Exclude Mental Model IDs', '排除的心智模型 ID'],
    ['e.g., model-a, model-b (comma-separated)', '例如:model-a, model-b(逗号分隔)'],
    ['Tags scope the model during reflect and filter source memories during refresh (default all_strict: only memories carrying every listed tag are read). If no memories have these tags yet, refresh will produce empty content — backfill tags on memories, or adjust 标签匹配方式 / 标签分组 below.', '标签用于限定模型在 reflect 时的搜索范围,并在刷新时过滤源记忆(默认为 all_strict:仅读取包含所有列出标签的记忆)。如果尚无记忆包含这些标签,刷新将产生空内容——请为记忆补充标签,或调整下方的标签匹配方式 / 标签分组。'],
    ['Controls how the model\'s tags filter memories during refresh.', '控制模型的标签在刷新时如何过滤记忆。'],
    ['Compound boolean tag expressions for refresh filtering. Overrides flat tags when set.', '复合布尔标签表达式,用于刷新时的过滤。设置后覆盖普通标签。'],
    ['Override how the internal recall behaves when this model refreshes. Leave blank to inherit the bank/global default.', '覆盖此模型刷新时内部召回的行为。留空则继承记忆库或全局默认设置。'],
    ['Fact Types', '事实类型'],
    ['World', '世界观'],
    ['Experience', '经验'],
    ['Leave empty to include all types.', '留空则包含所有类型。'],
    ['Yes — include raw chunk text', '是——包含原始分块文本'],
    ['No — skip chunks (smaller prompt)', '否——跳过分块文本(提示词更短)'],
    ['Default (inherit)', '默认(继承)'],
    ['Token budget for facts returned by recall.', '召回返回事实的 Token 预算。'],
    ['Token budget for raw chunk text returned by recall.', '召回返回原始分块文本的 Token 预算。'],
    ['Directives are hard rules that must be followed during reflect.', '指令是深度思考时必须遵守的硬性规则。'],
    ['Name *', '名称 *'],
    ['Rule *', '规则 *'],
    ['Tags (optional)', '标签(可选)'],
    ['Extraction strategy', '提取策略'],
    ['Configure the default extraction rules and naming strategy. Pass a strategy name when calling retain to override the default for a single request.', '配置默认提取规则与命名策略。调用 retain 时可按策略名覆盖单条请求的默认设置。'],
    ['Extraction mode', '提取模式'],
    ['Controls how aggressively facts are extracted. concise = selective, verbose = thorough, verbatim = store chunks as-is (still extracts entities/dates), chunks = no LLM, custom = custom extraction rules.', '控制事实提取强度。concise = 选择性提取,verbose = 尽量全面提取,verbatim = 原样存储分块(仍提取实体/时间),chunks = 不使用 LLM,custom = 自定义提取规则。'],
    ['concise', '简洁'],
    ['verbose', '详细'],
    ['verbatim', '逐字'],
    ['chunks', '仅分块'],
    ['custom', '自定义'],
    ['Chunk size', '分块大小'],
    ['The size of text chunks used for processing (in characters).', '用于处理的文本分块大小(字符数)。'],
    ['Mission statement', '使命说明'],
    ['Free Form Entities', '自由形式实体'],
    ['Extract general named entities (people, places, concepts, etc.) with entity labels. When disabled, only entity labels are extracted.', '提取常规命名实体(人物、地点、概念等)并附带实体标签。关闭后仅提取实体标签。'],
    ['Entity-linked Tags', '实体关联标签'],
    ['Extract per-memory tags during retain. Each field is optional — only fill in when explicitly applicable.', '在 retain 时为每条记忆提取标签。每个字段均为可选——仅在明确适用时填写。'],
    ['No entity tags defined yet.', '尚未定义实体标签。'],
    ['Save changes', '保存更改'],
    ['Enable observations', '启用观察结论'],
    ['When enabled, the system automatically consolidates facts into reusable observations.', '开启后,系统会自动把事实整合成观察结论。'],
    ['LLM batch size', 'LLM 批处理大小'],
    ['Number of facts sent to the LLM in a single consolidation call. Higher values reduce LLM calls at the cost of larger prompts. Leave blank to use the server default.', '单次整合调用中发送给 LLM 的事实数量。更高数值可减少 LLM 调用次数,但代价是提示词更大。留空使用服务器默认值。'],
    ['Source facts max tokens', '来源事实最大 Token 数'],
    ['Total token budget for source facts included with observations during consolidation. -1 = unlimited.', '整合时与观察结论一起包含的来源事实总 Token 预算。-1 = 无限制。'],
    ['Per-observation source facts token cap', '每条观察的来源事实 Token 上限'],
    ['Per-observation token cap for source facts during consolidation. Each observation gets at most this many tokens of source facts. -1 = unlimited.', '整合时每条观察的来源事实 Token 上限。每条观察最多获得此数量的来源事实 Token。-1 = 无限制。'],
    ['Max observations per scope', '每个作用域的观察上限'],
    ['Maximum number of observations allowed per tag scope. When the limit is reached, only updates and deletes are allowed. Observations with no tags are not subject to this limit. -1 = unlimited.', '每个标签作用域允许的最大观察数量。达到上限后仅允许更新和删除。无标签的观察不受此限制。-1 = 无限制。'],
    ['Define how this memory bank reasons and responds during reflect.', '定义此记忆库在深度思考时的推理方式与响应风格。'],
    ['Agent identity and purpose. Used as framing context in reflect.', '智能体身份与定位。作为深度思考时的框架上下文。'],
    ['Skepticism', '怀疑程度'],
    ['Tendency to doubt versus trust when evaluating claims.', '评估信息时的怀疑与信任倾向。'],
    ['Literalness', '字面倾向'],
    ['How to interpret information (literal versus flexible).', '对信息的解读方式(字面 vs 灵活)。'],
    ['Empathy', '共情程度'],
    ['How much weight to give emotional context.', '对情绪背景的重视程度。'],
    ['Model', '模型'],
    ['Provider-specific model configuration.', '提供商专属模型配置。'],
    ['Safety settings', '安全阈值配置'],
    ['Configure safety thresholds.', '配置安全阈值。'],
    ['Configure a webhook endpoint to receive event notifications.', '配置 Webhook 端点以接收事件通知。'],
    ['Type', '类型'],
    ['URL *', '地址 *'],
    ['Method', '方法'],
    ['Timeout (seconds)', '超时时间(秒)'],
    ['Secret (optional)', '密钥(可选)'],
    ['Signing key', '签名密钥'],
    ['Custom headers', '自定义请求头'],
    ['Query parameters', '查询参数'],
    ['Event types', '事件类型'],
    ['Add Tag', '添加标签'],
    ['Add strategy', '添加策略'],
    ['Default', '默认'],
    ['Include only chunks (no LLM)', '仅分块(无 LLM)'],
    ['Default strategy', '默认策略'],
    ['When a request does not specify a strategy, this one is used automatically.', '当请求未指定策略时,会自动使用这里的默认策略。'],
    ['Add Webhook', '添加 Webhook'],
    ['consolidation.completed', '整合完成'],
    ['retain.completed', '记忆完成'],
    ['Disabled', '已禁用'],
    ['Default (all_strict when tags set)', '默认(设置标签时为 all_strict)'],
    ['field name', '字段名'],
    ['key: value', '例如:key: value'],
    ['Enter a query...', '输入查询词...'],
    ['Search memories...', '搜索记忆...'],
    ['Search entities...', '搜索实体...'],
    ['Search documents...', '搜索文档...'],
    ['Search...', '搜索...'],
    ['Type a question...', '输入问题...'],
    ['Enter directive name...', '输入指令名称...'],
    ['Enter rule...', '输入规则...'],
    ['Enter tags...', '输入标签...'],
    ['Enter description...', '输入描述...'],
    ['Enter content...', '输入内容...'],
    ['Enter URL...', '输入地址...'],
    ['Enter ID...', '输入 ID...'],
    ['Select date...', '选择日期...'],
    ['Select time...', '选择时间...'],
    ['user_alice, session_123, project_x', 'user_alice, session_123, project_x'],
  ]);

  const ARIA_LABEL_MAP = new Map([
    ['Remove tag', '移除标签'],
    ['Close', '关闭']
  ]);

  const EXACT_BLOCKLIST = new Set(['Hindsight', 'GitHub', 'JSON', 'HTTP', 'LLM', 'MCP', 'POST', 'GET']);

  function log(...args) {
    if (DEBUG) console.log('[Hindsight-ZH]', ...args);
  }

  function markActive() {
    const root = document.documentElement;
    if (!root) return;
    root.setAttribute(ROOT_ATTR, '1');
    root.classList.add(ROOT_CLASS);
    root.setAttribute('lang', 'zh-CN');
  }

  function normalizeSpaces(str) {
    return str.replace(/\s+/g, ' ').trim();
  }

  function decodeHtml(text) {
    if (!text || !/[&][a-z#0-9]+;/i.test(text)) return text;
    const textarea = document.createElement('textarea');
    textarea.innerHTML = text;
    return textarea.value;
  }

  function translateExact(text) {
    const decoded = decodeHtml(text);
    const normalized = normalizeSpaces(decoded);
    if (!normalized || EXACT_BLOCKLIST.has(normalized)) return null;
    return TEXT_MAP.get(normalized) || null;
  }

  function translateFragment(text) {
    if (!text) return text;
    let output = text;
    for (const [from, to] of TEXT_MAP.entries()) {
      if (from.length < 4) output = output === from ? to : output;
      else if (output.includes(from)) output = output.split(from).join(to);
    }
    return output;
  }

  function processTextNode(node) {
    if (!node || node.nodeType !== Node.TEXT_NODE) return false;
    const parent = node.parentElement;
    if (!parent || parent.closest('script, style, textarea, code, pre')) return false;
    const raw = node.nodeValue;
    if (!raw) return false;
    if (processedText.get(node) === raw) return false;
    const trimmed = raw.trim();
    if (!trimmed) {
      processedText.set(node, raw);
      return false;
    }

    const exact = translateExact(trimmed);
    if (exact) {
      const next = raw.replace(trimmed, exact);
      if (next !== raw) {
        node.nodeValue = next;
        processedText.set(node, next);
        return true;
      }
    }

    const replaced = translateFragment(raw);
    processedText.set(node, replaced);
    if (replaced !== raw) {
      node.nodeValue = replaced;
      return true;
    }
    return false;
  }

  function translateAttr(el, attrName, map, fallbackToTextMap = true) {
    const value = el.getAttribute(attrName);
    if (!value) return false;
    const key = normalizeSpaces(decodeHtml(value));
    const mapped = map.get(key) || (fallbackToTextMap ? TEXT_MAP.get(key) : null) || translateFragment(value);
    if (mapped && mapped !== value) {
      el.setAttribute(attrName, mapped);
      return true;
    }
    return false;
  }

  function replaceKnownInputs(el) {
    let changed = false;
    if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
      changed = translateAttr(el, 'placeholder', PLACEHOLDER_MAP) || changed;
    }
    if (el instanceof HTMLButtonElement || (el instanceof HTMLInputElement && ['button', 'submit'].includes(el.type))) {
      const value = el.getAttribute('value');
      if (value) {
        const mapped = TEXT_MAP.get(normalizeSpaces(value)) || translateFragment(value);
        if (mapped && mapped !== value) {
          el.setAttribute('value', mapped);
          changed = true;
        }
      }
    }
    return changed;
  }

  function processElement(el) {
    if (!(el instanceof HTMLElement)) return false;
    if (el.closest('script, style, textarea, code, pre')) return false;

    const fingerprint = [
      el.getAttribute('title') || '',
      el.getAttribute('placeholder') || '',
      el.getAttribute('aria-label') || '',
      el.getAttribute('value') || '',
      el.childElementCount === 0 ? (el.textContent || '').trim() : ''
    ].join('||');
    if (processedElements.get(el) === fingerprint) return false;

    let changed = false;
    changed = translateAttr(el, 'title', TITLE_MAP) || changed;
    changed = translateAttr(el, 'placeholder', PLACEHOLDER_MAP) || changed;

    const ariaLabel = el.getAttribute('aria-label');
    if (ariaLabel) {
      for (const [from, to] of ARIA_LABEL_MAP.entries()) {
        if (ariaLabel.startsWith(from)) {
          el.setAttribute('aria-label', ariaLabel.replace(from, to));
          changed = true;
          break;
        }
      }
    }

    changed = replaceKnownInputs(el) || changed;

    const finalFingerprint = [
      el.getAttribute('title') || '',
      el.getAttribute('placeholder') || '',
      el.getAttribute('aria-label') || '',
      el.getAttribute('value') || '',
      el.childElementCount === 0 ? (el.textContent || '').trim() : ''
    ].join('||');
    processedElements.set(el, finalFingerprint);
    return changed;
  }

  function walk(root) {
    if (!root) return 0;
    let count = 0;
    if (root.nodeType === Node.TEXT_NODE) return processTextNode(root) ? 1 : 0;
    if (root.nodeType === Node.ELEMENT_NODE && processElement(root)) count += 1;

    const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
    let current = walker.currentNode;
    while (current) {
      if (current.nodeType === Node.TEXT_NODE) count += processTextNode(current) ? 1 : 0;
      else if (current.nodeType === Node.ELEMENT_NODE) count += processElement(current) ? 1 : 0;
      current = walker.nextNode();
    }
    return count;
  }

  function injectDebugBadge() {
    if (document.getElementById('hindsight-zh-debug')) return;
    const badge = document.createElement('div');
    badge.id = 'hindsight-zh-debug';
    badge.textContent = 'Hindsight 中文脚本已加载';
    badge.style.cssText = [
      'position:fixed','right:12px','bottom:12px','z-index:2147483647','background:#16a34a','color:#fff','padding:6px 10px','border-radius:999px','font-size:12px','font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif','box-shadow:0 4px 14px rgba(0,0,0,.18)','opacity:.92','pointer-events:none'
    ].join(';');
    document.body.appendChild(badge);
    setTimeout(() => badge.remove(), 2600);
  }

  function scheduleSweep(reason) {
    if (scheduled) return;
    scheduled = true;
    requestAnimationFrame(() => {
      scheduled = false;
      const translated = walk(document.documentElement);
      if (translated > 0) log(reason, translated);
    });
  }

  function startObserver() {
    if (observerStarted) return;
    observerStarted = true;
    const observer = new MutationObserver((mutations) => {
      let shouldSweep = false;
      for (const mutation of mutations) {
        if (mutation.type === 'characterData') {
          if (processTextNode(mutation.target)) shouldSweep = true;
          continue;
        }
        if (mutation.type === 'attributes' && mutation.target instanceof HTMLElement) {
          if (processElement(mutation.target)) shouldSweep = true;
        }
        if (mutation.addedNodes && mutation.addedNodes.length > 0) shouldSweep = true;
      }
      if (shouldSweep) scheduleSweep('mutation');
    });

    observer.observe(document.documentElement, {
      subtree: true,
      childList: true,
      characterData: true,
      attributes: true,
      attributeFilter: ['title', 'placeholder', 'aria-label', 'value']
    });
  }

  function boot() {
    markActive();
    walk(document.documentElement);
    if (document.body) injectDebugBadge();
    else document.addEventListener('DOMContentLoaded', injectDebugBadge, { once: true });
    startObserver();
    window.addEventListener('load', () => scheduleSweep('window.load'), { once: true });
    setTimeout(() => scheduleSweep('600ms'), 600);
    setTimeout(() => scheduleSweep('1800ms'), 1800);
    setTimeout(() => scheduleSweep('3500ms'), 3500);
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true });
  else boot();
})();

hindsight-zh.user.zip (17.7 KB)

4 个帖子 - 2 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文