Files
App/www/i18n.js
HiepLM a2e87aeb29
Some checks failed
Test / test (push) Has been cancelled
Add function Language
2026-06-16 16:44:04 +07:00

772 lines
38 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Central i18n for LiDAR Manager — vi / en.
* Static DOM: data-i18n, data-i18n-placeholder, data-i18n-title, data-i18n-aria
* Dynamic JS: I18n.t("key") or I18n.t("key", { name: "..." })
*/
(() => {
const MESSAGES = {
vi: {
"app.title": "LiDAR Manager",
"app.robotName": "RobotApp",
"app.status.ready": "Sẵn sàng",
"app.status.reloaded": "Đã tải lại",
"app.status.backendError": "Không kết nối được backend",
"app.status.jsError": "Lỗi JavaScript",
"common.cancel": "Hủy",
"common.close": "Đóng",
"common.save": "Lưu",
"common.add": "Thêm",
"common.delete": "Xóa",
"common.apply": "Áp dụng",
"common.reload": "Tải lại",
"common.select": "Chọn",
"common.edit": "Sửa",
"common.enabled": "Bật",
"common.disabled": "Tắt",
"common.configure": "Cấu hình",
"common.error": "Lỗi: {msg}",
"common.none": "none",
"common.optional": "Tùy chọn",
"login.prompt": "Chọn cách đăng nhập:",
"login.tab.password": "Tên đăng nhập và mật khẩu",
"login.tab.pin": "Mã PIN",
"login.password.title": "Đăng nhập bằng tên và mật khẩu",
"login.password.help1": "Nhập tên đăng nhập và mật khẩu để truy cập robot.",
"login.password.help2": "Tài khoản do quản trị viên cấp hoặc xem trong tài liệu hướng dẫn robot.",
"login.password.help3": "Nếu chưa có tài khoản, vui lòng liên hệ quản trị viên robot.",
"login.field.username": "Tên đăng nhập:",
"login.field.password": "Mật khẩu:",
"login.placeholder.username": "Admin",
"login.submit": "Đăng nhập",
"login.submitting": "Đang đăng nhập…",
"login.pin.title": "Đăng nhập bằng mã PIN",
"login.pin.help1": "Người dùng được kích hoạt PIN có thể đăng nhập tại đây.",
"login.pin.help2": "Nếu chưa có mã PIN 4 chữ số, vui lòng liên hệ quản trị viên robot.",
"login.pin.helpNote": "Không có mã PIN cấu hình sẵn — quản trị viên phải gán PIN trước.",
"login.pin.aria.group": "Mã PIN 4 chữ số",
"login.pin.aria.keypad": "Bàn phím số",
"login.pin.aria.backspace": "Xóa",
"login.error.invalidPin": "Mã PIN không hợp lệ. Liên hệ quản trị viên.",
"login.error.invalidPinShort": "Mã PIN không hợp lệ",
"login.error.missingCredentials": "Nhập tên đăng nhập và mật khẩu",
"login.error.badCredentials": "Sai tên đăng nhập hoặc mật khẩu. Thử Admin / admin",
"login.error.serverUnreachable": "Không kết nối được server. Kiểm tra http://localhost:8080",
"login.error.failed": "Đăng nhập thất bại",
"nav.aria.main": "Điều hướng chính",
"nav.aria.submenu": "Menu phụ",
"nav.collapse": "Thu gọn menu",
"nav.expand": "Mở menu",
"nav.dashboards": "Dashboards",
"nav.setup": "Setup",
"nav.monitoring": "Monitoring",
"nav.system": "System",
"nav.help": "Help",
"nav.logout": "Log out",
"nav.dashboard": "Dashboard",
"nav.missions": "Missions",
"nav.maps": "Maps & layout",
"nav.monitoring-log": "System log",
"nav.integrations": "Tích hợp",
"nav.help-api": "API documentation",
"topbar.robotTitle": "Robot",
"topbar.controlAria": "Start / Pause robot",
"topbar.allOk": "ỔN ĐỊNH",
"topbar.error": "LỖI",
"topbar.paused": "TẠM DỪNG",
"topbar.running": "ĐANG CHẠY",
"topbar.waiting": "Đang chờ mission mới…",
"topbar.noMissionsQueue": "Không có mission trong queue…",
"topbar.reset": "RESET",
"topbar.changeUserData": "Lưu thông tin",
"topbar.changePassword": "Đổi mật khẩu",
"topbar.logout": "Đăng xuất",
"topbar.displayName": "Tên hiển thị",
"topbar.joystickTitle": "Điều khiển tay (Joystick)",
"topbar.joystickSpeed": "Tốc độ",
"topbar.joystickOff": "Tắt joystick",
"topbar.joystickAria": "Joystick",
"topbar.batteryTitle": "Pin",
"topbar.localeVi": "TIẾNG VIỆT",
"topbar.localeEn": "ENGLISH",
"topbar.localeOption.vi": "🇻🇳 Tiếng Việt",
"topbar.localeOption.en": "🇺🇸 English",
"topbar.userDefault": "USER",
"topbar.noControlPermission": "Không có quyền điều khiển",
"topbar.queueCount": "{n} mission trong queue",
"topbar.code": "Mã",
"topbar.module": "Module",
"topbar.joystickSpeed.slow": "Chậm",
"topbar.joystickSpeed.medium": "Trung bình",
"topbar.joystickSpeed.fast": "Nhanh",
"topbar.startHint": "Bấm để START robot",
"topbar.pauseHint": "Bấm để PAUSE robot",
"auth.profile.displayNameRequired": "Tên hiển thị không được trống",
"auth.profile.saveFailed": "Lưu thông tin thất bại",
"auth.changePassword.title": "Đổi mật khẩu",
"auth.changePassword.current": "Mật khẩu hiện tại",
"auth.changePassword.new": "Mật khẩu mới",
"auth.changePassword.confirm": "Xác nhận mật khẩu mới",
"auth.changePassword.mismatch": "Mật khẩu mới không khớp",
"auth.changePassword.failed": "Đổi mật khẩu thất bại",
"dashboard.title": "Dashboard",
"dashboard.subtitle": "Widget mission — chạy, xếp hàng và tạm dừng giống MiR Fleet.",
"dashboard.addWidget": "Thêm widget",
"dashboard.editLayout": "Sửa layout",
"dashboard.editDone": "Xong",
"dashboard.empty": "Chưa có widget. Bấm «Thêm widget» để bắt đầu.",
"dashboard.system.title": "Hệ thống",
"dashboard.system.subtitle": "Trạng thái backend và layout đang active.",
"dashboard.system.backend": "Backend",
"dashboard.system.layout": "Layout",
"dashboard.system.model": "Model robot",
"dashboard.system.sensors": "LiDAR / IMU",
"dashboard.system.sensorCount": "{lidars} LiDAR • {imus} IMU",
"dashboard.dialog.add.title": "Thêm widget",
"dashboard.dialog.add.type": "Loại widget",
"dashboard.dialog.edit.title": "Cấu hình widget",
"dashboard.dialog.edit.type": "Loại",
"dashboard.dialog.edit.delete": "Xóa widget",
"dashboard.widget.mission_button": "Nút mission",
"dashboard.widget.mission_group": "Nhóm mission",
"dashboard.widget.mission_queue": "Mission queue",
"dashboard.widget.pause_continue": "Tạm dừng / Tiếp tục",
"dashboard.widget.field.mission": "Mission",
"dashboard.widget.field.group": "Nhóm mission",
"dashboard.widget.field.title": "Tiêu đề widget (tùy chọn)",
"dashboard.widget.titlePlaceholder": "VD: Go to charging",
"dashboard.widget.pauseHint": "Tạm dừng / tiếp tục / hủy mission đang chạy trên robot.",
"dashboard.widget.selectMission": "Chọn mission…",
"dashboard.widget.configHint": "Cấu hình widget và chọn mission.",
"dashboard.widget.emptyGroup": "Không có mission trong nhóm «{group}».",
"dashboard.widget.queueEmpty": "Queue trống",
"dashboard.widget.clearQueue": "Xóa queue chờ",
"dashboard.widget.continue": "Tiếp tục",
"dashboard.widget.pause": "Tạm dừng",
"dashboard.widget.cancelMission": "Hủy mission",
"dashboard.widget.runner.paused": "Mission đang tạm dừng",
"dashboard.widget.runner.running": "Mission đang chạy",
"dashboard.widget.runner.idle": "Không có mission đang chạy",
"dashboard.widget.unsupported": "Widget không hỗ trợ.",
"dashboard.widget.deleteConfirm": "Xóa widget này?",
"config.layout.title": "Quản lý layout",
"config.layout.subtitle": "Nhiều cấu hình robot — mỗi layout có LiDAR và model riêng.",
"config.layout.save": "Lưu layout",
"config.layout.current": "Layout hiện tại",
"config.layout.newName": "Tên layout mới",
"config.layout.newNamePlaceholder": "VD: AGV kho A",
"config.layout.cloneCurrent": "Sao chép từ layout đang mở",
"config.layout.create": "Tạo layout",
"config.layout.editingHint": "Đang chỉnh: {name}{dirty}",
"config.layout.unsavedDirty": " • chưa lưu",
"config.layout.unsavedSwitchConfirm": "Layout hiện tại có thay đổi chưa lưu. Tiếp tục?",
"config.layout.deleteConfirm": "Xóa layout «{name}»? Hành động không hoàn tác.",
"config.lidar.title": "LiDARs",
"config.lidar.subtitle": "Đăng ký tên, IP, port và chỉnh pose theo robot frame.",
"config.lidar.field.name": "Tên",
"config.lidar.field.ip": "IP",
"config.lidar.field.port": "Port",
"config.lidar.placeholder.name": "Lidar trước",
"config.lidar.placeholder.ip": "192.168.0.10",
"config.lidar.empty": "Chưa có LiDAR",
"config.lidar.emptyHint": "Hãy thêm LiDAR ở form phía trên.",
"config.lidar.deleteConfirm": "Xóa LiDAR này?",
"config.imu.title": "IMU",
"config.imu.subtitle": "Cảm biến quán tính — frame, topic và pose trên robot.",
"config.imu.field.name": "Tên",
"config.imu.field.frame": "Frame ID",
"config.imu.field.topic": "Topic",
"config.imu.field.source": "Nguồn",
"config.imu.source.external": "Ngoài (ROS topic)",
"config.imu.source.lidarBuiltin": "Tích hợp LiDAR",
"config.imu.source.onboard": "Onboard robot",
"config.imu.field.rate": "Tần số (Hz)",
"config.imu.enabled": "Bật IMU",
"config.imu.add": "Thêm IMU",
"config.imu.placeholder.name": "IMU chính",
"config.imu.placeholder.frame": "imu_link",
"config.imu.placeholder.topic": "imu/data",
"config.imu.empty": "Chưa có IMU",
"config.imu.emptyHint": "Thêm IMU ở form phía trên.",
"config.imu.deleteConfirm": "Xóa IMU này?",
"config.robot.title": "Model robot",
"config.robot.subtitle": "Kinematic differential — bánh, động cơ và giới hạn vận tốc.",
"config.robot.model.diff": "Differential (2 bánh)",
"config.robot.model.bicycle": "Bicycle",
"config.canvas.title": "Bố trí trên robot",
"config.canvas.viewHint": "Cuộn chuột: zoom • Shift + kéo: di chuyển vùng nhìn",
"config.canvas.robotCenter": "Robot center:",
"config.canvas.selected": "Selected:",
"config.canvas.pose": "Pose:",
"config.pose.notSet": "chưa đặt pose",
"config.selected.lidar": "LiDAR: {name}",
"config.selected.imu": "IMU: {name}",
"config.motor.wheelRight": "Bánh phải",
"config.motor.wheelLeft": "Bánh trái",
"config.motor.wheelSteer": "Bánh trước (steer)",
"config.motor.wheelDrive": "Bánh sau (drive)",
"config.motor.vendor": "Hãng",
"config.motor.model": "Model",
"config.motor.joint": "Joint (ROS)",
"config.motor.ratio": "Tỷ số hộp số",
"config.motor.invert": "Đảo chiều quay",
"config.motor.invertSteer": "Đảo chiều",
"config.motor.custom": "Tùy chỉnh",
"config.motor.customMotor": "Motor tùy chỉnh",
"missions.title": "Missions",
"missions.subtitle": "Setup → Missions — danh sách nhiệm vụ robot.",
"missions.create": "Tạo mission",
"missions.empty": "Chưa có mission. Bấm Tạo mission để bắt đầu.",
"missions.queue.title": "Mission queue",
"missions.queue.subtitle": "Thêm mission bằng biểu tượng queue — robot chạy theo thứ tự từ trên xuống.",
"missions.queue.cancel": "Hủy chạy",
"missions.queue.clear": "Xóa queue",
"missions.queue.empty": "Queue trống. Bấm ▤ trên mission để thêm.",
"missions.editor.kicker": "Mission editor",
"missions.editor.unsaved": "Chưa lưu",
"missions.editor.saveAs": "Save as",
"missions.editor.save": "Save",
"missions.editor.flowHint": "Thực thi từ trên xuống dưới. Kéo biểu tượng ↔ để đổi thứ tự. Với Loop: kéo action vào vùng bên trong.",
"missions.editor.emptyActions": "Chọn action từ menu phía trên để bắt đầu.",
"missions.editor.backAria": "Quay lại danh sách",
"missions.editor.settingsAria": "Cài đặt mission",
"missions.editor.addActionAria": "Thêm action",
"missions.queue.status.pending": "Chờ",
"missions.queue.status.running": "Đang chạy",
"missions.queue.status.done": "Xong",
"missions.queue.status.error": "Lỗi",
"missions.queue.status.cancelled": "Đã hủy",
"missions.queue.ready": "Sẵn sàng",
"missions.queue.idleMessage": "Robot sẵn sàng — queue trống hoặc chờ mission mới.",
"missions.queue.moveUp": "Lên",
"missions.queue.moveDown": "Xuống",
"missions.queue.addAria": "Thêm vào mission queue",
"missions.deleteConfirm": "Xóa mission «{name}»?",
"missions.queue.clearConfirm": "Xóa các mission đang chờ trong queue?",
"missions.queue.cancelConfirm": "Hủy mission đang chạy? (thoát loop nếu đang lặp)",
"missions.dialog.create.title": "Tạo mission",
"missions.dialog.create.name": "Tên mission",
"missions.dialog.create.group": "Nhóm mission",
"missions.dialog.create.groupNew": "Hoặc nhóm mới",
"missions.dialog.create.desc": "Mô tả",
"missions.dialog.create.namePlaceholder": "VD: Go to charging station",
"missions.dialog.settings.title": "Cài đặt mission",
"missions.dialog.settings.name": "Tên",
"missions.dialog.settings.group": "Nhóm",
"missions.dialog.settings.desc": "Mô tả",
"missions.dialog.saveAs.title": "Save mission as",
"missions.dialog.saveAs.name": "Tên mission mới",
"missions.dialog.saveAs.submit": "Lưu bản sao",
"missions.dialog.actionConfig.title": "Cấu hình action",
"missions.dialog.queue.title": "Thêm vào mission queue",
"missions.group.Move": "Move",
"missions.group.Logic": "Logic",
"missions.group.IO": "I/O",
"missions.group.Cart": "Cart",
"missions.group.Misc": "Misc",
"missions.action.move_to_position": "Go to position",
"missions.action.move_to_marker": "Go to marker",
"missions.action.adjust_localization": "Adjust localization",
"missions.action.wait": "Wait",
"missions.action.set_speed": "Set speed",
"missions.action.if": "If",
"missions.action.loop": "Loop",
"missions.action.break": "Break",
"missions.action.continue": "Continue",
"missions.action.pause": "Pause",
"missions.action.set_digital_output": "Set digital output",
"missions.action.wait_digital_input": "Wait for digital input",
"missions.action.set_plc_register": "Set PLC register",
"missions.action.pick_cart": "Pick cart",
"missions.action.drop_cart": "Drop cart",
"missions.action.user_log": "User log",
"missions.action.play_sound": "Play sound",
"missions.error.nameRequired": "Tên mission không được trống.",
"missions.error.nameDuplicate": "Tên mission đã tồn tại.",
"missions.error.nameEmpty": "Tên không được trống.",
"missions.saveSuccess": "Đã lưu mission.",
"missions.editor.discardConfirm": "Bỏ thay đổi chưa lưu?",
"missions.queue.status.executing": "Đang chạy",
"missions.action.waitOnLevel": "Chờ mức ON",
"integrations.modbus.title": "Modbus trigger",
"integrations.modbus.subtitle": "System → Triggers — coil 10012000 gắn mission_id. Thiết bị remote bật coil (Modbus TCP :5502) → mission vào queue.",
"integrations.modbus.add": "Thêm trigger",
"integrations.modbus.empty": "Chưa có trigger Modbus.",
"integrations.modbus.coilsLabel": "Coil đã gán (bấm để mô phỏng rising edge)",
"integrations.rest.title": "REST API — MiR v2.0.0",
"integrations.rest.subtitle": "Hệ thống bên ngoài POST mission vào queue qua REST.",
"integrations.rest.baseUrl": "Base URL",
"integrations.rest.quickTest": "Thử nhanh",
"integrations.rest.postQueue": "POST queue",
"integrations.fleet.title": "MiRFleet — Lên lịch mission",
"integrations.fleet.subtitle": "Ưu tiên, gán robot, chạy ASAP hoặc theo thời gian.",
"integrations.fleet.addSchedule": "Thêm lịch",
"integrations.fleet.empty": "Chưa có lịch fleet.",
"integrations.noMissions": "— Chưa có mission —",
"integrations.defaultRobot": "Robot chính",
"integrations.fireTrigger": "Kích hoạt",
"integrations.coilsEmpty": "Chưa gán coil. Thêm trigger bên trên (10012000).",
"integrations.coilState": "coil hiện tại: {state}",
"integrations.confirm.deleteTrigger": "Xóa trigger Modbus này?",
"integrations.confirm.deleteSchedule": "Xóa lịch fleet này?",
"integrations.dialog.trigger.title": "Modbus trigger",
"integrations.dialog.trigger.name": "Tên trigger",
"integrations.dialog.trigger.coil": "Coil ID",
"integrations.dialog.trigger.mission": "Mission",
"integrations.dialog.schedule.title": "Lịch MiRFleet",
"integrations.dialog.schedule.name": "Tên lịch",
"integrations.dialog.schedule.robot": "Robot",
"integrations.dialog.schedule.priority": "Ưu tiên",
"integrations.dialog.schedule.mode": "Chế độ",
"integrations.dialog.schedule.asap": "ASAP",
"integrations.dialog.schedule.scheduled": "Lên lịch",
"integrations.dialog.schedule.startTime": "Thời gian bắt đầu",
"integrations.schedule.runNow": "Chạy ngay",
"monitoring.log.title": "System log",
"monitoring.log.subtitle": "Monitoring → System log — nhật ký hệ thống (đang phát triển).",
"monitoring.log.placeholder": "Tính năng monitoring sẽ hiển thị log robot, cảnh báo và lịch sử mission tại đây.",
"help.api.title": "API documentation",
"help.api.subtitle": "Help → API — tham chiếu REST MiR v2.0.0 cho tích hợp bên ngoài.",
"help.api.body1": "Xem chi tiết endpoint tại System → Tích hợp hoặc tài liệu /api/v2.0.0/.",
"help.api.body2": "Reference Guide MiR rev 1.9: docs/Reference guide.pdf",
},
en: {
"app.title": "LiDAR Manager",
"app.robotName": "RobotApp",
"app.status.ready": "Ready",
"app.status.reloaded": "Reloaded",
"app.status.backendError": "Cannot connect to backend",
"app.status.jsError": "JavaScript error",
"common.cancel": "Cancel",
"common.close": "Close",
"common.save": "Save",
"common.add": "Add",
"common.delete": "Delete",
"common.apply": "Apply",
"common.reload": "Reload",
"common.select": "Select",
"common.edit": "Edit",
"common.enabled": "On",
"common.disabled": "Off",
"common.configure": "Configure",
"common.error": "Error: {msg}",
"common.none": "none",
"common.optional": "Optional",
"login.prompt": "Choose sign-in method:",
"login.tab.password": "Username and password",
"login.tab.pin": "PIN code",
"login.password.title": "Sign in with username and password",
"login.password.help1": "Enter your username and password to access the robot.",
"login.password.help2": "Accounts are provided by an administrator or in the robot manual.",
"login.password.help3": "If you do not have an account, contact the robot administrator.",
"login.field.username": "Username:",
"login.field.password": "Password:",
"login.placeholder.username": "Admin",
"login.submit": "Sign in",
"login.submitting": "Signing in…",
"login.pin.title": "Sign in with PIN",
"login.pin.help1": "Users with PIN enabled can sign in here.",
"login.pin.help2": "If you do not have a 4-digit PIN, contact the robot administrator.",
"login.pin.helpNote": "No PIN is preconfigured — an administrator must assign one first.",
"login.pin.aria.group": "4-digit PIN",
"login.pin.aria.keypad": "Numeric keypad",
"login.pin.aria.backspace": "Delete",
"login.error.invalidPin": "Invalid PIN. Contact the administrator.",
"login.error.invalidPinShort": "Invalid PIN",
"login.error.missingCredentials": "Enter username and password",
"login.error.badCredentials": "Invalid username or password. Try Admin / admin",
"login.error.serverUnreachable": "Cannot reach server. Check http://localhost:8080",
"login.error.failed": "Sign-in failed",
"nav.aria.main": "Main navigation",
"nav.aria.submenu": "Submenu",
"nav.collapse": "Collapse menu",
"nav.expand": "Expand menu",
"nav.dashboards": "Dashboards",
"nav.setup": "Setup",
"nav.monitoring": "Monitoring",
"nav.system": "System",
"nav.help": "Help",
"nav.logout": "Log out",
"nav.dashboard": "Dashboard",
"nav.missions": "Missions",
"nav.maps": "Maps & layout",
"nav.monitoring-log": "System log",
"nav.integrations": "Integrations",
"nav.help-api": "API documentation",
"topbar.robotTitle": "Robot",
"topbar.controlAria": "Start / Pause robot",
"topbar.allOk": "ALL OK",
"topbar.error": "ERROR",
"topbar.paused": "PAUSED",
"topbar.running": "RUNNING",
"topbar.waiting": "Waiting for new missions…",
"topbar.noMissionsQueue": "No missions in queue…",
"topbar.reset": "RESET",
"topbar.changeUserData": "Change user data",
"topbar.changePassword": "Change password",
"topbar.logout": "Log out",
"topbar.displayName": "Display name",
"topbar.joystickTitle": "Manual control (Joystick)",
"topbar.joystickSpeed": "Speed",
"topbar.joystickOff": "Disengage joystick",
"topbar.joystickAria": "Joystick",
"topbar.batteryTitle": "Battery",
"topbar.localeVi": "TIẾNG VIỆT",
"topbar.localeEn": "ENGLISH",
"topbar.localeOption.vi": "🇻🇳 Tiếng Việt",
"topbar.localeOption.en": "🇺🇸 English",
"topbar.userDefault": "USER",
"topbar.noControlPermission": "No control permission",
"topbar.queueCount": "{n} mission(s) in queue",
"topbar.code": "Code",
"topbar.module": "Module",
"topbar.joystickSpeed.slow": "Slow",
"topbar.joystickSpeed.medium": "Medium",
"topbar.joystickSpeed.fast": "Fast",
"topbar.startHint": "Click to START the robot",
"topbar.pauseHint": "Click to PAUSE the robot",
"auth.profile.displayNameRequired": "Display name cannot be empty",
"auth.profile.saveFailed": "Failed to save profile",
"auth.changePassword.title": "Change password",
"auth.changePassword.current": "Current password",
"auth.changePassword.new": "New password",
"auth.changePassword.confirm": "Confirm new password",
"auth.changePassword.mismatch": "New passwords do not match",
"auth.changePassword.failed": "Failed to change password",
"dashboard.title": "Dashboard",
"dashboard.subtitle": "Mission widgets — run, queue and pause like MiR Fleet.",
"dashboard.addWidget": "Add widget",
"dashboard.editLayout": "Edit layout",
"dashboard.editDone": "Done",
"dashboard.empty": "No widgets yet. Click «Add widget» to start.",
"dashboard.system.title": "System",
"dashboard.system.subtitle": "Backend status and active layout.",
"dashboard.system.backend": "Backend",
"dashboard.system.layout": "Layout",
"dashboard.system.model": "Robot model",
"dashboard.system.sensors": "LiDAR / IMU",
"dashboard.system.sensorCount": "{lidars} LiDAR • {imus} IMU",
"dashboard.dialog.add.title": "Add widget",
"dashboard.dialog.add.type": "Widget type",
"dashboard.dialog.edit.title": "Configure widget",
"dashboard.dialog.edit.type": "Type",
"dashboard.dialog.edit.delete": "Delete widget",
"dashboard.widget.mission_button": "Mission button",
"dashboard.widget.mission_group": "Mission group",
"dashboard.widget.mission_queue": "Mission queue",
"dashboard.widget.pause_continue": "Pause / Continue",
"dashboard.widget.field.mission": "Mission",
"dashboard.widget.field.group": "Mission group",
"dashboard.widget.field.title": "Widget title (optional)",
"dashboard.widget.titlePlaceholder": "e.g. Go to charging",
"dashboard.widget.pauseHint": "Pause, continue or cancel the running mission on the robot.",
"dashboard.widget.selectMission": "Select mission…",
"dashboard.widget.configHint": "Configure the widget and select a mission.",
"dashboard.widget.emptyGroup": "No missions in group «{group}».",
"dashboard.widget.queueEmpty": "Queue empty",
"dashboard.widget.clearQueue": "Clear pending queue",
"dashboard.widget.continue": "Continue",
"dashboard.widget.pause": "Pause",
"dashboard.widget.cancelMission": "Cancel mission",
"dashboard.widget.runner.paused": "Mission paused",
"dashboard.widget.runner.running": "Mission running",
"dashboard.widget.runner.idle": "No mission running",
"dashboard.widget.unsupported": "Unsupported widget.",
"dashboard.widget.deleteConfirm": "Delete this widget?",
"config.layout.title": "Layout manager",
"config.layout.subtitle": "Multiple robot configurations — each layout has its own LiDAR and model.",
"config.layout.save": "Save layout",
"config.layout.current": "Current layout",
"config.layout.newName": "New layout name",
"config.layout.newNamePlaceholder": "e.g. Warehouse AGV A",
"config.layout.cloneCurrent": "Clone from open layout",
"config.layout.create": "Create layout",
"config.layout.editingHint": "Editing: {name}{dirty}",
"config.layout.unsavedDirty": " • unsaved",
"config.layout.unsavedSwitchConfirm": "Current layout has unsaved changes. Continue?",
"config.layout.deleteConfirm": "Delete layout «{name}»? This cannot be undone.",
"config.lidar.title": "LiDARs",
"config.lidar.subtitle": "Register name, IP, port and adjust pose in robot frame.",
"config.lidar.field.name": "Name",
"config.lidar.field.ip": "IP",
"config.lidar.field.port": "Port",
"config.lidar.placeholder.name": "Front lidar",
"config.lidar.placeholder.ip": "192.168.0.10",
"config.lidar.empty": "No LiDAR yet",
"config.lidar.emptyHint": "Add a LiDAR using the form above.",
"config.lidar.deleteConfirm": "Delete this LiDAR?",
"config.imu.title": "IMU",
"config.imu.subtitle": "Inertial sensor — frame, topic and pose on robot.",
"config.imu.field.name": "Name",
"config.imu.field.frame": "Frame ID",
"config.imu.field.topic": "Topic",
"config.imu.field.source": "Source",
"config.imu.source.external": "External (ROS topic)",
"config.imu.source.lidarBuiltin": "LiDAR integrated",
"config.imu.source.onboard": "Onboard robot",
"config.imu.field.rate": "Rate (Hz)",
"config.imu.enabled": "Enable IMU",
"config.imu.add": "Add IMU",
"config.imu.placeholder.name": "Main IMU",
"config.imu.placeholder.frame": "imu_link",
"config.imu.placeholder.topic": "imu/data",
"config.imu.empty": "No IMU yet",
"config.imu.emptyHint": "Add an IMU using the form above.",
"config.imu.deleteConfirm": "Delete this IMU?",
"config.robot.title": "Robot model",
"config.robot.subtitle": "Differential kinematics — wheels, motors and velocity limits.",
"config.robot.model.diff": "Differential (2 wheels)",
"config.robot.model.bicycle": "Bicycle",
"config.canvas.title": "Layout on robot",
"config.canvas.viewHint": "Mouse wheel: zoom • Shift + drag: pan view",
"config.canvas.robotCenter": "Robot center:",
"config.canvas.selected": "Selected:",
"config.canvas.pose": "Pose:",
"config.pose.notSet": "pose not set",
"config.selected.lidar": "LiDAR: {name}",
"config.selected.imu": "IMU: {name}",
"config.motor.wheelRight": "Right wheel",
"config.motor.wheelLeft": "Left wheel",
"config.motor.wheelSteer": "Front wheel (steer)",
"config.motor.wheelDrive": "Rear wheel (drive)",
"config.motor.vendor": "Vendor",
"config.motor.model": "Model",
"config.motor.joint": "Joint (ROS)",
"config.motor.ratio": "Gear ratio",
"config.motor.invert": "Invert rotation",
"config.motor.invertSteer": "Invert",
"config.motor.custom": "Custom",
"config.motor.customMotor": "Custom motor",
"missions.title": "Missions",
"missions.subtitle": "Setup → Missions — robot task list.",
"missions.create": "Create mission",
"missions.empty": "No missions yet. Click Create mission to start.",
"missions.queue.title": "Mission queue",
"missions.queue.subtitle": "Add missions via the queue icon — robot runs top to bottom.",
"missions.queue.cancel": "Cancel run",
"missions.queue.clear": "Clear queue",
"missions.queue.empty": "Queue empty. Click ▤ on a mission to add.",
"missions.editor.kicker": "Mission editor",
"missions.editor.unsaved": "Unsaved",
"missions.editor.saveAs": "Save as",
"missions.editor.save": "Save",
"missions.editor.flowHint": "Execute top to bottom. Drag ↔ to reorder. For Loop: drag actions inside.",
"missions.editor.emptyActions": "Pick an action from the menu above to start.",
"missions.editor.backAria": "Back to list",
"missions.editor.settingsAria": "Mission settings",
"missions.editor.addActionAria": "Add action",
"missions.queue.status.pending": "Pending",
"missions.queue.status.running": "Running",
"missions.queue.status.done": "Done",
"missions.queue.status.error": "Error",
"missions.queue.status.cancelled": "Cancelled",
"missions.queue.ready": "Ready",
"missions.queue.idleMessage": "Robot ready — queue empty or waiting for new mission.",
"missions.queue.moveUp": "Up",
"missions.queue.moveDown": "Down",
"missions.queue.addAria": "Add to mission queue",
"missions.deleteConfirm": "Delete mission «{name}»?",
"missions.queue.clearConfirm": "Clear pending missions in queue?",
"missions.queue.cancelConfirm": "Cancel running mission? (exits loop if looping)",
"missions.dialog.create.title": "Create mission",
"missions.dialog.create.name": "Mission name",
"missions.dialog.create.group": "Mission group",
"missions.dialog.create.groupNew": "Or new group",
"missions.dialog.create.desc": "Description",
"missions.dialog.create.namePlaceholder": "e.g. Go to charging station",
"missions.dialog.settings.title": "Mission settings",
"missions.dialog.settings.name": "Name",
"missions.dialog.settings.group": "Group",
"missions.dialog.settings.desc": "Description",
"missions.dialog.saveAs.title": "Save mission as",
"missions.dialog.saveAs.name": "New mission name",
"missions.dialog.saveAs.submit": "Save copy",
"missions.dialog.actionConfig.title": "Configure action",
"missions.dialog.queue.title": "Add to mission queue",
"missions.group.Move": "Move",
"missions.group.Logic": "Logic",
"missions.group.IO": "I/O",
"missions.group.Cart": "Cart",
"missions.group.Misc": "Misc",
"missions.action.move_to_position": "Go to position",
"missions.action.move_to_marker": "Go to marker",
"missions.action.adjust_localization": "Adjust localization",
"missions.action.wait": "Wait",
"missions.action.set_speed": "Set speed",
"missions.action.if": "If",
"missions.action.loop": "Loop",
"missions.action.break": "Break",
"missions.action.continue": "Continue",
"missions.action.pause": "Pause",
"missions.action.set_digital_output": "Set digital output",
"missions.action.wait_digital_input": "Wait for digital input",
"missions.action.set_plc_register": "Set PLC register",
"missions.action.pick_cart": "Pick cart",
"missions.action.drop_cart": "Drop cart",
"missions.action.user_log": "User log",
"missions.action.play_sound": "Play sound",
"missions.error.nameRequired": "Mission name cannot be empty.",
"missions.error.nameDuplicate": "Mission name already exists.",
"missions.error.nameEmpty": "Name cannot be empty.",
"missions.saveSuccess": "Mission saved.",
"missions.editor.discardConfirm": "Discard unsaved changes?",
"missions.queue.status.executing": "Running",
"missions.action.waitOnLevel": "Wait for ON level",
"integrations.modbus.title": "Modbus trigger",
"integrations.modbus.subtitle": "System → Triggers — coils 10012000 map to mission_id. Remote device sets coil (Modbus TCP :5502) → mission queued.",
"integrations.modbus.add": "Add trigger",
"integrations.modbus.empty": "No Modbus triggers yet.",
"integrations.modbus.coilsLabel": "Assigned coils (click to simulate rising edge)",
"integrations.rest.title": "REST API — MiR v2.0.0",
"integrations.rest.subtitle": "External systems POST missions to the queue via REST.",
"integrations.rest.baseUrl": "Base URL",
"integrations.rest.quickTest": "Quick test",
"integrations.rest.postQueue": "POST queue",
"integrations.fleet.title": "MiRFleet — Schedule missions",
"integrations.fleet.subtitle": "Priority, robot assignment, run ASAP or scheduled.",
"integrations.fleet.addSchedule": "Add schedule",
"integrations.fleet.empty": "No fleet schedules yet.",
"integrations.noMissions": "— No missions —",
"integrations.defaultRobot": "Main robot",
"integrations.fireTrigger": "Fire",
"integrations.coilsEmpty": "No coils assigned. Add a trigger above (10012000).",
"integrations.coilState": "coil state: {state}",
"integrations.confirm.deleteTrigger": "Delete this Modbus trigger?",
"integrations.confirm.deleteSchedule": "Delete this fleet schedule?",
"integrations.dialog.trigger.title": "Modbus trigger",
"integrations.dialog.trigger.name": "Trigger name",
"integrations.dialog.trigger.coil": "Coil ID",
"integrations.dialog.trigger.mission": "Mission",
"integrations.dialog.schedule.title": "MiRFleet schedule",
"integrations.dialog.schedule.name": "Schedule name",
"integrations.dialog.schedule.robot": "Robot",
"integrations.dialog.schedule.priority": "Priority",
"integrations.dialog.schedule.mode": "Mode",
"integrations.dialog.schedule.asap": "ASAP",
"integrations.dialog.schedule.scheduled": "Scheduled",
"integrations.dialog.schedule.startTime": "Start time",
"integrations.schedule.runNow": "Run now",
"monitoring.log.title": "System log",
"monitoring.log.subtitle": "Monitoring → System log — system log (coming soon).",
"monitoring.log.placeholder": "Monitoring will show robot logs, alerts and mission history here.",
"help.api.title": "API documentation",
"help.api.subtitle": "Help → API — MiR v2.0.0 REST reference for external integration.",
"help.api.body1": "See endpoint details under System → Integrations or /api/v2.0.0/ docs.",
"help.api.body2": "Reference Guide MiR rev 1.9: docs/Reference guide.pdf",
},
};
const LOCALE_META = {
vi: { flag: "🇻🇳", labelKey: "topbar.localeVi" },
en: { flag: "🇺🇸", labelKey: "topbar.localeEn" },
};
let locale = "vi";
function interpolate(str, vars) {
if (!vars) return str;
return String(str).replace(/\{(\w+)\}/g, (_, k) => (vars[k] != null ? String(vars[k]) : `{${k}}`));
}
function t(key, vars) {
const raw = MESSAGES[locale]?.[key] ?? MESSAGES.en[key] ?? key;
return interpolate(raw, vars);
}
function applyDOM() {
document.querySelectorAll("[data-i18n]").forEach((node) => {
const key = node.dataset.i18n;
if (key) node.textContent = t(key);
});
document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => {
const key = node.dataset.i18nPlaceholder;
if (key) node.placeholder = t(key);
});
document.querySelectorAll("[data-i18n-title]").forEach((node) => {
const key = node.dataset.i18nTitle;
if (key) node.title = t(key);
});
document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
const key = node.dataset.i18nAria;
if (key) node.setAttribute("aria-label", t(key));
});
document.querySelectorAll("option[data-i18n]").forEach((node) => {
const key = node.dataset.i18n;
if (key) node.textContent = t(key);
});
const titleKey = document.documentElement.dataset.i18nTitle;
if (titleKey) document.title = t(titleKey);
}
function syncTopbarLocaleUI() {
const meta = LOCALE_META[locale];
if (!meta) return;
const flagEl = document.getElementById("mirLocaleFlag");
const labelEl = document.getElementById("mirLocaleLabel");
if (flagEl) flagEl.textContent = meta.flag;
if (labelEl) labelEl.textContent = t(meta.labelKey);
}
function setLocale(next, opts = {}) {
locale = MESSAGES[next] ? next : "vi";
try {
localStorage.setItem("lm_locale", locale);
} catch {
/* ignore */
}
document.documentElement.lang = locale;
applyDOM();
syncTopbarLocaleUI();
if (!opts.silent) {
window.dispatchEvent(new CustomEvent("lm:locale-change", { detail: { locale } }));
}
}
function loadLocale() {
try {
const saved = localStorage.getItem("lm_locale");
if (saved && MESSAGES[saved]) locale = saved;
} catch {
/* ignore */
}
setLocale(locale, { silent: true });
window.dispatchEvent(new CustomEvent("lm:locale-change", { detail: { locale } }));
}
window.I18n = {
t,
getLocale: () => locale,
setLocale,
applyDOM,
loadLocale,
LOCALE_META,
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadLocale);
} else {
loadLocale();
}
})();