// ==UserScript== // @name ADO韩中翻译 // @namespace https://ado.treed.ai/ // @version 1.1.0 // @description 将 ALCHERA ADO 3D Cuboid 标注平台界面(韩文 + 英文)实时翻译为中文。术语依据《PJT64 Cuboid_Guide》官方中文版与标注行业规范。 // @author Bro // @match https://ado.treed.ai/* // @run-at document-idle // @grant none // @noframes // ==/UserScript== /* * ──────────────────────────────────────────────────────────────── * ADO 界面韩中/英中对照翻译(专为 PJT64 Cuboid 项目) * ──────────────────────────────────────────────────────────────── * 术语依据: * 1) 《02_PJT64 Cuboid_Guide_CN V2》官方中文版(ALCHERA Inc.) * - P14/P15 类别·子类别定义 → 英文子类别名译法 * - P24-P26 停车障碍物定义 → cone=锥桶 / bollard=护柱 … * - P5 术语表 → Width 宽 / Height 高 / Length 长 * 2) 3D 立方体标注行业通用译法 * * 翻译原则: * · 只替换「界面文案」,绝不改动任何业务数据 * - 不动 input / textarea 的 value(ID、尺寸、TC/OC、帧号等) * - 不动 select 的 value(只改 option 显示文字,提交值仍是原英文码) * - 不动 canvas / WebGL 内的绘制内容 * · 英文只做「白名单整段精确匹配」: * PCD 文件名、序列名、ROI 预设名(PJT64_Cuboid 等)、tracking_id、 * JSON / ROI / PCD / ID / Shift / Ctrl 等技术标识一律保持原样, * 避免破坏与项目方的字段对照关系。 * · 类别名 / 子类别名属数据标签,可用 Ctrl+Alt+L 单独关闭(保持英文) * · 无法命中的长句保留原文,不猜测、不生造。 * 控制台 adoCN.report() 查看未命中的韩文。 * * 使用: * · 右下角小圆点 —— 左键:中/韩切换;右键:类别名翻译开关 * 绿 = 全中文 橙 = 译文但类别名保持英文 灰 = 韩文原文 * · Ctrl+Alt+T 中/韩切换 | Ctrl+Alt+L 类别名翻译开关 * · 两项状态均自动记忆 * ──────────────────────────────────────────────────────────────── */ (function () { "use strict"; var HANGUL = /[\uAC00-\uD7A3\u1100-\u11FF\u3130-\u318F]/; var STORE_KEY = "ado_cn_translate_on"; var LABELS_KEY = "ado_cn_labels_on"; /* ============ 1. 整段精确词典(静态界面文案) ============ */ var DICT = { /* ---- 顶栏 ---- */ "ALCHERA ADO": "ALCHERA ADO", "3D Cuboid Annotation Tool": "3D 立方体标注工具", "단축키 안내": "快捷键说明", "JSON 불러오기": "载入 JSON", "CLEAR": "清空本帧", "SAVE FRAME": "保存帧", "닫기": "关闭", "한국어": "韩语", "영문": "英文", /* ---- 左侧面板:三个页签 ---- */ "표시": "显示", "조작": "操作", "ROI 영역": "ROI 区域", "객체": "对象", "클래스": "类别", "설정": "设置", /* ---- 显示设置 ---- */ "포인트 크기": "点大小", "선 두께": "线宽", "카메라 선 두께": "摄像头线宽", "착색 기준": "着色基准", "높이": "高度", "INTENSITY": "强度", "착색 배율": "着色倍率", "선택 박스만 표시 (O키)": "仅显示选中框 (O 键)", "선택 박스만 표시": "仅显示选中框", "선택 박스만 표시 토글": "切换仅显示选中框", "객체 ID 표시 (I키)": "显示对象 ID (I 键)", "객체 ID 표시": "显示对象 ID", "객체 ID 표시 토글": "切换显示对象 ID", "높이(Z) 필터": "高度(Z) 过滤", "높이 범위": "高度范围", "데이터를 불러오면 높이 범위를 조절할 수 있습니다": "载入数据后可调节高度范围", /* ---- 操作设置 ---- */ "이동 스텝": "平移步长", "회전 스텝": "旋转步长", "카메라 속도": "摄像头旋转速度", /* ---- ROI 区域 / 预设 ---- */ "전체 지우기": "全部清除", "기본값으로 초기화": "恢复默认值", "프리셋 적용": "应用预设", "프리셋 적용 (경계 테두리 포함)": "应用预设(含边界外框)", /* ---- 类面板 ---- */ "선택한 클래스의 크기로 새 큐보이드가 생성됩니다 (Shift+클릭). 클래스는 미지정으로 시작하니 객체 탭에서 지정하세요.": "将以所选类别的尺寸创建立方体(Shift+点击)。新建时类别为未指定,请在「对象」页签中指定。", /* JSX 中该提示被拆成 3 个文本节点,故分别收录以保证拼接顺序正确 */ "선택한 클래스의": "将以所选类别的", "로 새 큐보이드가 생성됩니다 (Shift+클릭). 클래스는 미지정으로 시작하니 객체 탭에서 지정하세요.": "创建立方体(Shift+点击)。新建时类别为未指定,请在「对象」页签中指定。", "크기": "尺寸", "큐보이드를 선택하면 단면이 표시됩니다": "选中立方体后显示截面", "카메라 이미지와 2D 박스가 표시됩니다": "显示摄像头图像和 2D 框", /* ---- 对象面板 ---- */ "전체 클래스": "全部类别", "미지정": "未指定", "전체 선택": "全选", "클래스 필터": "类别筛选", "필터된 클래스의 객체를 모두 선택": "全选筛选类别下的对象", "해당 클래스의 객체가 없습니다.": "该类别下没有对象。", "이 프레임에 객체가 없습니다.": "本帧没有对象。", "데이터셋을 불러오면 객체 목록이 표시됩니다.": "载入数据集后显示对象列表。", "데이터셋 디렉토리를 불러오세요.": "请载入数据集目录。", /* ---- 属性面板 ---- */ "ID": "ID", "서브클래스": "子类别", "카테고리": "类别", "디멘션": "尺寸", "카메라 속성": "摄像头属性", "반대면 고정": "对面固定", "위치·회전은 유지": "保持位置·旋转不变", "정방형 강제로 연동됨": "正方形强制联动", "허용 범위 이탈": "超出允许范围", "(없음)": "(无)", "(빈 값)": "(空值)", "보정": "修正", "숨김": "隐藏", "재결합": "重新合并", "이 객체의": "该对象的", "전체 프레임에 적용": "应用到全部帧", "전체 프레임에 적용 (즉시 저장)": "应用到全部帧(立即保存)", /* ---- 尺寸轴 / tracking_id 提示 ---- */ "length (전후, 로컬 +x)": "length(前后,局部 +x)", "width (좌우, 로컬 +y)": "width(左右,局部 +y)", "height (상하)": "height(上下)", "tracking_id — 다른 프레임의 같은 물체와 이어붙일 때 사용": "tracking_id — 用于与其他帧中的同一物体关联", /* ---- 快捷键弹窗:分组标题 ---- */ "프레임 · 시퀀스": "帧 · 序列", "생성 · 선택": "创建 · 选择", "이동 · 크기 (3D)": "平移 · 尺寸 (3D)", "회전 · 정렬": "旋转 · 对齐", "편집 · 클립보드": "编辑 · 剪贴板", "화면 조작": "视图操作", "스텝 설정": "步长设置", "카메라 뷰 (2D 박스 모드)": "摄像头视图(2D 框模式)", /* ---- 快捷键弹窗:功能名 ---- */ "이전 프레임 (저장 후 이동)": "上一帧(保存后移动)", "다음 프레임 (저장 후 이동)": "下一帧(保存后移动)", "현재 프레임 전체 지우기": "清空当前帧", "이전 프레임 어노테이션 복사": "复制上一帧标注", "선택 객체 치수 시퀀스 통일": "选中对象尺寸全序列统一", "큐보이드 생성 (활성 클래스)": "创建立方体(当前类别)", "박스 선택": "选择框", "다중 선택 토글": "切换多选", "전체 선택 / 선택 해제": "全选 / 取消全选", "다음 / 이전 박스": "下一个 / 上一个框", "선택 박스 삭제": "删除选中框", "진행 중 취소 / 선택 해제": "取消进行中 / 取消选择", "앞 / 뒤 이동": "前后平移", "좌 / 우 이동": "左右平移", "위 / 아래 이동": "上下平移", "크기 변경 (길이·폭·높이)": "调整尺寸(长·宽·高)", "표준 크기 즉시 적용": "立即应用标准尺寸", "단면 뷰 코너 드래그": "截面视图角点拖拽", "Yaw 회전": "Yaw 旋转(偏航)", "Pitch 회전": "Pitch 旋转(俯仰)", "Roll 회전": "Roll 旋转(翻滚)", "최소 각도 정렬 (자차 방향 0°)": "最小角度对齐(自车方向 0°)", "방향 자동 맞춤": "方向自动适配", "긴 변 앞으로": "长边朝前", "실행 취소 / 다시 실행": "撤销 / 重做", "박스 복사": "复制框", "커서 위치에 붙여넣기": "粘贴到光标位置", "선택 박스에 덮어쓰기": "覆盖到选中框", "tracking ID 변경": "修改跟踪 ID", "화면 이동 (팬)": "平移视图", "화면 회전": "旋转视图", "줌": "缩放", "시점 초기화": "重置视角", "이동 스텝 감소 / 증가": "减小 / 增大平移步长", "회전 스텝 감소 / 증가": "减小 / 增大旋转步长", "2D 박스 모드 전환": "切换 2D 框模式", "2D 박스 이동": "2D 框平移", "2D 박스 크기 조절": "2D 框尺寸调整", "이 카메라에서 강제 숨김": "在该摄像头中强制隐藏", "자동 투영으로 재결합": "自动投影重新合并", "단일뷰 / 4분할 전환": "单视图 / 四分屏 切换", /* ---- 快捷键弹窗:备注 ---- */ "확인창 후 삭제 — Ctrl+Z로 복원 가능": "确认后删除 — 可用 Ctrl+Z 恢复", "현재 내용 교체 — Ctrl+Z로 복원 가능": "替换当前内容 — 可用 Ctrl+Z 恢复", "같은 ID의 W/H/L을 전체 프레임에 적용 (즉시 저장)": "将同一 ID 的 W/H/L 应用到全部帧(立即保存)", "Shift 조합 시 2배 속도 (이동 공통) · 다중 선택 시 W/A/S/D는 화면 기준 위/좌/아래/우 일괄 이동": "加 Shift 速度翻倍(平移通用)· 多选时 W/A/S/D 按屏幕方向整体平移上/左/下/右", "Shift 조합 시 90°씩": "加 Shift 每次 90°", "Shift 조합 시 2배 속도": "加 Shift 速度翻倍", "카메라 이미지의 ID 텍스트만 숨김 — 박스는 그대로": "仅隐藏摄像头图像上的 ID 文字 — 框保持不变", "위치·회전은 유지": "保持位置·旋转不变", "TOP/SIDE/FRONT에서 크기 조절": "在 TOP/SIDE/FRONT 视图中调整尺寸", "빈 공간에서 시작 — 휠 버튼 드래그·Space+좌드래그도 동일": "从空白处开始 — 中键拖拽、Space+左键拖拽等效", "프레임 내 중복 불가": "帧内不可重复", "코너 드래그로도 가능": "也可拖拽角点", "마우스": "鼠标", "클릭": "点击", "Ctrl+클릭": "Ctrl+点击", "Shift+클릭": "Shift+点击", "좌드래그": "左键拖拽", "우드래그": "右键拖拽", "휠": "滚轮", "이미지 우클릭": "在图像上右键", "채널 칩 클릭": "点击通道标签", "우측 패널 ID 입력": "在右侧面板输入 ID", "Ctrl+이동키": "Ctrl+方向键", "단축키 안내 탭": "快捷键说明页签", "툴 우측 상단 단축키 탭 참고": "参见工具右上角快捷键页签", /* ---- 角色模式(URL ?role=) ---- */ "객체 작업자": "对象作业员", "속성 작업자": "属性作业员", "검수자": "质检员", "객체 작업자 모드에서는 클래스·속성 편집이 제한됩니다.": "对象作业员模式下,类别·属性编辑受限。", "속성 작업자 모드에서는 객체·지오메트리 편집이 제한됩니다.": "属性作业员模式下,对象·几何编辑受限。", "검수자 모드에서는 클래스·속성 편집이 제한됩니다.": "质检员模式下,类别·属性编辑受限。", "URL 의 ?role= 로 정해진다": "由 URL 的 ?role= 参数决定", /* ---- 尺寸/范围校验提示 ---- */ "단면이 원형·정사각인 클래스라 L·W가 연동됩니다 — 한쪽을 바꾸면 다른 쪽이 따라옵니다.": "该类别截面为圆形/正方形,长(L)·宽(W) 联动 — 改动一侧另一侧会跟随变化。", ". 크기가 맞다면 subclass 오지정일 수 있으니 subclass를 확인하세요.": "。若尺寸正确,可能是子类别指定错误,请检查子类别。", "truncation/occlusion 미입력": "截断/遮挡未填写", "길이(length)가 너비(width)보다 긴 객체에는 적용할 수 없습니다.": "长(length)大于宽(width)的对象无法应用。", "도형의 변이 서로 교차합니다.": "图形各边互相交叉。", "범위 이탈은 대개 subclass 오지정입니다 — subclass가 맞는지 확인하세요.": "超出范围通常是子类别指定错误 — 请确认子类别是否正确。", "확인 — 그대로 저장": "确定 — 照原样保存", "취소 — 저장 취소 후 첫 해당 객체 선택": "取消 — 取消保存并选中第一个相关对象", "취소 — 저장 취소 후 첫 이탈 객체 선택": "取消 — 取消保存并选中第一个超范围对象", "확인 — 새 ID 발급 (트래킹 분리)": "确定 — 分配新 ID(跟踪分离)", "취소 — 이 객체 붙여넣기 취소": "取消 — 放弃粘贴该对象", "확인 — 기존 객체를 붙여넣는 내용으로 교체": "确定 — 用粘贴内容替换已有对象", "취소 — 다음 선택지 (새 ID / 붙여넣기 취소)": "取消 — 查看下一选项(新 ID / 取消粘贴)", /* ---- 状态 / 错误 / 提示 ---- */ "전체 삭제 완료": "已全部删除", "프레임 저장 완료": "帧保存完成", "프레임 저장 실패 — SAVE FRAME으로 재시도할 수 있습니다.": "帧保存失败 — 可用 SAVE FRAME 重试。", "프레임 저장 실패 — 이동을 취소했습니다.": "帧保存失败 — 已取消移动。", "저장이 이미 진행 중입니다.": "保存已在进行中。", "시퀀스 로드 실패": "序列载入失败", "시퀀스 로드 완료": "序列载入完成", "시퀀스 로드에 실패했습니다.": "序列载入失败。", "시퀀스 불러오는 중...": "正在载入序列…", "프레임 로드에 실패했습니다.": "帧载入失败。", "프레임을 찾을 수 없습니다.": "找不到该帧。", "이 프레임은 서버에 어노테이션 파일이 등록되어 있지 않습니다.": "该帧的标注文件尚未在服务器登记。", "어노테이션 파일 미등록 프레임 — 저장할 수 없습니다.": "标注文件未登记的帧 — 无法保存。", "어노테이션 파일 미등록 프레임 — 저장할 수 없어 이동을 취소했습니다.": "标注文件未登记的帧 — 无法保存,已取消移动。", "업로드 URL 발급에 실패했습니다 (파일 미등록).": "获取上传 URL 失败(文件未登记)。", "업로드에 실패했습니다.": "上传失败。", "토큰을 찾을 수 없습니다.": "找不到令牌。", "JSON 파일만 열 수 있습니다.": "只能打开 JSON 文件。", "PCD에 x/y/z 필드가 없습니다.": "PCD 中缺少 x/y/z 字段。", "ID는 0 이상의 정수여야 합니다.": "ID 必须为 0 及以上的整数。", "시퀀스 JSON 파일을 놓아 열기": "拖入序列 JSON 文件以打开", "시퀀스 JSON 파일을 직접 놓아주세요.": "请直接拖入序列 JSON 文件。", "시퀀스 JSON 형식이 아닙니다. (payload.files 없음)": "不是序列 JSON 格式。(缺少 payload.files)", "시퀀스 config.json을 해석할 수 없어 기본 클래스 정의를 사용합니다.": "无法解析序列 config.json,将使用默认类别定义。", "파일을 1개만 놓아주세요.": "请只拖入 1 个文件。", "폴더는 열 수 없습니다.": "无法打开文件夹。", "이 프레임에 해당 채널 이미지가 없습니다": "该帧没有此通道的图像", "이전 프레임에 복사할 객체가 없습니다.": "上一帧没有可复制的对象。", "파일명을 입력하세요": "请输入文件名", "검색 결과가 없습니다": "无搜索结果", "PCD 파일명 검색": "搜索 PCD 文件名", "시점 초기화 (Ctrl+X)": "重置视角 (Ctrl+X)" /* 视图名 / 相机方向(TOP·SIDE·FRONT·front·rear…)统一由下方英文词典处理 */ }; /* ============ 2. 动态文案规则(含变量/数字的界面句) ============ */ var RULES = [ /* 对象计数 / 属性面板标题 */ [/^객체 \((\d+)\/(\d+)\)$/, "对象 ($1/$2)"], [/^객체 \((\d+) \/ (\d+)\)$/, "对象 ($1/$2)"], [/^클래스 — #(\d+)$/, "类别 — #$1"], [/^크기 — #(\d+)$/, "尺寸 — #$1"], [/^카메라 속성 — #(\d+)$/, "摄像头属性 — #$1"], [/^이동 스텝 (\d+)\/10 \((\d+)cm\/키\)$/, "平移步长 $1/10 ($2cm/次)"], [/^회전 스텝 (\d+)\/10 \(([\d.]+)°\/키\)$/, "旋转步长 $1/10 ($2°/次)"], /* 尺寸超范围警告 */ [/\(허용 ([\d.]+)~([\d.]+)\)/, "(允许 $1~$2)"], [/([\d.]+) — 허용 범위 이탈/, "$1 — 超出允许范围"], [/([\d.]+) — 정방형 강제로 연동됨/, "$1 — 正方形强制联动"], [/^크기 허용 범위 이탈 — (.+) · subclass 확인 필요$/, "尺寸超出允许范围 — $1 · 需确认子类别"], [/^(.+) 허용 범위 이탈 — (.+)$/, "$1 超出允许范围 — $2"], /* 保存 / 移动 / 清空的确认与拦截 */ [/^truncation\/occlusion 미입력 객체가 (\d+)개 있습니다\./, "有 $1 个对象的截断/遮挡未填写。"], [/^크기 허용 범위를 벗어난 객체가 (\d+)개 있습니다\./, "有 $1 个对象尺寸超出允许范围。"], [/^클래스 미지정 객체 (\d+)개 — 저장할 수 없습니다\.$/, "有 $1 个对象未指定类别 — 无法保存。"], [/^클래스 미지정 객체 (\d+)개 — 프레임을 이동할 수 없습니다\.$/, "有 $1 个对象未指定类别 — 无法移动帧。"], [/^저장된 프레임에 truncation\/occlusion 미입력 객체 (\d+)개$/, "已保存的帧中有 $1 个对象截断/遮挡未填写"], [/^저장된 프레임에 크기 허용 범위 이탈 객체 (\d+)개 — subclass 확인 필요$/, "已保存的帧中有 $1 个对象尺寸超出允许范围 — 需确认子类别"], [/— 클래스를 채운 뒤 저장하세요\.$/, "— 请填写类别后再保存。"], [/— 클래스를 채운 뒤 이동하세요\.$/, "— 请填写类别后再移动。"], [/^현재 프레임의 모든 객체\((\d+)개\)를 삭제할까요\?/, "是否删除当前帧的全部对象($1 个)?"], [/^현재 프레임의 객체 (\d+)개를 이전 프레임 내용\((\d+)개\)으로 교체할까요\?/, "是否将当前帧的 $1 个对象替换为上一帧内容($2 个)?"], /* 粘贴 / ID 相关 */ [/^#(\d+)를 새 ID로 붙여넣을까요\?/, "是否将 #$1 作为新 ID 粘贴?"], [/^이미 이 프레임에서 사용 중인 ID입니다\. \(#(\d+)\)$/, "该 ID 已被本帧使用。(#$1)"], [/^#(\d+) 객체가 있는 다른 프레임이 없습니다\.$/, "不存在包含 #$1 对象的其他帧。"], [/^(.+?) 객체가 현재 프레임에 이미 있습니다 \(하이라이트 표시\)\./, "$1 对象已存在于当前帧(已高亮显示)。"], /* 批量应用结果 */ [/^#(\d+) 클래스를 (\d+)개 프레임에 적용했습니다\.$/, "已将 #$1 的类别应用到 $2 个帧。"], [/^#(\d+)의 클래스\((.+)\)를 시퀀스 전체 프레임에 적용할까요\?/, "是否将 #$1 的类别($2)应用到序列全部帧?"], [/^다른 프레임은 즉시 서버에 저장되며 되돌릴 수 없습니다\.$/, "其他帧将立即保存到服务器且无法撤销。"], [/^#(\d+) 치수를 (\d+)개 프레임에 통일했습니다\.$/, "已将 #$1 的尺寸统一到 $2 个帧。"], [/^치수 통일 — (\d+)개 프레임 반영, (\d+)개 실패$/, "尺寸统一 — 成功 $1 个帧,失败 $2 个"], [/^클래스 일괄 적용 — (\d+)개 프레임 반영, (\d+)개 실패$/, "类别批量应用 — 成功 $1 个帧,失败 $2 个"], [/^이전 프레임 객체 (\d+)개를 복사했습니다\.$/, "已复制上一帧的 $1 个对象。"], [/^선택 객체 (\d+)개의 2D 박스가 수동 보정되어 있습니다\. 3D 편집을 계속하면 보정이 초기화\(자동 투영으로 재결합\)됩니다\. 계속할까요\?$/, "选中 $1 个对象的 2D 框已被手动修正。继续 3D 编辑将重置修正(自动投影重新合并)。是否继续?"], [/^이 객체의 2D 박스가 수동 보정되어 있습니다\. 3D 편집을 계속하면 보정이 초기화\(자동 투영으로 재결합\)됩니다\. 계속할까요\?$/, "该对象的 2D 框已被手动修正。继续 3D 编辑将重置修正(自动投影重新合并)。是否继续?"], [/^(\d+)개가 드롭되었습니다 — 시퀀스 JSON 1개만 열 수 있습니다\.$/, "有 $1 个被丢弃 — 只能打开 1 个序列 JSON。"], [/^선택 객체 (\d+)개의$/, "选中 $1 个对象的"], /* 网络 / 状态 */ [/^로그인 실패 \(status: (\d+)\)$/, "登录失败 (status: $1)"], [/^작업 상태 저장 실패 \((\d+)\)/, "作业状态保存失败 ($1)"], [/^작업 상태 조회 실패 \((\d+)\)$/, "作业状态查询失败 ($1)"], [/^파일 다운로드 실패 \((\d+)\)$/, "文件下载失败 ($1)"], [/^파일 업로드 실패 \((\d+)\)$/, "文件上传失败 ($1)"], [/^프리사인 URL 요청 실패 \((\d+)\)/, "预签名 URL 请求失败 ($1)"], [/^지원하지 않는 PCD DATA 형식: (.+)$/, "不支持的 PCD 数据格式: $1"], [/^(.+) — 시퀀스 JSON\(\.json\)을 놓아주세요\.$/, "$1 — 请拖入序列 JSON (.json) 文件。"], [/^#(\d+) \(카메라 엔트리 없음\)$/, "#$1(无摄像头条目)"], /* 片段级兜底(供被 React 拆分的文本节点与拼接句使用) */ [/크기 허용 범위 이탈 — /, "尺寸超出允许范围 — "], [/허용 범위 이탈/, "超出允许范围"], [/ · subclass 확인 필요/, " · 需确认子类别"], [/\. 크기가 맞다면 subclass 오지정일 수 있으니 subclass를 확인하세요\./, "。若尺寸正确,可能是子类别指定错误,请检查子类别。"], [/Ctrl\+Z로 되돌릴 수 있습니다/, "可用 Ctrl+Z 恢复"], [/Ctrl\+Z로 복원 가능/, "可用 Ctrl+Z 恢复"], [/한국어/, "韩语"], [/영문/, "英文"] ]; /* ============ 3. 词组兜底(仅用于短文本,保证用词规范) ============ */ var PHRASES = [ ["큐보이드", "立方体"], ["어노테이션", "标注"], ["서브클래스", "子类别"], ["카테고리", "类别"], ["클래스", "类别"], ["시퀀스", "序列"], ["프레임", "帧"], ["카메라", "摄像头"], ["채널", "通道"], ["객체", "对象"], ["속성", "属性"], ["크기", "尺寸"], ["높이", "高度"], ["길이", "长"], ["너비", "宽"], ["두께", "粗细"], ["배율", "倍率"], ["필터", "过滤"], ["스텝", "步长"], ["속도", "速度"], ["정렬", "对齐"], ["회전", "旋转"], ["이동", "平移"], ["생성", "创建"], ["삭제", "删除"], ["저장", "保存"], ["불러오기", "载入"], ["초기화", "重置"], ["지우기", "清除"], ["선택", "选择"], ["표시", "显示"], ["숨김", "隐藏"], ["보정", "修正"], ["재결합", "重新合并"], ["투영", "投影"], ["복사", "复制"], ["붙여넣기", "粘贴"], ["되돌릴", "恢复"], ["복원", "恢复"], ["확인", "确定"], ["취소", "取消"], ["닫기", "关闭"], ["전체", "全部"], ["미지정", "未指定"], ["미입력", "未填写"], ["미등록", "未登记"], ["허용 범위", "允许范围"], ["이탈", "超出"], ["기준", "基准"], ["프리셋", "预设"], ["적용", "应用"], ["테두리", "边框"], ["경계", "边界"], ["단면", "截面"], ["강제", "强制"], ["반대면", "对面"], ["고정", "固定"], ["자차", "自车"], ["방향", "方向"], ["맞춤", "适配"], ["앞으로", "朝前"], ["화면", "视图"], ["줌", "缩放"], ["휠", "滚轮"], ["클릭", "点击"], ["우클릭", "右键"], ["드래그", "拖拽"], ["커서", "光标"], ["위치", "位置"], ["무시", "忽略"], ["교차", "交叉"], ["실패", "失败"], ["완료", "完成"] ]; /* ============ 4. 英文标签词典(依据 PJT64 指南官方中文版) ============ */ /* 仅做「整段精确匹配」,绝不触碰数字、ID、文件名、序列名等技术标识。 * 类别 / 子类别名属数据标签,可用 Ctrl+Alt+L 单独开关(默认开)。 */ var EN_DICT = { /* ---- 顶栏 / 通用按钮 ---- */ "clear": "清空本帧", "save frame": "保存帧", "3d cuboid annotation tool": "3D 立方体标注工具", "on": "开", "off": "关", "intensity": "强度", "2d box": "2D 框", "json": "JSON", /* ---- 大类别(Title Case / UPPER 两种写法) ---- */ "vehicle": "车辆", "person": "人", "cycle": "两轮车", "mobility": "代步工具", "cart": "推车", "parking obstacle": "停车障碍物", "parking_obstacle": "停车障碍物", /* ---- VEHICLE 子类别(指南 P14) ---- */ "unknown_vehicle": "其他车辆", "compact": "微型车", "sedan": "乘用车", "suv": "运动型多用途车", "van": "厢式车", "bus": "巴士", "truck": "卡车", "special": "特种车辆", /* ---- PERSON 子类别(指南 P14) ---- */ "unknown_person": "其他人员", "adult_pedestrian": "成人行人", "child_pedestrian": "儿童行人", "unknown_rider": "其他骑乘者", "motorcycle_rider": "摩托车骑手", "bicycle_rider": "自行车骑手", "mobility_rider": "代步工具乘坐者", /* ---- MOBILITY 子类别(指南 P15) ---- */ "unknown_mobility": "其他代步工具", "kick_scooter": "电动滑板车", "wheelchair": "轮椅", /* ---- CYCLE 子类别(指南 P15) ---- */ "unknown_cycle": "其他两轮车", "motorcycle": "摩托车", "bicycle": "自行车", /* ---- CART 子类别(指南 P15) ---- */ "unknown_cart": "其他推车", "shopping_single": "购物车(单辆)", "shopping_stacked": "购物车(叠放)", /* ---- PARKING_OBSTACLE 子类别(指南 P24-P26) ---- */ "unknown_parking_obstacle": "其他停车障碍物", "cone": "锥桶", "parking_locker": "车位锁", "parking_gate": "停车道闸", "parking_gate_body": "道闸本体", "unknown_pillar": "其他停车柱", "pillar": "停车柱", "pillar_circle": "圆形停车柱", "speed_bump": "减速带", "stopper": "挡车器", "stopper_long": "长挡车器", "bollard": "护柱", "bollard_u": "U 型护柱", "no_parking_sign": "禁止停车标志", "close_bar": "关闭杆", "moving_bar": "移动中杆", "open_bar": "打开杆", /* ---- 视图 / 相机方向 ---- */ "top": "顶视", "side": "侧视", "front": "前视", "rear": "后视", "left": "左视", "right": "右视", "unknown": "未知", /* ---- 尺寸字段(指南 P5:Width 宽 / Height 高 / Length 长) ---- */ "l": "长(L)", "w": "宽(W)", "h": "高(H)", "length": "长(length)", "width": "宽(width)", "height": "高(height)" }; /* 这些 key 属「类别 / 子类别」数据标签,受 Ctrl+Alt+L 开关控制 */ var LABEL_KEYS = { "vehicle": 1, "person": 1, "cycle": 1, "mobility": 1, "cart": 1, "parking obstacle": 1, "parking_obstacle": 1, "unknown_vehicle": 1, "compact": 1, "sedan": 1, "suv": 1, "van": 1, "bus": 1, "truck": 1, "special": 1, "unknown_person": 1, "adult_pedestrian": 1, "child_pedestrian": 1, "unknown_rider": 1, "motorcycle_rider": 1, "bicycle_rider": 1, "mobility_rider": 1, "unknown_mobility": 1, "kick_scooter": 1, "wheelchair": 1, "unknown_cycle": 1, "motorcycle": 1, "bicycle": 1, "unknown_cart": 1, "shopping_single": 1, "shopping_stacked": 1, "unknown_parking_obstacle": 1, "cone": 1, "parking_locker": 1, "parking_gate": 1, "parking_gate_body": 1, "unknown_pillar": 1, "pillar": 1, "pillar_circle": 1, "speed_bump": 1, "stopper": 1, "stopper_long": 1, "bollard": 1, "bollard_u": 1, "no_parking_sign": 1, "close_bar": 1, "moving_bar": 1, "open_bar": 1 }; /* ============ 5. 翻译核心 ============ */ var MISS = Object.create(null); function matchEn(raw) { var m = raw.match(/^(\s*)([\s\S]*?)(\s*)$/); var core = m[2]; if (!core) return null; var key = core.toLowerCase(); var hit = EN_DICT[key]; if (hit === undefined) return null; if (!labelsOn && LABEL_KEYS[key]) return null; return m[1] + hit + m[3]; } function translateText(raw) { if (!raw) return raw; /* 纯英文:只做白名单精确匹配,命中则译,否则原样返回 */ if (!HANGUL.test(raw)) { var en = matchEn(raw); return en === null ? raw : en; } var m = raw.match(/^(\s*)([\s\S]*?)(\s*)$/); var lead = m[1], core = m[2], tail = m[3]; if (!core) return raw; if (Object.prototype.hasOwnProperty.call(DICT, core)) { return lead + DICT[core] + tail; } var out = core, i; /* 依次应用全部规则(每条只针对韩文模式,译后为中文不会再被后续规则命中) */ for (i = 0; i < RULES.length; i++) { if (HANGUL.test(out)) out = out.replace(RULES[i][0], RULES[i][1]); } if (!HANGUL.test(out)) return lead + out + tail; /* 兜底:仅对短文本逐词替换,长句宁保留韩文也不乱译 */ if (out.length <= 24) { var tmp = out; for (i = 0; i < PHRASES.length; i++) { if (tmp.indexOf(PHRASES[i][0]) !== -1) { tmp = tmp.split(PHRASES[i][0]).join(PHRASES[i][1]); } } if (!HANGUL.test(tmp)) return lead + tmp + tail; } MISS[core] = (MISS[core] || 0) + 1; return lead + out + tail; } var ATTRS = ["title", "aria-label", "placeholder", "alt", "data-tooltip"]; var SKIP_TAGS = { SCRIPT: 1, STYLE: 1, NOSCRIPT: 1, TEXTAREA: 1, CODE: 1, PRE: 1 }; var LATIN = /[A-Za-z]/; /* 纯数字 / 纯符号节点直接跳过,减少无谓调用 */ var NEED = /[\uAC00-\uD7A3\u1100-\u11FF\u3130-\u318F A-Za-z]/; function translateAttrs(el) { if (!el || el.nodeType !== 1 || SKIP_TAGS[el.tagName]) return; for (var i = 0; i < ATTRS.length; i++) { var a = ATTRS[i], v = el.getAttribute && el.getAttribute(a); if (v && NEED.test(v)) { var t = translateText(v); if (t !== v) { el.__adoRaw = el.__adoRaw || {}; if (el.__adoRaw[a] === undefined) el.__adoRaw[a] = v; el.setAttribute(a, t); } } } } function translateNode(node) { if (!node) return; if (node.nodeType === 3) { var p = node.parentNode; if (!p || SKIP_TAGS[p.tagName]) return; if (p.isContentEditable) return; var v = node.nodeValue; if (!v || !NEED.test(v)) return; var t; /* 属性面板的 T / O 字段(截断 / 遮挡):仅限