Files
App/www/i18n.js
HiepLM a6cf06d7eb
Some checks failed
Test / test (push) Has been cancelled
Add phần create map by upload
2026-06-19 11:52:21 +07:00

1102 lines
56 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 Robot App — 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": "Robot App",
"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",
"common.yes": "Có",
"common.no": "Không",
"common.ok": "OK",
"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.dashboardsList": "Dashboards",
"nav.missions": "Missions",
"nav.maps": "Maps",
"nav.build-robot": "Build Robot",
"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.list.title": "Dashboards",
"dashboard.list.subtitle": "Tạo và chỉnh sửa dashboard cho robot.",
"dashboard.list.create": "+ Tạo dashboard",
"dashboard.list.clearFilters": "Xóa bộ lọc",
"dashboard.list.filterLabel": "Lọc:",
"dashboard.list.filterPlaceholder": "Nhập tên để lọc…",
"dashboard.list.itemsFound": "{count} mục",
"dashboard.list.pageOf": "Trang {page} / {total}",
"dashboard.list.col.name": "Tên",
"dashboard.list.col.createdBy": "Tạo bởi",
"dashboard.list.col.functions": "Chức năng",
"dashboard.list.empty": "Không có dashboard nào.",
"dashboard.list.back": "← Danh sách",
"dashboard.list.design": "Thiết kế",
"dashboard.list.active": "Dashboard đang active",
"dashboard.list.edit": "Sửa",
"dashboard.list.delete": "Xóa",
"dashboard.list.deleteConfirm": "Xóa dashboard «{name}»?",
"dashboard.list.cannotDeleteDefault": "Không thể xóa Default Dashboard.",
"dashboard.list.noEditPermission": "Bạn không có quyền chỉnh sửa dashboard này.",
"dashboard.dialog.create.title": "Tạo dashboard",
"dashboard.create.title": "Tạo dashboard",
"dashboard.create.subtitle": "Tạo dashboard mới trên robot.",
"dashboard.create.backToList": "← Quay lại danh sách",
"dashboard.create.name": "Tên",
"dashboard.create.namePlaceholder": "VD: John's Dashboard",
"dashboard.create.permissions": "Chọn user groups được phép chỉnh sửa dashboard này.",
"dashboard.create.permissionsBtn": "Quyền",
"dashboard.create.permissionsTitle": "Quyền chỉnh sửa",
"dashboard.create.submit": "Tạo dashboard",
"dashboard.create.cancel": "Hủy",
"dashboard.dialog.editDashboard.title": "Sửa dashboard",
"dashboard.designer.empty": "Chưa có widget trên dashboard này.",
"dashboard.designer.emptyEdit": "Chưa có widget. Chọn loại widget trên thanh Maps / Missions / Miscellaneous.",
"dashboard.designer.dragHint": "Kéo thanh tiêu đề để di chuyển widget trên lưới",
"dashboard.designer.configure": "Cấu hình widget",
"dashboard.designer.resize": "Kéo để đổi kích thước",
"dashboard.designer.save": "Lưu",
"dashboard.designer.saved": "Đã lưu",
"dashboard.menu.maps": "Maps",
"dashboard.menu.missions": "Missions",
"dashboard.menu.plc": "PLC Registers",
"dashboard.menu.io": "I/O",
"dashboard.menu.misc": "Miscellaneous",
"dashboard.menu.comingSoon": "Widget nhóm này sẽ có trong bản cập nhật sau.",
"dashboard.createdBy.system": "MiR",
"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.mission_action_log": "Mission action log",
"dashboard.widget.logout_button": "Nút đăng xuất",
"dashboard.widget.map_locked": "Locked map",
"dashboard.widget.map": "Map",
"dashboard.widget.robot_summary": "Robot summary",
"dashboard.widget.field.map": "Map",
"dashboard.widget.mapActive": "Active map (robot)",
"dashboard.widget.mapHint": "Chọn map cố định hoặc để «Active map» dùng map đang gắn với robot.",
"dashboard.widget.mapLoading": "Đang tải map…",
"dashboard.widget.mapEmpty": "Chưa có map. Distributor tạo map qua API /api/maps.",
"dashboard.widget.mapNoImage": "Chưa có ảnh map — upload qua POST /api/maps/{id}/image",
"dashboard.widget.mapImageError": "Không tải được ảnh map.",
"dashboard.widget.actionLog.empty": "Chưa có action đang chạy.",
"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",
"maps.title": "Maps",
"maps.subtitle": "Tạo và chỉnh sửa map.",
"maps.create": "Tạo map",
"maps.importSite": "Import site",
"maps.clearFilters": "Xóa bộ lọc",
"maps.filterLabel": "Lọc:",
"maps.filterPlaceholder": "Nhập tên để lọc…",
"maps.itemsFound": "{n} mục",
"maps.pageOf": "Trang {page} / {total}",
"maps.colName": "Tên",
"maps.colCreatedBy": "Tạo bởi",
"maps.colFunctions": "Chức năng",
"maps.empty": "Chưa có map. Bấm Tạo map để bắt đầu.",
"maps.emptyFilter": "Không có map khớp bộ lọc.",
"maps.activeBadge": "ACTIVE",
"maps.activeHint": "Map đang hoạt động: {name}",
"maps.view": "Xem",
"maps.importComingSoon": "Import site sẽ có trong phiên bản sau.",
"maps.helpTitle": "Trợ giúp Maps",
"maps.helpText": "Tạo map mới, upload ảnh PNG qua menu ⋮, sau đó kích hoạt map cho robot.",
"maps.createDialog.title": "Tạo map",
"maps.createDialog.name": "Tên *",
"maps.createDialog.site": "Site *",
"maps.createDialog.manageSite": "Tạo / Sửa site…",
"maps.createDialog.submit": "Tạo map",
"maps.createPage.title": "Tạo map",
"maps.createPage.subtitle": "Tạo map mới.",
"maps.createPage.goBack": "Quay lại",
"maps.createPage.name": "Tên",
"maps.createPage.namePlaceholder": "Nhập tên map…",
"maps.createPage.nameHelp": "Tên hiển thị trong danh sách Maps.",
"maps.createPage.site": "Site",
"maps.createPage.siteHelp": "Site chứa map trong cơ sở.",
"maps.createPage.siteManage": "Tạo / Sửa",
"maps.createPage.submit": "Tạo map",
"maps.createPage.cancel": "Hủy",
"maps.createPage.helpText": "Nhập tên map và chọn site, sau đó bấm Tạo map để mở trình editor.",
"maps.siteDialog.create": "Tạo site",
"maps.siteDialog.edit": "Sửa site",
"maps.siteDialog.name": "Tên *",
"maps.siteForm.create": "Tạo site",
"maps.siteForm.edit": "Sửa site",
"maps.siteForm.name": "Tên *",
"maps.sitesDialog.title": "Sites",
"maps.sitesDialog.createSite": "Tạo site",
"maps.sitesDialog.description": "Site là container chứa maps và dữ liệu cơ sở trên robot.",
"maps.sitesDialog.empty": "Chưa có site.",
"maps.sitesDialog.deleteConfirm": "Xóa site \"{name}\"?",
"maps.deleteConfirm": "Xóa map \"{name}\"?",
"maps.error.nameEmpty": "Tên map không được để trống.",
"maps.error.noImage": "Map chưa có ảnh — upload PNG trước khi kích hoạt.",
"maps.error.pngOnly": "Chỉ chấp nhận file PNG.",
"maps.activateDialog.title": "Kích hoạt map?",
"maps.activateDialog.text": "Đặt \"{name}\" làm map hoạt động của robot?",
"maps.menu.title": "Upload, download and record maps",
"maps.menu.uploadOverwrite": "Upload and overwrite",
"maps.menu.uploadOverwriteDesc": "Thay map hiện tại bằng map upload.",
"maps.menu.uploadAppend": "Upload and append",
"maps.menu.uploadAppendDesc": "Upload map mới và ghép vào map hiện tại.",
"maps.menu.download": "Download map",
"maps.menu.downloadDesc": "Tải map hiện tại.",
"maps.menu.recordOverwrite": "Record and overwrite",
"maps.menu.recordOverwriteDesc": "Thay map hiện tại bằng bản ghi map mới.",
"maps.menu.recordAppend": "Record and append",
"maps.menu.recordAppendDesc": "Ghi map mới và ghép vào map hiện tại.",
"maps.menu.comingSoon": "Sắp có",
"maps.menu.recordHint": "Cần LiDAR",
"maps.settings.title": "Cài đặt map",
"maps.settings.name": "Tên",
"maps.settings.description": "Mô tả",
"maps.settings.resolution": "Resolution (m/px)",
"maps.settings.originX": "Origin X",
"maps.settings.originY": "Origin Y",
"maps.settings.originYaw": "Origin yaw",
"maps.editor.back": "Maps",
"maps.editor.goBack": "Quay lại",
"maps.editor.subtitle": "Chỉnh sửa và vẽ map.",
"maps.editor.helpTitle": "Trợ giúp map editor",
"maps.editor.helpText": "Dùng công cụ Pan để kéo map, zoom bằng nút +/- hoặc con lăn chuột. Menu ⋮ để upload/lưu map.",
"maps.editor.toolbarAria": "Mapping tools",
"maps.editor.canvasTip": "Kéo map để di chuyển vùng nhìn hoặc dùng nút zoom in/out để phóng to/thu nhỏ.",
"maps.editor.unsaved": "Chưa lưu",
"maps.editor.unsavedLeave": "Có thay đổi chưa lưu. Rời editor?",
"maps.editor.menu": "Menu",
"maps.editor.undo": "Hoàn tác",
"maps.editor.save": "Lưu",
"maps.editor.settings": "Cài đặt",
"maps.editor.tool.search": "Tìm kiếm",
"maps.editor.tool.save": "Lưu map",
"maps.editor.tool.pan": "Pan — di chuyển vùng nhìn",
"maps.editor.tool.crosshair": "Crosshair",
"maps.editor.tool.center": "Căn giữa vùng nhìn",
"maps.editor.tool.lidar": "Hiển thị LiDAR",
"maps.editor.tool.waypoints": "Vị trí / waypoint",
"maps.editor.fit": "Vừa khung",
"maps.editor.zoomIn": "Phóng to",
"maps.editor.zoomOut": "Thu nhỏ",
"maps.editor.noData": "Chưa có dữ liệu map — mở menu ⋮ để upload PNG.",
"maps.editor.objectTypesNone": "Chưa chọn object-type",
"maps.menu.save": "Lưu map",
"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": "Robot App",
"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",
"common.yes": "Yes",
"common.no": "No",
"common.ok": "OK",
"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.dashboardsList": "Dashboards",
"nav.missions": "Missions",
"nav.maps": "Maps",
"nav.build-robot": "Build Robot",
"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.list.title": "Dashboards",
"dashboard.list.subtitle": "Create and edit dashboards for the robot.",
"dashboard.list.create": "+ Create dashboard",
"dashboard.list.clearFilters": "Clear filters",
"dashboard.list.filterLabel": "Filter:",
"dashboard.list.filterPlaceholder": "Write name to filter by…",
"dashboard.list.itemsFound": "{count} item(s) found",
"dashboard.list.pageOf": "Page {page} of {total}",
"dashboard.list.col.name": "Name",
"dashboard.list.col.createdBy": "Created by",
"dashboard.list.col.functions": "Functions",
"dashboard.list.empty": "No dashboards found.",
"dashboard.list.back": "← Back to list",
"dashboard.list.design": "Design",
"dashboard.list.active": "Active dashboard",
"dashboard.list.edit": "Edit",
"dashboard.list.delete": "Delete",
"dashboard.list.deleteConfirm": "Delete dashboard «{name}»?",
"dashboard.list.cannotDeleteDefault": "Cannot delete Default Dashboard.",
"dashboard.list.noEditPermission": "You do not have permission to edit this dashboard.",
"dashboard.dialog.create.title": "Create dashboard",
"dashboard.create.title": "Create dashboard",
"dashboard.create.subtitle": "Create a new dashboard in the robot.",
"dashboard.create.backToList": "← Back to the list",
"dashboard.create.name": "Name",
"dashboard.create.namePlaceholder": "John's Dashboard",
"dashboard.create.permissions": "Select user groups allowed to edit this dashboard.",
"dashboard.create.permissionsBtn": "Permissions",
"dashboard.create.permissionsTitle": "Permissions",
"dashboard.create.submit": "Create dashboard",
"dashboard.create.cancel": "Cancel",
"dashboard.dialog.editDashboard.title": "Edit dashboard",
"dashboard.designer.empty": "This dashboard has no widgets yet.",
"dashboard.designer.emptyEdit": "No widgets yet. Pick a type from the Maps / Missions / Miscellaneous toolbar.",
"dashboard.designer.dragHint": "Drag the header bar to move the widget on the grid",
"dashboard.designer.configure": "Configure widget",
"dashboard.designer.resize": "Drag to resize",
"dashboard.designer.save": "Save",
"dashboard.designer.saved": "Saved",
"dashboard.menu.maps": "Maps",
"dashboard.menu.missions": "Missions",
"dashboard.menu.plc": "PLC Registers",
"dashboard.menu.io": "I/O",
"dashboard.menu.misc": "Miscellaneous",
"dashboard.menu.comingSoon": "Widgets in this category are coming in a future release.",
"dashboard.createdBy.system": "MiR",
"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.mission_action_log": "Mission action log",
"dashboard.widget.logout_button": "Log-out button",
"dashboard.widget.map_locked": "Locked map",
"dashboard.widget.map": "Map",
"dashboard.widget.robot_summary": "Robot summary",
"dashboard.widget.field.map": "Map",
"dashboard.widget.mapActive": "Active map (robot)",
"dashboard.widget.mapHint": "Pick a fixed map or leave «Active map» to follow the robot's current map.",
"dashboard.widget.mapLoading": "Loading map…",
"dashboard.widget.mapEmpty": "No maps yet. A Distributor can create maps via /api/maps.",
"dashboard.widget.mapNoImage": "No map image yet — upload via POST /api/maps/{id}/image",
"dashboard.widget.mapImageError": "Could not load the map image.",
"dashboard.widget.actionLog.empty": "No running action to show.",
"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",
"maps.title": "Maps",
"maps.subtitle": "Create and edit maps.",
"maps.create": "Create map",
"maps.importSite": "Import site",
"maps.clearFilters": "Clear filters",
"maps.filterLabel": "Filter:",
"maps.filterPlaceholder": "Write name to filter by...",
"maps.itemsFound": "{n} item(s) found",
"maps.pageOf": "Page {page} of {total}",
"maps.colName": "Name",
"maps.colCreatedBy": "Created by",
"maps.colFunctions": "Functions",
"maps.empty": "No maps yet. Click Create map to get started.",
"maps.emptyFilter": "No maps match the filter.",
"maps.activeBadge": "ACTIVE",
"maps.activeHint": "Active map: {name}",
"maps.view": "View",
"maps.importComingSoon": "Import site will be available in a future release.",
"maps.helpTitle": "Maps help",
"maps.helpText": "Create a new map, upload a PNG via the ⋮ menu, then activate the map for the robot.",
"maps.createDialog.title": "Create map",
"maps.createDialog.name": "Name *",
"maps.createDialog.site": "Site *",
"maps.createDialog.manageSite": "Create / Edit site…",
"maps.createDialog.submit": "Create map",
"maps.createPage.title": "Create map",
"maps.createPage.subtitle": "Create a new map.",
"maps.createPage.goBack": "Go back",
"maps.createPage.name": "Name",
"maps.createPage.namePlaceholder": "Enter the map's name...",
"maps.createPage.nameHelp": "Display name shown in the Maps list.",
"maps.createPage.site": "Site",
"maps.createPage.siteHelp": "The facility site that contains this map.",
"maps.createPage.siteManage": "Create / Edit",
"maps.createPage.submit": "Create map",
"maps.createPage.cancel": "Cancel",
"maps.createPage.helpText": "Enter a map name and select a site, then click Create map to open the editor.",
"maps.siteDialog.create": "Create site",
"maps.siteDialog.edit": "Edit site",
"maps.siteDialog.name": "Name *",
"maps.siteForm.create": "Create site",
"maps.siteForm.edit": "Edit site",
"maps.siteForm.name": "Name *",
"maps.sitesDialog.title": "Sites",
"maps.sitesDialog.createSite": "Create site",
"maps.sitesDialog.description": "A site is a container for maps and other facility data on the robot.",
"maps.sitesDialog.empty": "No sites yet.",
"maps.sitesDialog.deleteConfirm": "Delete site \"{name}\"?",
"maps.deleteConfirm": "Delete map \"{name}\"?",
"maps.error.nameEmpty": "Map name is required.",
"maps.error.noImage": "Map has no image — upload a PNG before activating.",
"maps.error.pngOnly": "Only PNG files are accepted.",
"maps.activateDialog.title": "Activate map?",
"maps.activateDialog.text": "Set \"{name}\" as the robot's active map?",
"maps.menu.title": "Upload, download and record maps",
"maps.menu.uploadOverwrite": "Upload and overwrite",
"maps.menu.uploadOverwriteDesc": "Replace existing map with uploaded map.",
"maps.menu.uploadAppend": "Upload and append",
"maps.menu.uploadAppendDesc": "Upload a new map and append it to current map.",
"maps.menu.download": "Download map",
"maps.menu.downloadDesc": "Download the current map.",
"maps.menu.recordOverwrite": "Record and overwrite",
"maps.menu.recordOverwriteDesc": "Replace existing map with new recording of map.",
"maps.menu.recordAppend": "Record and append",
"maps.menu.recordAppendDesc": "Record a new map and append it to current map.",
"maps.menu.comingSoon": "Coming soon",
"maps.menu.recordHint": "Requires LiDAR",
"maps.settings.title": "Map settings",
"maps.settings.name": "Name",
"maps.settings.description": "Description",
"maps.settings.resolution": "Resolution (m/px)",
"maps.settings.originX": "Origin X",
"maps.settings.originY": "Origin Y",
"maps.settings.originYaw": "Origin yaw",
"maps.editor.back": "Maps",
"maps.editor.goBack": "Go back",
"maps.editor.subtitle": "Edit and draw the map.",
"maps.editor.helpTitle": "Map editor help",
"maps.editor.helpText": "Use the Pan tool to drag the map, zoom with +/- buttons or the mouse wheel. Open ⋮ menu to upload or save the map.",
"maps.editor.toolbarAria": "Mapping tools",
"maps.editor.canvasTip": "Drag the map to move your view or use the zoom-in and -out buttons to zoom.",
"maps.editor.unsaved": "Unsaved",
"maps.editor.unsavedLeave": "You have unsaved changes. Leave the editor?",
"maps.editor.menu": "Menu",
"maps.editor.undo": "Undo",
"maps.editor.save": "Save",
"maps.editor.settings": "Settings",
"maps.editor.tool.search": "Search",
"maps.editor.tool.save": "Save map",
"maps.editor.tool.pan": "Pan — move view",
"maps.editor.tool.crosshair": "Crosshair",
"maps.editor.tool.center": "Center view",
"maps.editor.tool.lidar": "LiDAR overlay",
"maps.editor.tool.waypoints": "Positions",
"maps.editor.fit": "Fit to view",
"maps.editor.zoomIn": "Zoom in",
"maps.editor.zoomOut": "Zoom out",
"maps.editor.noData": "No map data — open ⋮ menu to upload a PNG.",
"maps.editor.objectTypesNone": "No object-type selected",
"maps.menu.save": "Save map",
"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();
}
})();