// ==UserScript== // @name ADO 韩中翻译(ALCHERA ADO 3D Cuboid) // @namespace https://ado.treed.ai/ // @version 1.5.2 // @description 将 ALCHERA ADO 3D Cuboid 标注平台界面(韩文 + 英文)实时翻译为中文。术语依据《PJT64 Cuboid_Guide》官方中文版与标注行业规范。v1.4.0 新增「多行文案逐行翻译」(修复原生确认框第 2 行起仍是韩文的老问题,粘贴冲突框/类别批量应用框/尺寸拦截框全部覆盖),补齐帧级「上下限例外」说明框的 JSX 分片文案、禁用输入框的悬浮原因(锁定/角色受限)、类别变更通知、以及 config.json 校验失败的全部原因提示;并修复两条因正文自带前导空格而永远命不中的词典键。 // @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() 查看未命中的韩文。 * * v1.4.0 变更(用户反馈「帧上下限例外那段还有韩文」→ 顺带全量复查): * ① 【通用修复】多行文案逐行翻译 * 平台的原生确认框是「标题 + 空行 + 选项说明」的多行结构: * `#${c}를 새 ID로 붙여넣을까요?\n\n확인 — 새 ID 발급 (트래킹 분리)\n취소 — …` * 整串查 DICT 必然落空;而整句规则只锚定了第一行 —— 第 2 行起的 * 「选项说明」在 DICT 里有条目,但 **DICT 只在整串精确命中时生效**, * 不会作用于被规则替换后的残句 → 第一行中文、后面全韩文。 * 现在 translateText 遇到 \n 会先拆行、逐行走完整链路再拼回。 * 一次修好:粘贴冲突框、类别批量应用框、尺寸/截断拦截框。 * ② 帧级「上下限例外」说明框 * 平台把它写成 JSX 数组 children:["프레임 상하한 예외 — 이탈 ", t, * "개 · 예외 ", e, "개.", a?"…":"…"] —— **每片都是独立文本节点**, * 整句规则永远命中不了,必须逐片收录(개 · 예외 / 개. / 长说明两条)。 * ③ 修复「正文自带前导空格」的词典键(同 v1.2.0 那类坑) * 平台原文是 `" 켜져 있는 동안 …"`(前导空格用于拼接), * 但 translateText 会先 trim 再查表 → 带空格的键永远命不中。 * 前导空格由 lead 原样保留,视觉不变。**已加注释警告后人**。 * ④ 禁用输入框的悬浮原因(锁定 / 角色受限时才出现,不 hover 看不到) * 검수 완료된 프레임이라 편집할 수 없습니다 * 이 역할에서는 클래스·T/O 가 읽기 전용입니다 * 이 역할에서는 객체·크기 편집이 제한됩니다 * ⑤ 类别变更通知 `#N 클래스 변경 (…)`(带「应用到全部帧」动作的 toast) * ⑥ config.json 校验失败的全部原因提示(原文多为模板串,只能用规则) * * v1.3.0 变更(平台新增「作业者信息 / 作业状态」功能后): * ① 作业者身份按钮与弹窗 * 작업자 정보 / 작업자 정보 — 눌러서 변경 / 이름 없음 / 역할 없음 / 이메일 미입력 * 작업 상태를 누가 했는지 기록하는 데 씁니다 * 新增 4 个角色选项的说明句(역할 없음 / 객체 작업자 / 속성 작업자 / 검수자) * 及字段标签(이름 / 이메일)与占位示例名(홍길동 → 张三)。 * ② 作业状态三行(对象 · 属性 · 质检)的四种状态 * 완료 → 已完成 반려 → 退回 되돌림 → 待重做 미착수 → 未开始 * 以及四种状态各自的悬浮说明(原文整句收录)。 * ⚠ 반려 一词两用(状态标签 + 模态动作按钮),统一译「退回」,两种情况都通顺。 * ③ 三个新动作及其提示 * 작업 완료 (Shift+Space) / 검수 완료 취소 / 반려(退回该帧) * 含「저장한 뒤 X 작업을 완료로 기록합니다」这类带变量的整句规则, * 以及退回模态的「되돌릴 역할 / 사유 / 무엇을 어떻게 고쳐야 하는지」。 * ④ 其它新文案:읽기 전용(只读)、角色权限说明、帧级「上下限例外」整句、 * 帧列表空状态、ID 유지 붙여넣기、序列 config.json 读取失败提示。 * * v1.3.0 修的两个真 bug: * · 片段规则带了前导空格(`/ · subclass 확인 필요/`)。translateText 会先把 * 正文 trim,所以这类独立文本节点永远命不中 → 实测漏译。已去掉前导空格。 * · 进度文案被通用规则抢先:`^(.+) 받음$` 排在 `^파일 N/N` 之前, * 把整条「파일 5/5 · 15.3 MB 받음」吃成「已接收 파일 5/5 · 15.3 MB」 * ("파일" 仍是韩文,且后续规则再也命不中)。 * 已把整行规则前置,并补上带速率的形态。 * * v1.2.0 变更(平台新增「尺寸上下限例外」功能后): * ① 顶部提示框(走帧 / 保存被尺寸超范围拦截) * 크기 허용 범위 이탈 객체 N개 — 프레임을 이동할 수 없습니다. * 新增整句规则;并修复「同一条消息里 (허용 a~b) 只译出第一处」的 bug * —— 根因是 String.replace 不带 /g 只替换第一处,现改为循环替换到稳定。 * ② 对象列表的尺寸告警徽标 * 크기 → 尺寸异常 (红色 = 尺寸超出允许范围) * 크기 예외 → 尺寸例外 (琥珀色 = 已指定上下限例外) * 注意「크기」单独出现时不能全局译成「尺寸异常」——「类别」面板里 * 还有一处同名 span("将以所选类别的 [크기] 创建立方体"), * 故徽标按父元素 title 前缀上下文识别,只改徽标那一处。 * ③ 尺寸面板的「上下限例外」复选框及其悬浮说明(原文整句收录) * * 使用: * · 右下角小圆点 —— 左键:中/韩切换;右键:类别名翻译开关 * 绿 = 全中文 橙 = 译文但类别名保持英文 灰 = 韩文原文 * · 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 / 取消粘贴)", /* ---- 尺寸「上下限例外」(v1.2.0 平台新增功能) ---- 背景:对象尺寸超出类别允许范围时会拦截保存 / 走帧; 若实物确实就是这个尺寸,可勾选「上下限例外」放行, 并在标注里留下「已设例外」的标记。 */ "크기 예외": "尺寸例外", " (예외 지정됨)": "(已指定例外)", "상하한 예외 — 실물이 이 크기가 맞아 범위를 벗어난 경우에만 켠다. 켜면 저장·이동이 통과하고 예외 표시가 어노테이션에 남는다.": "上下限例外 — 仅当实物确实是该尺寸、因而超出允许范围时才勾选。勾选后保存·移动可放行,标注中会留下「已设例外」的标记。", "실물이 이 크기가 맞을 때만 켠다 — 켜면 이 객체는 저장·이동 차단에서 빠지고, 예외로 저장됐다는 표시가 어노테이션에 남는다": "仅当实物确实是该尺寸时才勾选 — 勾选后该对象不再被保存·移动拦截,标注中会留下「已设例外」的标记", "예외 지정은 객체 작업자·검수자 몫이다": "例外指定由对象作业员·质检员负责", /* ---- 平台更新后新增 / 改写的界面文案(全量扫描产物,v1.2.0 补齐) ---- */ "불러오는 중...": "正在载入…", "불러오는 중입니다.": "正在载入。", "PCD 파일명 검색...": "搜索 PCD 文件名…", "이미 시퀀스를 불러오는 중입니다.": "已在载入序列。", "지금 로드가 끝난 뒤에 다시 선택해 주세요.": "请在当前载入完成后再选择。", "자동 투영으로 재결합 (R)": "自动投影重新合并 (R)", /* ⚠ DICT 是按 trim 后的正文查表,键首尾不能带空格,否则永远命不中 */ "— 시퀀스 JSON(.json)을 놓아주세요.": "— 请拖入序列 JSON (.json) 文件。", "(예외 지정됨)": "(已指定例外)", "수동 보정된 2D 박스를 자동 투영으로 재결합할까요? 보정 내용은 사라집니다.": "是否将手动修正过的 2D 框用自动投影重新合并?修正内容将丢失。", /* ---- 状态 / 错误 / 提示 ---- */ "전체 삭제 완료": "已全部删除", "프레임 저장 완료": "帧保存完成", "프레임 저장 실패 — 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)", /* ======== v1.3.0:平台新增「作业者信息 / 作业状态」整套功能 ======== 背景:平台加了「谁做了这个帧」的作业追踪 —— · 顶栏多了一个作业者身份按钮(姓名 / 角色 / 邮箱) · 作业状态分三行(对象 / 属性 / 质检),各有「已完成 / 退回 / 待重做 / 未开始」 · 有了「作业完成」(Shift+Space)、「取消质检完成」、「退回该帧」三个动作 术语取舍(三个状态词必须互不混淆): 완료 → 已完成 (作业者自己做完并保存) 반려 → 退回 (质检员打回,原因写在帧上) 되돌림 → 待重做 (自己取消了完成,需要重做) 미착수 → 未开始 (从未改动过) ⚠ 반려 既是「状态标签」也是「模态里的动作按钮」,一词两用; 统一译成「退回」两种情况都通顺,故不做上下文区分。 */ "작업자 정보": "作业者信息", "작업자 정보 — 눌러서 변경": "作业者信息 — 点击修改", "작업 상태를 누가 했는지 기록하는 데 씁니다": "用于记录作业状态由谁完成", "이름 없음": "无姓名", "이메일 미입력": "未填写邮箱", "역할 없음": "无角色", "역할": "角色", "이름": "姓名", "이메일": "邮箱", /* 韩语版用「洪吉童」当示例人名(相当于中文的「张三」), 它是输入框的占位示例、不是业务数据,故按中文习惯替换 */ "홍길동": "张三", "제한 없음. 완료하면 객체·속성 함께 기록": "无限制。完成后同时记录对象·属性", "큐보이드·2D 박스 편집. 완료하면 객체 기록": "编辑立方体·2D 框。完成后记录对象", "클래스·T/O 편집. 완료하면 속성 기록": "编辑类别·T/O。完成后记录属性", "큐보이드 편집, 속성은 읽기. 완료하면 검수 기록": "编辑立方体,属性只读。完成后记录质检", "이메일 형식이 아닙니다.": "邮箱格式不正确。", "이름만 저장할 수 없습니다. 이메일을 함께 입력해주세요.": "不能只保存姓名,请同时填写邮箱。", /* ---- 作业状态标签与说明 ---- */ "미착수": "未开始", "반려": "退回", "되돌림": "待重做", "검수": "质检", "그 역할 작업을 끝내고 저장했습니다": "已完成该角色作业并保存", "검수자가 다시 하라고 되돌렸습니다. 사유는 프레임을 열면 보입니다": "质检员已退回重做,退回原因打开该帧即可看到", "완료를 취소해 다시 할 것으로 돌려놨습니다": "已取消完成,需重新作业", "아직 아무 기록이 없습니다. 되돌림과 달리 손댄 적이 없는 상태입니다": "尚无任何记录。与「待重做」不同,表示从未改动过", /* ---- 作业完成 / 取消质检完成 / 退回该帧 ---- */ "작업 완료": "作业完成", "작업 완료 (저장 후 기록)": "作业完成(保存后记录)", "내 역할을 완료로 기록 — 필수값이 비면 속성 역할은 빠진다": "将我的角色记为完成 — 必填项为空时属性角色会被跳过", "검수 완료 취소": "取消质检完成", "검수 완료를 되돌려 이 프레임을 다시 수정할 수 있게 합니다": "取消质检完成,使该帧可以再次修改", "검수 완료된 프레임입니다.": "该帧质检已完成。", "검수 완료된 프레임이라 저장할 수 없습니다": "该帧质检已完成,无法保存", "검수 완료된 프레임이라 작업을 완료로 기록할 수 없습니다": "该帧质检已完成,无法记录作业完成", "검수 완료를 취소하거나 반려해야 다시 수정할 수 있습니다 — 객체·속성 작업자 화면에는 그 버튼이 없습니다.": "需先取消质检完成或退回,才能再次修改 — 对象·属性作业员界面没有该按钮。", "검수 완료를 취소했습니다. 이 프레임을 다시 수정할 수 있습니다.": "已取消质检完成,该帧可以再次修改。", "검수 완료 취소에 실패했습니다.": "取消质检完成失败。", "저장만 합니다": "仅保存", "작업 상태 기록에 실패했습니다. 저장은 되었습니다.": "作业状态记录失败,但已保存。", "프레임 반려": "退回该帧", "고른 역할은 담당자를 그대로 둔 채 상태만 반려로 바뀝니다": "所选角色保留原负责人,仅将状态改为退回", "되돌릴 역할": "要退回的角色", "객체 작업": "对象作业", "속성 작업": "属性作业", "큐보이드·2D 박스를 다시 손본다": "重新修整立方体·2D 框", "클래스·T/O 를 다시 손본다": "重新修整类别·T/O", "사유 — 재작업하는 사람이 이것만 보고 고친다": "退回原因 — 返工人员只看这一条来修正", "무엇을 어떻게 고쳐야 하는지": "需要改什么、怎么改", "반려로 기록했습니다.": "已记录为退回。", "반려 기록에 실패했습니다.": "退回记录失败。", /* ---- 只读标记与角色权限提示 ---- */ "읽기 전용": "只读", "객체·지오메트리 편집이 제한되며, 저장 시 서버 최신본에 속성만 병합됩니다.": "对象·几何编辑受限,保存时仅把属性合并到服务器最新版本。", "속성은 읽기 전용이며, 저장 시 서버 최신본의 속성이 보존됩니다.": "属性为只读,保存时保留服务器最新版本的属性。", /* ---- 帧级「上下限例外」(v1.3.0:例外从单对象扩展到整帧)---- */ "이 프레임의 이탈 객체를 한 번에 예외로 지정한다 — 끄면 이 프레임의 예외가 전부 풀린다": "把该帧的超范围对象一次性指定为例外 — 关闭则解除该帧的全部例外", "프레임 상하한 예외가 켜져 있습니다 — 실물 크기가 맞는지 확인하고, 아니면 객체별 예외를 끄세요.": "该帧的上下限例外已开启 — 请确认实物尺寸是否正确,否则请关闭对应对象的例外。", /* ⚠ 下面这两条的正文键已移到底部 v1.4.0 段(去掉前导空格才有用)—— 原来写成带前导空格的键,导致 trim 后永远查不到,实测截图里那段韩文一直没译。 */ /* ---- 帧列表空状态 ---- */ "시퀀스 JSON을 불러오면 프레임이 표시됩니다.": "载入序列 JSON 后即会显示帧。", "마지막 프레임입니다.": "已是最后一帧。", /* ---- 序列 config.json 读取失败(平台新增容错提示)---- */ "config.json을 내려받지 못했습니다.": "下载 config.json 失败。", "config.json을 읽지 못했습니다.": "读取 config.json 失败。", /* 配置校验失败原因(config.json 损坏时会作为原因提示出来) ⚠ 只有「纯字符串」才能放 DICT;带变量的走下方 RULES (这两条是 m7() 里的普通字符串字面量) */ "config 최상위가 객체가 아니다": "config 顶层不是对象", "categories 가 없거나 빈 배열이다": "缺少 categories,或为空数组", "objectCountSource 가 없어 객체 수를 보낼 수 없습니다.": "缺少 objectCountSource,无法上报对象数。", "size_ranges 가 객체가 아니다": "size_ranges 不是对象", "size_ranges 가 빈 객체다": "size_ranges 是空对象", "size_ranges 에 빈 키가 있다": "size_ranges 中存在空键", /* ======== v1.4.0:禁用输入框的悬浮原因(锁定 / 角色受限时才出现) ======== 平台 wS() 决定:锁定 → 第一条;角色=属性作业员 → 第二条;其余受限角色 → 第三条。 这些是 input 的 title,不 hover 看不到,最容易漏。 */ "검수 완료된 프레임이라 편집할 수 없습니다": "该帧质检已完成,无法编辑", "이 역할에서는 클래스·T/O 가 읽기 전용입니다": "该角色下类别·T/O 为只读", "이 역할에서는 객체·크기 편집이 제한됩니다": "该角色下对象·尺寸编辑受限", /* ======== v1.4.0:帧级「上下限例外」说明框的 JSX 分片 ======== ⚠ 平台把这句话写成 JSX 数组: children:["프레임 상하한 예외 — 이탈 ", t, "개 · 예외 ", e, "개.", a?"…":"…"] 所以每片都会成为**独立文本节点**,整句规则永远命中不了,必须逐片收录。 */ "프레임 상하한 예외 — 이탈": "该帧上下限例外 — 超范围", "개 · 예외": " 个 · 例外", "개.": " 个。", /* ⚠⚠ 下面两条平台原文**自带前导空格**(它被拼在「개.」后面)。 但 translateText 会先 trim 再查 DICT,所以键**绝不能带前导空格**—— 带了就永远命不中(v1.3.0 就是这么漏掉的,实测截图里那段韩文没译)。 前导空格由 lead 原样保留,视觉上不受影响。 */ "켜져 있는 동안 새로 생기는 이탈도 예외가 되고, 다음 프레임에도 켜진 채로 이어집니다. 개별로 끈 객체는 그대로 둡니다.": "开启期间新出现的超范围也会成为例外,并延续到下一帧;单独关闭的对象保持原样。", "켜면 이 프레임의 이탈 객체가 예외가 되고, 이후 새로 생기는 이탈과 다음 프레임까지 따라갑니다.": "开启后该帧的超范围对象成为例外,此后新出现的超范围以及下一帧都会延续。" /* 视图名 / 相机方向(TOP·SIDE·FRONT·front·rear…)统一由下方英文词典处理 */, /* ======== v1.5.0:2026-10 平台新版(语义分割 / 分组 / 尺寸统一 / 错误列表 / PO dim / ego 位移) ======== */ "세그멘테이션": "语义分割", "세그멘테이션 모드 (?segment=1) — 켜면 왼쪽에 세그 탭이 생긴다": "语义分割模式 (?segment=1) — 开启后左侧出现分割页签", "JSON 가져오기": "导入 JSON", "오류 리스트": "错误列表", "오류 리스트 열기": "打开错误列表", "오류 리스트 닫기": "关闭错误列表", "오류 리스트를 읽을 수 없습니다.": "无法读取错误列表。", "오류 내용 닫기": "关闭错误内容", "오류 내용이 비어 있습니다": "错误内容为空", "검수 결과 CSV를 열면 오류 목록이 표시됩니다. 줄을 누르면 그 프레임의 해당 객체로 이동합니다.": "打开质检结果 CSV 后显示错误列表。点击某行可跳转到该帧的对应对象。", "결과 확인": "查看结果", "결과 확인 — 기준과 다름": "查看结果 — 与基准不同", "닫기 (Esc)": "关闭 (Esc)", "그룹 라벨링": "分组标注", "그룹 확인": "分组确认", "그룹 라벨링 완료 →": "完成分组标注 →", "그룹 확인 완료 → 크기 조정": "完成分组确认 → 尺寸调整", "◀ 그룹 라벨링으로": "◀ 返回分组标注", "◀ 그룹 확인으로": "◀ 返回分组确认", "◀ 선택으로 돌아가기": "◀ 返回选择", "+ 그룹 추가": "+ 添加分组", "그룹": "分组", "그룹 대표": "分组代表", "키). 같은 그룹을 다시 누르면 해제": "键)。再次按同一分组键即取消", "1~9 그룹 지정 (같은 키 다시 = 해제) · Backspace 해제 · ↑↓ 카드 · ←→ 프레임 · Enter 이 프레임의 제안 받기": "1~9 指定分组(再按同键=取消)· Backspace 取消 · ↑↓ 卡片 · ←→ 帧 · Enter 接收本帧建议", "그룹은 A~I 아홉 개까지입니다.": "分组最多 A~I 共九个。", "그룹을 하나도 정하지 않았습니다.": "尚未指定任何分组。", "그룹을 정하지 않은 객체": "未指定分组的对象", "작업 종류": "作业类型", "작업할 종류 고르기": "选择作业类型", "종류 고르기": "选择类型", "시작": "开始", "시작 안 함": "不开始", "수집": "采集", "미정": "未定", "편집 중": "编辑中", "다시 읽기": "重新读取", "접기": "收起", "접기 — 뒤 화면을 넓게 본다": "收起 — 让后方视野更宽", "누르면 이 프레임의 객체를 화면에 띄웁니다": "点击后显示本帧的对象", "같은 물체로 선택": "选为同一物体", "✓ 같은 물체 (선택됨)": "✓ 同一物体(已选中)", "모두 선택": "全选", "모두 해제": "全部取消", "다른 그룹으로 옮기기": "移到其他分组", "그룹으로 옮긴다": "移入分组", "옮기기": "移动", "같은 실물끼리 묶였는지 봅니다. 아니면 줄 끝에서 다른 그룹으로 옮깁니다.": "确认是否属于同一实物。否则在行末移到其他分组。", "기준과 같은 물체인 카드만 고른다. 고르지 않은 카드는 새 ID 하나로 옮겨 간다.": "只勾选属于同一实物的卡片。未勾选的卡片会合并为新 ID。", "단독 — 그대로 둠": "单独 — 保持不变", "새 ID 로 옮김": "已改为新 ID", "선택 안 함 — 새 ID": "不选择 — 新 ID", "기준 객체": "基准对象", "기준 객체 · #": "基准对象 · #", "기준 프레임 #": "基准帧 #", "그룹 치수에 쓴 객체": "用于分组尺寸的对象", "그룹 치수 —": "分组尺寸 —", "미입력 (선택)": "未填写(可选)", "저장하고 계속": "保存并继续", "버리고 계속": "丢弃并继续", "화면에 열림": "已在画面中显示", "확인 완료 — 다음 종류로": "确认完成 — 下一个类型", "이 시퀀스에 있는 대상 종류입니다. 고르면 그 종류로 시작합니다.": "本序列中存在的目标类型。选择后以该类型开始。", "시퀀스를 열면 그 프레임이 대상이 된다": "打开序列后该帧即为目标", "시퀀스를 다시 읽는다 — 모아 둔 수집 결과를 버린다 (§1-10)": "重新读取序列 — 丢弃已采集结果 (§1-10)", "시퀀스 전 프레임의 같은 종류(주차콘·볼라드·U자 볼라드·스토퍼·주차 락커)를 모아 그룹으로 묶고 그룹마다 L/W/H를 하나로 맞춤": "汇总整条序列中同类型(停车锥·护柱·U 型护柱·挡车器·停车地锁)并分组,每组统一 L/W/H", "주차장 장애물 치수 그룹별 일치": "停车场障碍物尺寸按分组统一", "주차콘": "停车锥", "볼라드": "护柱", "U자 볼라드": "U 型护柱", "스토퍼": "挡车器", "주차 락커": "停车地锁", "치수 통일": "尺寸统一", "치수 통일 창 펴기": "展开尺寸统一窗口", "PO dim 창 펴기": "展开 PO dim 窗口", "PO dim 일치": "PO dim 一致", "PO dim 적용 —": "PO dim 应用 —", "이 시퀀스에 PO dim 대상 객체가 없습니다.": "本序列没有 PO dim 目标对象。", "이 시퀀스의 PO dim 대상 종류를 모두 마쳤습니다.": "本序列的 PO dim 目标类型已全部完成。", "기준 치수로 통일": "统一为基准尺寸", "확정 치수": "确认尺寸", "확정 치수를 비운다": "清除确认尺寸", "치수를 안 정한 그룹 —": "未确定尺寸的分组 —", "치수를 안 정한 그룹이 있습니다 —": "存在未确定尺寸的分组 —", "바꿀 객체가 없습니다 — 이미 전부 그룹 치수입니다.": "没有需要变更的对象 — 已全部为分组尺寸。", "멤버가 2개 이상인 그룹이 없습니다 — 맞출 상대가 없어 크기 조정을 건너뜁니다.": "没有成员 ≥2 的分组 — 无对齐对象,跳过尺寸调整。", "그룹 치수와 다른 객체가": "与分组尺寸不同的对象", "선택 객체 크기 적용": "应用选中对象尺寸", "편집 화면에서 지금 고른 객체의 L/W/H 를 이 그룹의 치수로": "将编辑界面当前选中对象的 L/W/H 设为本组尺寸", "편집 화면에서 기준이 될 객체를 먼저 고르세요.": "请先在编辑界面选中作为基准的对象。", "편집 화면에 저장하지 않은 수정이 있습니다.": "编辑界面有未保存的修改。", "편집을 버리지 못했습니다 — 적용을 멈춥니다.": "无法丢弃编辑内容 — 已停止应用。", "적용 (즉시 저장)": "应用(立即保存)", "적용하는 중...": "正在应用...", "적용한 뒤 저장된 값을 다시 읽어 견줍니다.": "应用后重新读取已保存的值进行比对。", "적용은 서버의 파일을 고쳐 씁니다. 저장하지 않은 수정은 그 파일에 없으므로, 버리고 적용하면 지금 화면에서 본 치수와 들어가는 값이 갈립니다.": "应用会改写服务器上的文件。未保存的修改不在该文件中,因此若丢弃后应用,界面看到的尺寸与实际写入的值会不一致。", "저장한 프레임을 다시 읽은 값이다. ✘ 카드를 눌러 틀어진 위치를 고친다.": "这是重新读取已保存帧得到的值。点击 ✘ 卡片修正偏离的位置。", "✔ 기준과 같음": "✔ 与基准相同", "✘ 기준과 다름": "✘ 与基准不同", "크기 다름": "尺寸不同", "크기 조정": "尺寸调整", "피팅 후보 — 누르면 그 객체로 가서 PCD·이미지로 크기를 맞춘 뒤 「선택 객체 크기 적용」:": "拟合候选 — 点击跳转到该对象,用 PCD·图像调整尺寸后「应用选中对象尺寸」:", "브러시": "画笔", "브러시 —": "画笔 —", "브러시 깊이": "画笔深度", "브러시 반지름": "画笔半径", "칠한 것 지우기": "清除涂抹", "비우기": "清空", "잠금 전환 — 잠근 클래스의 포인트는 안 칠해진다": "切换锁定 — 已锁定的类别点不会被涂抹", "잠금 해제": "解除锁定", "잠김": "已锁定", "열림": "已解锁", "보임": "显示", "보기": "查看", "켜짐": "开", "꺼짐": "关", "표면 끊김 기준": "表面断开阈值", "이보다 높은 점은 안 칠해진다": "高于该值的点不会被涂抹", "깊이가 이만큼 벌어지면 다른 면으로 본다. 큐보이드를 고르지 않고 칠할 때만 쓴다 — 상자 안에서는 상자가 깊이를 가둔다": "深度差超过该值时视为不同表面。仅在未选立方体涂抹时生效 — 在框内由框限定深度", "브러시 원이 집는 것은 평면이 아니라 기둥이다. 줄이면 앞쪽 몇 %만 칠해 큐보이드를 관통하지 않는다": "画笔圆取的是柱体而非平面。调小后只涂抹前面几 %,不会穿透立方体", "브러시 획만 되돌린다 — 큐보이드는 ADO 의 되돌리기가 갖는다": "仅撤销画笔笔迹 — 立方体由 ADO 的撤销负责", "큐보이드를 고르면 그 안으로 좁혀진다. 안 고르면 화면 전체가 대상이다": "选中立方体后范围缩小到其内部。未选中时整个画面都是目标", "세그 모드 밖에서 만든 상자 — 안쪽을 칠하지 않고 저장에서도 안 빠진다": "在分割模式外创建的框 — 不涂抹内部,也不会在保存时被剔除", "세그 클래스가 아닌 큐보이드": "非分割类别的立方体", "클래스는 객체 탭에서 바꾼다": "类别在对象页签中修改", "보이기 전환 — 결과 JSON 의 isVisible 에 실린다": "切换显示 — 写入结果 JSON 的 isVisible", "다른 툴에서 받은 결과를 이 프레임에 깐다 — 가져오기 전용이다": "将其他工具的结果铺到本帧 — 仅用于导入", "PCD 의 색에서 세그 결과를 읽었습니다 —": "已从 PCD 颜色读取分割结果 —", "PCD 를 먼저 열어야 합니다.": "需先打开 PCD。", "이 PCD 에는 intensity 가 없어 높이로 그린다.": "该 PCD 无 intensity,改用高度着色。", "상방이 x 인 데이터로 보여 ADO 규약(z 상방)으로 돌렸습니다.": "数据以上方为 x 显示,已按 ADO 规范(上方为 z)旋转。", "점 중 라벨": "点中已标注", "점. 이어서 고칠 수 있습니다.": "点。可以继续修改。", "선택 객체 포커싱": "聚焦选中对象", "선택 객체 포커싱 (P키)": "聚焦选中对象 (P 键)", "선택 객체 포커싱 토글": "切换聚焦选中对象", "켜면 객체를 고를 때마다 3D 뷰와 카메라 뷰가 그 객체로 따라온다": "开启后每次选中对象,3D 视图与相机视图都会跟随该对象", "포인트 번호": "点编号", "포인트": "点", "거리": "距离", "지정": "指定", "선택": "选择", "저장": "保存", "프레임": "帧", "없음": "无", "미입력": "未填写", "클래스 없음": "无类别", "이미지 없음": "无图像", "bbox 없음": "无 bbox", "ID 없음 · #": "无 ID · #", "상자": "框", "새 ID": "新 ID", "기준 치수로 통일 · 고르지 않은": "统一为基准尺寸 · 未选中的", "치수 통일 · ID": "尺寸统一 · ID", "z 범위 상한": "z 范围上限", "오류": "错误", "오류 (": "错误(", "· 깊이": "· 深度", "ego 이동량 반영 붙여넣기": "按 ego 位移粘贴", "ego 이동량에": "按 ego 位移", "ego pose 파일을 읽지 못해 이동량 붙여넣기를 끕니다.": "无法读取 ego pose 文件,已关闭位移粘贴。", "같은 시퀀스에서 복사한 객체만 이동량을 반영할 수 있습니다.": "只有从同一序列复制的对象才能应用位移。", "이 시퀀스에는 ego 이동량 데이터가 없습니다.": "本序列没有 ego 位移数据。", "복사한 프레임과의 차량 이동량만큼 옮겨 붙여넣음 — 시퀀스에 ego pose가 있을 때만": "按与复制帧之间的车辆位移粘贴 — 仅当序列含 ego pose 时可用", "두 프레임 사이 이동량이 없어 제자리에 붙여넣습니다.": "两帧之间没有位移,将原位粘贴。", "이전 프레임 카테고리·서브클래스 가져오기": "从上一帧导入类别·子类别", "이전 프레임에 객체가 없습니다.": "上一帧没有对象。", "이전 프레임에서 가져올 카테고리·서브클래스가 없습니다.": "上一帧没有可导入的类别·子类别。", "첫 프레임이라 가져올 이전 프레임이 없습니다.": "这是第一帧,没有可导入的上一帧。", "이 프레임에 받을 제안이 없습니다.": "本帧没有可接收的建议。", "이 객체는 ID 가 없어 자동으로 고르지 못합니다.": "该对象没有 ID,无法自动选中。", "이미 지워졌거나 tracking_id 가 바뀐 객체입니다.": "该对象已被删除或 tracking_id 已改变。", "프레임으로 갔습니다 — 이 객체는 ID 가 없어 자동으로 고르지 못합니다.": "已跳转到该帧 — 此对象没有 ID,无法自动选中。", "다른 시퀀스의 오류입니다.": "这是其他序列的错误。", "같은 이름의 다른 패키지 시퀀스입니다.": "这是同名但属于其他题包的序列。", "패키지를 알아내지 못해 시퀀스 이름만으로 가립니다": "未能获取题包,仅按序列名区分", "GET /sequences 로 패키지를 알아내지 못했습니다. 시퀀스를 다시 열면 한 번 더 묻습니다.": "未能通过 GET /sequences 获取题包。重新打开序列时会再询问一次。", "시퀀스 조회 실패 (": "序列查询失败(", "시퀀스를 읽는 중...": "正在读取序列...", "프레임을 읽는 중...": "正在读取帧...", "프레임을 불러오는 중": "正在载入帧", "이미지 받는 중...": "正在下载图像...", "어노테이션만 읽습니다 (이미지·PCD 는 받지 않습니다) —": "仅读取标注(不下载图像·PCD)—", "빈 파일입니다.": "文件为空。", "파일에 프레임이 없습니다.": "文件中没有帧。", "읽을 수 있는 줄이 없습니다.": "没有可读取的行。", "필수 열이 없습니다 —": "缺少必需列 —", "이름을 입력해주세요.": "请输入名称。", "이메일을 입력해주세요.": "请输入邮箱。", "클래스가 너무 많아 \"": "类别过多,\"", "목록에 없던 클래스 \"": "不在列表中的类别 \"", "가 중복이다 (대소문자 구분 없음)": "重复(不区分大小写)", "segmentation_classes 가 배열이 아니다": "segmentation_classes 不是数组", "segmentation_classes 가 빈 배열이다": "segmentation_classes 是空数组", "camera_attributes 가 객체가 아니다": "camera_attributes 不是对象", "sequence 가 비었습니다": "sequence 为空", "unlabeled_color 가 #RRGGBB 형식이 아니다": "unlabeled_color 不是 #RRGGBB 格式", ".required 가 true/false 가 아니다": ".required 不是 true/false", ".range 가 [하한, 상한] 형식이 아니다 — 정수, 하한은 0 이상, 하한 < 상한": ".range 不是 [下限, 上限] 格式 — 须为整数、下限≥0、下限<上限", "].name 이 문자열이 아니다": "].name 不是字符串", "tracking_id 가 정수가 아닙니다 — \"": "tracking_id 不是整数 — \"", "frame 이 1 이상의 정수가 아닙니다 — \"": "frame 不是 ≥1 的整数 — \"", "[dev] 로컬 시퀀스를 못 읽었다:": "[dev] 无法读取本地序列:", "(이름 없는 시퀀스)": "(未命名序列)", "ADO 조작 그대로": "与 ADO 操作一致", "· 우드래그 회전 · 중클릭 이동": "· 右拖旋转 · 中键平移", /* ---- v1.5.0 第二批:JSX 分片 / 句前后缀(独立文本节点)---- */ "(끝까지)": "(到末尾)", "(세그 클래스 아님)": "(非分割类别)", "(열린 시퀀스)": "(已打开序列)", "(충돌 —": "(冲突 —", "· 고른 큐보이드 안만": "· 仅所选立方体内", "· 변화 없음": "· 无变化", "· 불러온 라벨 위": "· 在已载入标签之上", "· 선택 안 함": "· 不选择", "· 지정": "· 指定", "· 큐보이드를 고르면 그 안으로 좁혀진다": "· 选中立方体后范围缩小到其内部", "· 패키지": "· 题包", "— 바뀌는 객체": "— 会变更的对象", "가 그사이 바뀌었다)": "期间被改动)", "가 됐습니다.": "已变更。", "가 아닙니다 — 그룹 치수로 쓸 수 없습니다.": "不是该类型 — 不能用作分组尺寸。", "가 없거나 객체가 아니다 — truncation·occlusion 둘 다 적는다": "缺失或不是对象 — 截断与遮挡都要填写", "가 없습니다.": "不存在。", "객체가 없습니다.": "没有对象。", "고르지 않은": "未选中的", "그룹 —": "分组 —", "그룹으로 (": "移入分组(", "로 넘어갑니다.": ",进入下一步。", "로 옮긴 객체": "已移入的对象", "를 열고 누르세요.": ",打开后点击。", "를 열어야 이동할 수 있습니다": ",打开后才能跳转", "미입력 객체가": "未填写对象", "에 ID": "的 ID", "에 tid": "的 tid", "이고 이 줄은": ",本行为", "입니다 —": " —", "입니다.": "。", "치수 통일 — 기준은 #": "尺寸统一 — 基准为 #", "키 · 지정": "键 · 指定", "프레임의 객체": "帧的对象", "\" 를 더했습니다.": "\" 已添加。", "\" 를 버렸습니다.": "\" 已丢弃。", "\" 하나만 엽니다.": "\" 中的一个。", ") 밖이라 버렸습니다 — 짝이 맞는 PCD 인지 확인하세요.": ")之外,已丢弃 — 请确认是否为匹配的 PCD。", "m · 가림": "m · 遮挡", "segmentation_classes 가": "segmentation_classes", "segmentation_classes 에": "segmentation_classes 中", "— 이 객체만 고칠 수 있습니다": "— 只能修改该对象", "같은 ID 객체를 리스트로 띄워 고른 것에만 W/H/L 적용 (즉시 저장) — 고르지 않은 객체는 새 ID": "列出同 ID 对象,仅对选中者应用 W/H/L(立即保存)— 未选中的对象将分配新 ID", "같은 ID 객체의 둘 다 덮어씀 — 큐보이드와 T/O는 그대로, Ctrl+Z로 복원 가능": "同 ID 对象的二者都会被覆盖 — 立方体与 T/O 保持不变,可用 Ctrl+Z 恢复", "같은 ID 의 다른 프레임": "同 ID 的其他帧", "같은 이름의 다른 패키지입니다 — package_id": "同名但属于其他题包 — package_id", "개 · 객체": "个 · 对象", "개 · 지정": "个 · 指定", "개 · 프레임": "个 · 帧", "개 — 크기를 바꾸지 않습니다": "个 — 不改变尺寸", "개 → 기준 치수로 통일": "个 → 统一为基准尺寸", "개 → 기준 치수로 통일 · 고르지 않은": "个 → 统一为基准尺寸 · 未选中的", "개 → 새 ID": "个 → 新 ID", "개 → 새 ID 하나로 변경": "个 → 合并为新 ID", "개 있습니다. 넘어갈까요?": "个。要继续吗?", "개 프레임 실패": "帧失败", "개)": "个)", "개가 PCD 범위(0~": "个超出 PCD 范围(0~", "개가 바뀝니다 (변화 없음": "个将变更(无变化", "개가 여러 객체에 겹쳐 있어 나중 것으로 덮었습니다.": "个重叠在多个对象上,已用后者覆盖。", "개까지다": "个为上限", "개는 #": "个为 #", "개는 다시 찾지 못해 건너뜀.": "个无法再次找到,已跳过。", "개는 크기를 바꾸지 않습니다. 넘어갈까요?": "个不改变尺寸。要继续吗?", "개다 — 라벨이 1바이트라": "个 — 标签为 1 字节,", "개에 치수를 적용했습니다.": "个应用了尺寸。", "개의 카테고리·서브클래스를 가져왔습니다.": "个的类别·子类别已导入。", "개입니다 — 데모는 \"": "个 — 演示为 \"", "개입니다.": "个。", "건 · 이 시퀀스": "条 · 本序列", "건 —": "条 —", "건을 불러왔습니다.": "条已载入。", "고른": "选中的", "고른 객체가": "选中的对象", "고른 상자 #": "选中的框 #", "다시 찾지 못해 건너뜀 —": "无法再次找到,已跳过 —", "번 프레임이 없습니다.": "帧不存在。", "번째 줄:": "行:", "시퀀스 JSON 을 먼저 불러오세요 — 이 줄은": "请先载入序列 JSON — 本行为", "열린 시퀀스는": "已打开的序列为", "열린 시퀀스의 패키지는": "已打开序列的题包为", "이 시퀀스에": "本序列", "이 시퀀스의 프레임은": "本序列的帧为", "이 클래스의 큐보이드 수": "该类别的立方体数量", "이전 프레임에서 객체": "从上一帧复制对象", "저장된 프레임에": "已保存的帧中", "줄은 건너뜀": "行已跳过", "프레임이": "帧", "다른 프레임은 즉시 서버에 저장되며 되돌릴 수 없습니다.": "其他帧会立即保存到服务器,且无法撤销。", "즉시 서버에 저장되며 되돌릴 수 없습니다.": "会立即保存到服务器,且无法撤销。", "적용할까요?": "要应用吗?", "2D 박스가 수동 보정되어 있습니다. 3D 편집을 계속하면 보정이 초기화(자동 투영으로 재결합)됩니다. 계속할까요?": "2D 框已手动修正。继续 3D 编辑会重置修正(按自动投影重新合并)。要继续吗?", /* ---- v1.5.0 第三批:句前后缀 / 弹窗正文 / 日志提示 ---- */ "다른 프레임은 즉시 서버에 저장되며 되돌릴 수 없습니다. 적용할까요?": "其他帧会立即保存到服务器,且无法撤销。要应用吗?", "즉시 서버에 저장되며 되돌릴 수 없습니다. 적용할까요?": "会立即保存到服务器,且无法撤销。要应用吗?", "(카메라 엔트리 없음)": "(无相机条目)", "(허용": "(允许", "— 정방형 강제로 연동됨": "— 已按强制正方形联动", "가 [하한, 상한] 형식이 아니다 — 하한은 0 이상, 상한은 0 초과, 하한 < 상한": " 不是 [下限, 上限] 格式 — 下限≥0、上限>0、下限<上限", "가 객체가 아니다": " 不是对象", "가 빈 객체다": " 是空对象", "가 있다 — 렌더링 폴백 전용이다": " 存在 — 仅用于渲染回退", "가 중복이다": " 重复", "가 한 자리 숫자(0~9)도 \"-\" 도 아니다": " 既不是 0~9 的单个数字,也不是 \"-\"", "객체가 있는 다른 프레임이 없습니다.": "没有其他含对象的帧。", "객체가 현재 프레임에 이미 있습니다 (하이라이트 표시).": "对象已存在于当前帧(高亮显示)。", "를 새 ID로 붙여넣을까요?": "要作为新 ID 粘贴吗?", "모드": "模式", "받음": "已接收", "와 \"": "与 \"", "의 width·height·length 가 0 보다 큰 수가 아니다": " 的 width·height·length 不是大于 0 的数", "의 키": "的键", "이상 정수여야 합니다). 자동 닫기를 끕니다.": " 以上的整数)。已关闭自动关闭。", "작업을 완료로 기록합니다 (Shift+Space)": "作业记录为完成 (Shift+Space)", "작업을 완료로 기록했습니다.": "作业已记录为完成。", "치수를": "尺寸", "클래스 변경 (": "类别变更(", "클래스를": "类别", "\" 가 함께 있다 — 하나만 둔다": "\" 同时存在 — 只能保留一个", "\". 클래스색을 사용합니다.": "\"。将使用类别色。", "\"에 유효한 pointCount가 없습니다 (받은 값:": "\" 没有有效的 pointCount(收到的值:", "\"의 pointCount가 유효하지 않습니다 (받은 값:": "\" 的 pointCount 无效(收到的值:", "). 생성을 무시합니다.": ")。已忽略该创建。", ")를 시퀀스 전체 프레임에 적용할까요?": ")应用到序列的全部帧吗?", ")입니다.": ")。", ".subclasses 가 배열이 아니다": ".subclasses 不是数组", ".subclasses 에 빈 문자열이거나 문자열이 아닌 항목이 있다": ".subclasses 中有空字符串或非字符串项", "ID 유지 붙여넣기:": "保留 ID 粘贴:", "[AnnotationColorService] 색 오버라이드가 #RRGGBB 형식이 아닙니다: \"": "[AnnotationColorService] 颜色覆盖不是 #RRGGBB 格式:\"", "[labeling-engine] groupPoint 인터페이스 \"": "[labeling-engine] groupPoint 接口 \"", "[labeling-engine] polygon 인터페이스 \"": "[labeling-engine] polygon 接口 \"", "].name 이 비어 있다": "].name 为空", "categories 에": "categories 中", "categories 에 예약된 이름 \"": "categories 中有保留名 \"", "categories 에 예약된 이름": "categories 中有保留名", "cm/키)": "cm/键)", "config.json을 읽지 못했습니다 —": "无法读取 config.json —", "°/키)": "°/键)", "✅ 로드 완료 —": "✅ 载入完成 —", "개": "个", "개 — 저장할 수 없습니다.": " 个 — 无法保存。", "개 — 프레임을 이동할 수 없습니다.": " 个 — 无法切换帧。", "개 실패": " 个失败", "개 있습니다.": " 个。", "개 프레임 반영,": " 帧已写入,", "개 프레임에 적용했습니다.": " 帧已应用。", "개 프레임에 통일했습니다.": " 帧已统一。", "개)를 삭제할까요? (Ctrl+Z로 되돌릴 수 있습니다)": " 个)吗?(可用 Ctrl+Z 撤销)", "개)으로 교체할까요?": " 个)替换吗?", "개가 드롭되었습니다 — 시퀀스 JSON 1개만 열 수 있습니다.": " 个被丢弃 — 只能打开 1 个序列 JSON。", "개를 복사했습니다.": " 个已复制。", "개를 상하한 예외로 지정했습니다.": " 个已设为上下限例外。", "개를 이전 프레임 내용(": " 个沿用上一帧内容(", "개를 해제했습니다.": " 个已解除。", "개의": " 个的", "개의 치수를 바꿔 저장합니다.": " 个的尺寸将被修改并保存。", "객체 수 기록 실패 (": "对象数量上报失败(", "건": "条", "로그인 실패 (status:": "登录失败 (status: ", "사유는": "原因为", "의 클래스(": " 的类别(", "이름은": "名称为", "이미 이 프레임에서 사용 중인 ID입니다. (#": "该 ID 已在本帧使用中。(#", "이전 프레임 객체": "上一帧对象", "자까지입니다.": " 字符。", "작업 상태 저장 실패 (": "作业状态保存失败(", "작업 상태 조회 실패 (": "作业状态查询失败(", "저장한 뒤": "保存后", "지원하지 않는 PCD DATA 형식:": "不支持的 PCD DATA 格式:", "치수 통일 —": "尺寸统一 —", "클래스 일괄 적용 —": "类别批量应用 —", "파일": "文件", "파일 다운로드 실패 (": "文件下载失败(", "파일 업로드 실패 (": "文件上传失败(", "프리사인 URL 요청 실패 (": "预签名 URL 请求失败(", "현재 프레임의 객체": "当前帧的对象", "현재 프레임의 모든 객체(": "当前帧的全部对象(", }; /* ============ 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°/次)"], /* ======== v1.3.0:作业状态 / 作业完成 / 退回(含变量) ======== ⚠ 必须排在下面的片段兜底之前:这些是整句规则, 否则会被拆碎而永不命中 */ /* 先清内层:平台的 WF() 会把角色标签用「 · 」拼起来 (none→객체 · 속성 / object→객체 / attribute→속성 / reviewer→검수)。 若不在整句规则之前译掉,整句替换后内层仍残留韩文, 而结果长度超过 24 又会跳过词组兜底 → 整句被判为未翻译(实测踩到)。 长组合排前面,避免先命中短的。 */ [/객체 · 속성 · 검수/, "对象 · 属性 · 质检"], [/객체 · 속성/, "对象 · 属性"], [/객체 · 검수/, "对象 · 质检"], [/속성 · 검수/, "属性 · 质检"], [/^저장한 뒤 (.+) 작업을 완료로 기록합니다 \(Shift\+Space\)$/, "保存后将「$1」作业记为完成 (Shift+Space)"], [/^(.+) 작업을 완료로 기록했습니다\.$/, "已将「$1」作业记为完成。"], [/^사유는 (\d+)자까지입니다\.$/, "退回原因最多 $1 字。"], [/^이름은 (\d+)자까지입니다\.$/, "姓名最多 $1 字。"], [/^프레임 \((\d+)\)$/, "帧 ($1)"], [/^ID 유지 붙여넣기: (.+)$/, "保持 ID 粘贴:$1"], /* 类别变更通知(带「应用到全部帧」动作的 toast) */ [/^#(\d+) 클래스 변경 \((.+)\)$/, "#$1 类别变更($2)"], [/^프레임 상하한 예외 — 이탈 (\d+)개 · 예외 (\d+)개\./, "该帧上下限例外 — 超范围 $1 个 · 例外 $2 个。"], /* 配置读取/校验失败(config.json 损坏时作为原因提示) ⚠ 这些原文都是**模板串**(`categories 中 ${A} ...`), 运行时才拼成整句,所以只能用规则匹配整句,不能放 DICT 片段。 顺序:更具体的(含固定词「예약된 이름」)排在通用的前面。 */ [/^config\.json을 읽지 못했습니다 — (.+)$/, "读取 config.json 失败 — $1"], [/^categories 에 예약된 이름 (.+) 가 있다 — 렌더링 폴백 전용이다$/, "categories 使用了保留名 $1 — 该名称仅供渲染兜底使用"], [/^categories 에 (.+) 가 중복이다$/, "categories 中 $1 重复"], [/^categories\[(.+)\]\.name 이 비어 있다$/, "categories[$1].name 为空"], [/^categories\[(.+)\] 가 객체가 아니다$/, "categories[$1] 不是对象"], [/^(.+)\.subclasses 가 배열이 아니다$/, "$1.subclasses 不是数组"], [/^(.+)\.subclasses 에 빈 문자열이거나 문자열이 아닌 항목이 있다$/, "$1.subclasses 中存在空字符串或非字符串项"], [/^size_ranges\.(.+)\.(.+) 가 \[하한, 상한\] 형식이 아니다 — 하한은 0 이상, 상한은 0 초과, 하한 < 상한$/, "size_ranges.$1.$2 不是 [下限, 上限] 格式 — 下限需 ≥0、上限需 >0,且下限 < 上限"], [/^size_ranges\.(.+) 가 객체가 아니다$/, "size_ranges.$1 不是对象"], [/^(.+) 와 "(.+)" 가 함께 있다 — 하나만 둔다$/, "$1 与 \"$2\" 同时存在 — 只能保留一个"], [/^(.+)\.(.+) 의 width·height·length 가 0 보다 큰 수가 아니다$/, "$1.$2 的 width·height·length 不是大于 0 的数"], [/^(.+) 의 키 (.+) 가 한 자리 숫자\(0~9\)도 "-" 도 아니다$/, "$1 的键 $2 既不是一位数字(0~9)也不是 \"-\""], [/^(.+) 가 빈 객체다$/, "$1 是空对象"], [/^(.+) 가 객체가 아니다$/, "$1 不是对象"], [/^이탈 객체 (\d+)개를 상하한 예외로 지정했습니다\.$/, "已把 $1 个超范围对象指定为例外。"], [/^상하한 예외 (\d+)개를 해제했습니다\.$/, "已解除 $1 个上下限例外。"], /* 尺寸超范围警告 */ [/\(허용 ([\d.]+)~([\d.]+)\)/, "(允许 $1~$2)"], [/([\d.]+) — 허용 범위 이탈/, "$1 — 超出允许范围"], [/([\d.]+) — 정방형 강제로 연동됨/, "$1 — 正方形强制联动"], [/^크기 허용 범위 이탈 — (.+) · subclass 확인 필요$/, "尺寸超出允许范围 — $1 · 需确认子类别"], [/^크기 허용 범위 이탈 — (.+) · 상하한 예외로 지정됨$/, "尺寸超出允许范围 — $1 · 已指定为上下限例外"], /* 列表徽标的 tooltip:크기 허용 범위 이탈 — L 3.46 (허용 a~b) (必须先于下面的通用规则,否则「크기」会被当成 $1 原样留下) */ [/^크기 허용 범위 이탈 — (.+)$/, "尺寸超出允许范围 — $1"], [/^(.+) 허용 범위 이탈 — (.+)$/, "$1 超出允许范围 — $2"], /* 顶部提示框:走帧 / 保存被尺寸超范围拦截(v1.2.0) 原文:크기 허용 범위 이탈 객체 N개 — 프레임을 이동할 수 없습니다. 说明:<清单> — subclass가 맞는지 확인하고 크기를 고친 뒤 이동하세요. 실물이 이 크기가 맞으면 크기 패널에서 상하한 예외를 켜세요. ⚠ 整句规则必须排在下面的片段兜底之前,否则会被拆碎而永不命中 */ [/^크기 허용 범위 이탈 객체 (\d+)개 — 프레임을 이동할 수 없습니다\.$/, "有 $1 个对象尺寸超出允许范围 — 无法移动帧。"], [/^크기 허용 범위 이탈 객체 (\d+)개 — 저장할 수 없습니다\.$/, "有 $1 个对象尺寸超出允许范围 — 无法保存。"], [/^저장된 프레임에 크기 허용 범위 이탈 객체 (\d+)개$/, "已保存的帧中有 $1 个对象尺寸超出允许范围"], [/— subclass가 맞는지 확인하고 크기를 고친 뒤 이동하세요\. 실물이 이 크기가 맞으면 크기 패널에서 상하한 예외를 켜세요\./, "— 请确认子类别是否正确、修正尺寸后再移动。若实物确实是该尺寸,请在尺寸面板中开启「上下限例外」。"], [/— subclass가 맞는지 확인하고 크기를 고친 뒤 저장하세요\. 실물이 이 크기가 맞으면 크기 패널에서 상하한 예외를 켜세요\./, "— 请确认子类别是否正确、修正尺寸后再保存。若实物确实是该尺寸,请在尺寸面板中开启「上下限例外」。"], /* 保存 / 移动 / 清空的确认与拦截 */ [/^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 拆分的文本节点与拼接句使用) ⚠ v1.3.0 修正:translateText 会先把正文 trim,所以片段规则**不能带前导空格**, 否则「 · subclass 확인 필요」这种独立文本节点永远命不中(实测漏译)。 */ [/크기 허용 범위 이탈 — /, "尺寸超出允许范围 — "], [/허용 범위 이탈/, "超出允许范围"], [/· subclass 확인 필요/, "· 需确认子类别"], [/\. 크기가 맞다면 subclass 오지정일 수 있으니 subclass를 확인하세요\./, "。若尺寸正确,可能是子类别指定错误,请检查子类别。"], /* 尺寸超范围拦截提示的片段兜底(规则措辞变了也不会漏译) */ [/크기 허용 범위 이탈 객체 /, "尺寸超出允许范围的对象 "], [/(\d+)개 — 프레임을 이동할 수 없습니다\./, "共 $1 个 — 无法移动帧。"], [/(\d+)개 — 저장할 수 없습니다\./, "共 $1 个 — 无法保存。"], [/subclass가 맞는지 확인하고 크기를 고친 뒤 이동하세요/, "请确认子类别是否正确、修正尺寸后再移动"], [/subclass가 맞는지 확인하고 크기를 고친 뒤 저장하세요/, "请确认子类别是否正确、修正尺寸后再保存"], [/실물이 이 크기가 맞으면 크기 패널에서 상하한 예외를 켜세요/, "若实物确实是该尺寸,请在尺寸面板中开启「上下限例外」"], /* 上下限例外(复选框说明 / 徽标悬浮提示 / 尺寸行角标) ⚠ 同上的 trim 问题:不带前导空格 */ [/· 상하한 예외로 지정됨/, "· 已指定为上下限例外"], [/ \(예외 지정됨\)/, "(已指定例外)"], [/\(없음\)/, "(无)"], [/상하한 예외/, "上下限例外"], [/크기 패널/, "尺寸面板"], [/저장·이동 차단/, "保存·移动拦截"], /* 尺寸行 tooltip:轴名 + 状态(轴名单独出现时由 DICT 处理, 这里是拼接进长句后的形态:「length (전후, 로컬 +x) — 허용 범위 이탈 (예외 지정됨)」) */ [/^length \(전후, 로컬 \+x\)/, "length(前后,局部 +x)"], [/^width \(좌우, 로컬 \+y\)/, "width(左右,局部 +y)"], [/^height \(상하\)/, "height(上下)"], [/ — 허용 범위 이탈/, " — 超出允许范围"], [/ — 정방형 강제로 연동됨/, " — 正方形强制联动"], /* 载入 / 上传进度(平台更新后的新文案,v1.2.0 补齐) ⚠ v1.3.0 修 bug:原来「^(.+) 받음$ → 已接收 $1」排在最前, 会把整条「파일 5/5 · 15.3 MB 받음」先吃掉 → 变成 「已接收 파일 5/5 · 15.3 MB」("파일" 仍是韩文,且后面的 「^파일 N/N」规则再也命不中)。故**整行规则必须排在通用 받음 之前**。 */ [/네트워크가 느립니다 — 수신이 멈춰 있습니다/, "网络较慢 — 接收已停止"], /* 「파일 6/6 · 16.6 MB 받음 · 8346 KB/s」(带速率) */ [/^파일 (\d+)\/(\d+) · ([\d.]+ [KMG]B) 받음 · ([\d.]+ [KMG]B)\/s$/, "文件 $1/$2 · 已接收 $3 · $4/s"], /* 「파일 1/6 · 0 KB 받음」(无速率) */ [/^파일 (\d+)\/(\d+) · ([\d.]+ [KMG]B) 받음$/, "文件 $1/$2 · 已接收 $3"], /* 通用兜底:单独的「16.6 MB 받음」 */ [/^([\d.]+ [KMG]B) 받음$/, "已接收 $1"], [/^(.+) 받음$/, "已接收 $1"], /* ⚠ 注意:正则会拿 trim 后的正文去匹配,模式里的尾随空格会永远匹配不到本行末尾 */ [/^파일 (\d+)\/(\d+)/, "文件 $1/$2"], /* 后台标签页时的 document.title 提示 */ [/✅ 로드 완료 — /, "✅ 载入完成 — "], [/^객체 수 기록 실패 \((\d+)\)/, "对象数记录失败 ($1)"], [/Ctrl\+Z로 되돌릴 수 있습니다/, "可用 Ctrl+Z 恢复"], [/Ctrl\+Z로 복원 가능/, "可用 Ctrl+Z 恢复"], [/한국어/, "韩语"], [/영문/, "英文"], /* ---- v1.5.0:新版多变量句式 ---- */ [/^#(\d+) 치수 통일 — 기준은 #(\d+) 프레임 \((.+)\)입니다\.$/, "#$1 尺寸统一 — 基准为 #$2 帧($3)"], [/^고른 (\d+)개 → 기준 치수로 통일$/, "选中的 $1 个 → 统一为基准尺寸"], [/^고르지 않은 (\d+)개 → 새 ID (\S+)$/, "未选中的 $1 个 → 新 ID $2"], [/^▼ (\d+)점\. 이어서 고칠 수 있습니다\.$/, "▼ $1 点。可以继续修改。"], /* v1.5.1 尺寸统一面板头部(截图箭头 2) */ [/^같은 ID 의 다른 프레임 (\d+)개$/, "同一 ID 的其他 $1 个帧"], [/^결과 확인 — 기준과 다름 (\d+) \/ (\d+)$/, "查看结果 — 与基准不同 $1 / $2"], /* v1.5.2 相机缩略图 alt / 悬浮标题 */ [/^#(\d+) 프레임의 객체$/, "#$1 帧的对象"], [/^누르면 이 프레임의 객체를 화면에 띄웁니다$/, "点击把本帧对象显示到画面上"], ]; /* ============ 3. 词组兜底(仅用于短文本,保证用词规范) ============ */ var PHRASES = [ ["큐보이드", "立方体"], ["어노테이션", "标注"], ["서브클래스", "子类别"], ["카테고리", "类别"], ["클래스", "类别"], ["시퀀스", "序列"], ["프레임", "帧"], ["카메라", "摄像头"], ["채널", "通道"], ["객체", "对象"], ["속성", "属性"], ["크기", "尺寸"], ["높이", "高度"], ["길이", "长"], ["너비", "宽"], ["두께", "粗细"], ["배율", "倍率"], ["필터", "过滤"], ["스텝", "步长"], ["속도", "速度"], ["정렬", "对齐"], ["회전", "旋转"], ["이동", "平移"], ["생성", "创建"], ["삭제", "删除"], ["저장", "保存"], ["불러오기", "载入"], ["초기화", "重置"], ["지우기", "清除"], ["선택", "选择"], ["표시", "显示"], ["숨김", "隐藏"], ["보정", "修正"], ["재결합", "重新合并"], ["투영", "投影"], ["복사", "复制"], ["붙여넣기", "粘贴"], ["되돌릴", "恢复"], ["복원", "恢复"], ["확인", "确定"], ["취소", "取消"], ["닫기", "关闭"], ["전체", "全部"], ["미지정", "未指定"], ["미입력", "未填写"], ["미등록", "未登记"], ["허용 범위", "允许范围"], ["이탈", "超出"], ["기준", "基准"], ["프리셋", "预设"], ["적용", "应用"], ["테두리", "边框"], ["경계", "边界"], ["단면", "截面"], ["강제", "强制"], ["반대면", "对面"], ["고정", "固定"], ["자차", "自车"], ["방향", "方向"], ["맞춤", "适配"], ["앞으로", "朝前"], ["화면", "视图"], ["줌", "缩放"], ["휠", "滚轮"], ["클릭", "点击"], ["우클릭", "右键"], ["드래그", "拖拽"], ["커서", "光标"], ["위치", "位置"], ["무시", "忽略"], ["교차", "交叉"], ["실패", "失败"], ["완료", "完成"], /* 尺寸上下限例外术语(v1.2.0) */ ["상하한 예외", "上下限例外"], ["예외", "例外"], ["지정됨", "已指定"], ["실물", "实物"], ["차단", "拦截"], ["통과", "放行"], /* 作业状态 / 作业者信息术语(v1.3.0) 只收「无歧义」的词:这些词在平台里语义唯一,逐词替换不会串味。 (검수 与已存在的 검수자 是两个键,互不影响) */ ["검수", "质检"], ["미착수", "未开始"], ["반려", "退回"], ["되돌림", "待重做"], ["역할", "角色"], ["담당자", "负责人"], ["사유", "原因"], ["해제", "解除"], ["전용", "专用"], ["이메일", "邮箱"], ["이름", "姓名"], ["작업", "作业"], ["손본다", "修整"], ["기록", "记录"] ]; /* ============ 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", "segment": "语义分割", /* ---- 大类别(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": "禁止停车标志", "no_park_sign_circle": "禁止停车标志(圆形)", "no_park_sign_square": "禁止停车标志(方形)", "pillar_unknown": "其他停车柱", "speedbump": "减速带", "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, "no_park_sign_circle": 1, "no_park_sign_square": 1, "pillar_unknown": 1, "speedbump": 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) { /* ⭐ v1.5.2【大类/子类复合写法】────────────────────────────────── 平台相机缩略图、对象行显示的是 `VEHICLE/sedan`:数据层就是 `hu=(cat,sub)=>`${cat}/${sub}``,整串在词典里没有键 ⇒ 之前原样留英文。 这里按 `/` 拆开逐段查(同一 EN_DICT、同一 labelsOn 开关), **只有每一段都命中**才拼回去 —— 路径、文件名、`a/b` 尺寸这类 拆开后查不到词,仍原样返回,不会误伤。 */ if (core.indexOf("/") !== -1) { var parts = core.split("/"), outs = [], allHit = true, pi, k2, h2; for (pi = 0; pi < parts.length; pi++) { k2 = parts[pi].trim().toLowerCase(); h2 = EN_DICT[k2]; if (!k2 || h2 === undefined || (!labelsOn && LABEL_KEYS[k2])) { allHit = false; break; } outs.push(h2); } if (allHit && outs.length > 1) return m[1] + outs.join("/") + m[3]; } 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; } /* ⚠⚠ v1.4.0【多行文案必须逐行翻】──────────────────────────────────── 平台的原生确认框是「标题 + 空行 + 选项说明」的多行结构,例如: `#${c}를 새 ID로 붙여넣을까요? ← 空行 확인 — 새 ID 발급 (트래킹 분리) 취소 — 이 객체 붙여넣기 취소` 整串查 DICT 必然落空(没有这种整串的键),而整句 RULES 只锚定了第一行 —— 第 2 行起全是独立的「选项说明」句,它们在 DICT 里有条目,但 **DICT 只在「整串精确命中」时生效,不会作用于被规则替换后的残句**。 结果:第一行中文、后面全韩文(实测粘贴冲突框、类批量应用框都是这样)。 ⇒ 这里先按 \n 拆开、逐行走完整链路,再拼回去。 逐行后仍有韩文就继续往下走整串规则兜底(顺序很重要,不能 return 死)。 */ if (core.indexOf("\n") !== -1) { var lines = core.split("\n"), touched = false, k; for (k = 0; k < lines.length; k++) { if (!HANGUL.test(lines[k])) continue; var lv = translateText(lines[k]); if (lv !== lines[k] && !HANGUL.test(lv)) { lines[k] = lv; touched = true; } } if (touched) { var joined = lines.join("\n"); if (!HANGUL.test(joined)) return lead + joined + tail; } } var out = core, i; /* 依次应用全部规则(每条只针对韩文模式,译后为中文不会再被后续规则命中) ⚠ 循环替换:同一条消息里同一句式可能出现多次 —— 例如尺寸超范围清单 「#128 sedan: L 3.46 (허용 3.937~4.663) #142 sedan: W 2.19 (허용 1.5~2.1) …」 而 String.replace 不带 /g 时只替换第一处,导致后面几条原样留韩文。 这里对每条规则反复替换到不再变化(上限 40 次,防规则自匹配死循环)。 */ for (i = 0; i < RULES.length; i++) { var re = RULES[i][0], rp = RULES[i][1]; for (var guard = 0; guard < 40; guard++) { var next = out.replace(re, rp); if (next === out) break; out = next; } } 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; var core = v.trim(); /* 对象列表的「尺寸告警徽标」:크기 ⚠ 「크기」不能进 DICT 全局译成「尺寸异常」——「类别」面板里还有一处同名 span("将以所选类别的 [크기] 创建立方体"),全局译会毁掉那句话。 故按父元素 title 前缀做上下文识别,只改徽标这一处。 译完后 title 已变中文,所以中韩两种前缀都要认(保证幂等)。 */ var pTitle = p.getAttribute ? p.getAttribute("title") : null; if (core === "크기" && pTitle && /^(크기 허용 범위 이탈|尺寸超出允许范围)/.test(pTitle)) { t = v.replace(core, "尺寸异常"); } else if (p.tagName === "LABEL" && (core === "T" || core === "O")) { t = v.replace(core, core === "T" ? "截断" : "遮挡"); } else { t = translateText(v); } if (t !== v) { if (!rawText.has(node)) rawText.set(node, v); node.nodeValue = t; } } else if (node.nodeType === 1) { if (SKIP_TAGS[node.tagName]) return; translateAttrs(node); var w = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, null); var n; while ((n = w.nextNode())) translateNode(n); var all = node.querySelectorAll ? node.querySelectorAll("*") : []; for (var i = 0; i < all.length; i++) translateAttrs(all[i]); } } /* ============ 6. 开关与开关状态 ============ */ var rawText = new WeakMap(); var enabled = true; var labelsOn = true; try { if (localStorage.getItem(STORE_KEY) === "0") enabled = false; if (localStorage.getItem(LABELS_KEY) === "0") labelsOn = false; } catch (e) { /* 隐私模式下忽略 */ } function applyAll() { translateNode(document.body); if (window.__ADO_TITLE_RAW === undefined) window.__ADO_TITLE_RAW = document.title; var t = translateText(document.title || ""); if (t && t !== document.title) document.title = t; } /* 卸载旧实例(重复注入 / 热更新时使用) */ function destroy() { if (mo) { mo.disconnect(); mo = null; } if (timer) { clearTimeout(timer); timer = null; } if (tailTimer) { clearTimeout(tailTimer); tailTimer = null; } try { restoreAll(); } catch (e) { } var bx = document.getElementById("ado-cn-toggle"); if (bx && bx.parentNode) bx.parentNode.removeChild(bx); } function restoreAll() { /* 1) 文本节点还原 */ var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null); var n; while ((n = tw.nextNode())) { if (rawText.has(n)) { try { n.nodeValue = rawText.get(n); } catch (e) { /* textarea 等只读场景 */ } } } /* 2) 属性还原 */ var all = document.querySelectorAll("[title],[aria-label],[placeholder],[alt]"); for (var i = 0; i < all.length; i++) { var el = all[i], raw = el.__adoRaw; if (!raw) continue; for (var k in raw) { if (Object.prototype.hasOwnProperty.call(raw, k) && raw[k] !== undefined) { el.setAttribute(k, raw[k]); } } } /* 3) 标题还原 */ if (window.__ADO_TITLE_RAW !== undefined) document.title = window.__ADO_TITLE_RAW; } /* ============ 7. MutationObserver(增量翻译,静默期防自触发循环) ============ */ var mo = null, timer = null, tailTimer = null, queued = []; function observeTarget() { if (!mo) return; mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ATTRS }); } function flush() { timer = null; if (!enabled) { queued = []; return; } var list = queued; queued = []; /* 关键:翻译自身会改动 DOM,先断开观察器,避免回调自触发导致循环 */ if (mo) mo.disconnect(); try { for (var i = 0; i < list.length; i++) { try { translateNode(list[i]); } catch (e) { /* 忽略单个节点异常 */ } } applyAll(); } finally { if (mo && enabled) observeTarget(); } } function schedule(immediate) { if (timer) clearTimeout(timer); timer = setTimeout(flush, immediate ? 0 : 60); /* 尾部补偿:展开动画 / WebGL 阻塞可能让内容晚于观察回调出现 */ if (!tailTimer) { tailTimer = setTimeout(function () { tailTimer = null; flush(); }, 400); } } function startObserver() { mo = new MutationObserver(function (muts) { if (!enabled) return; if (!muts.length) return; for (var i = 0; i < muts.length; i++) { var m = muts[i]; if (m.type === "characterData") { queued.push(m.target.parentNode || m.target); } else if (m.type === "childList") { for (var j = 0; j < m.addedNodes.length; j++) queued.push(m.addedNodes[j]); } else if (m.type === "attributes") { queued.push(m.target); } } schedule(false); }); observeTarget(); } /* ============ 8. 原生弹窗(confirm/alert)翻译 ============ */ function patchDialogs() { var wConfirm = window.confirm, wAlert = window.alert; if (wConfirm && !window.__adoCnConfirm) { window.confirm = function (msg) { return wConfirm.call(window, translateText(String(msg))); }; window.__adoCnConfirm = true; } if (wAlert && !window.__adoCnAlert) { window.alert = function (msg) { return wAlert.call(window, translateText(String(msg))); }; window.__adoCnAlert = true; } } /* ============ 9. 悬浮开关 ============ */ function buildUI() { var box = document.createElement("div"); box.id = "ado-cn-toggle"; box.style.cssText = [ "position:fixed", "right:10px", "bottom:10px", "z-index:2147483647", "width:30px", "height:30px", "border-radius:50%", "display:flex", "align-items:center", "justify-content:center", "font:600 12px/1 system-ui,'Microsoft YaHei',sans-serif", "color:#fff", "cursor:pointer", "user-select:none", "box-shadow:0 2px 8px rgba(0,0,0,.45)", "opacity:.9", "transition:background .15s,opacity .15s" ].join(";"); box.title = "ADO 界面翻译:左键切换中/韩(Ctrl+Alt+T)|右键切换类别名是否翻译(Ctrl+Alt+L)"; function paint() { box.textContent = enabled ? "中" : "韩"; if (!enabled) { box.style.background = "rgba(100,116,139,.95)"; /* 灰 = 韩文原文 */ } else if (!labelsOn) { box.style.background = "rgba(217,119,6,.95)"; /* 橙 = 译文,类别名保持英文 */ } else { box.style.background = "rgba(22,163,74,.95)"; /* 绿 = 全中文 */ } } box.addEventListener("click", function () { toggle(); }); box.addEventListener("contextmenu", function (e) { e.preventDefault(); toggleLabels(); }); paint(); box.__paint = paint; (document.body || document.documentElement).appendChild(box); window.__adoCnBox = box; } function toggle() { enabled = !enabled; try { localStorage.setItem(STORE_KEY, enabled ? "1" : "0"); } catch (e) { } if (enabled) { applyAll(); } else { restoreAll(); } if (window.__adoCnBox && window.__adoCnBox.__paint) window.__adoCnBox.__paint(); } /* 类别 / 子类别名属数据标签,可单独切换是否翻译 */ function toggleLabels() { labelsOn = !labelsOn; try { localStorage.setItem(LABELS_KEY, labelsOn ? "1" : "0"); } catch (e) { } if (enabled) { restoreAll(); applyAll(); } if (window.__adoCnBox && window.__adoCnBox.__paint) window.__adoCnBox.__paint(); console.log("[adoCN] 类别/子类别名翻译:" + (labelsOn ? "已开启" : "已关闭(保持英文)")); } /* ============ 10. 启动 ============ */ function boot() { /* 防重复注入:先卸载上一个实例 */ if (typeof window.__adoCnDestroy === "function") { try { window.__adoCnDestroy(); } catch (e) { } } window.__adoCnDestroy = destroy; window.__adoCnInstalled = true; patchDialogs(); if (enabled) applyAll(); startObserver(); buildUI(); document.addEventListener("keydown", function (e) { if (e.ctrlKey && e.altKey && (e.key === "t" || e.key === "T")) { e.preventDefault(); toggle(); } else if (e.ctrlKey && e.altKey && (e.key === "l" || e.key === "L")) { e.preventDefault(); toggleLabels(); } }, true); window.adoCN = { on: function () { if (!enabled) toggle(); }, off: function () { if (enabled) toggle(); }, toggle: toggle, labels: function (v) { if (v === undefined) { console.log("[adoCN] 类别名翻译:" + labelsOn); return labelsOn; } if (!!v !== labelsOn) toggleLabels(); return labelsOn; }, reapply: applyAll, destroy: destroy, report: function () { var keys = Object.keys(MISS); if (!keys.length) { console.log("[adoCN] 全部韩文均已翻译,无遗漏。"); return []; } console.log("[adoCN] 未命中词典的韩文(" + keys.length + " 条):"); keys.sort(function (a, b) { return MISS[b] - MISS[a]; }); keys.forEach(function (k) { console.log(" " + MISS[k] + " × " + k); }); if (console.table) { console.table(keys.map(function (k) { return { 韩文: k, 次数: MISS[k] }; })); } return keys; } }; console.log("[adoCN] ADO 韩中翻译已加载。Ctrl+Alt+T 切换中/韩。adoCN.report() 查看未译条目。"); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", boot); } else { boot(); } })();