1140 lines
58 KiB
JavaScript
1140 lines
58 KiB
JavaScript
/**
|
||
* 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.originLabelShort": "Gốc ({x}, {y})",
|
||
"maps.editor.originTooltip": "Origin map: X={x} m, Y={y} m, yaw={yaw}°",
|
||
"maps.uploadConfirm.title": "Ghi đè floor plan?",
|
||
"maps.uploadConfirm.text": "Ảnh map hiện tại sẽ bị thay thế. Tiếp tục?",
|
||
"maps.uploadConfirm.yes": "Ghi đè",
|
||
"maps.uploadMeta.title": "Metadata map (ROS)",
|
||
"maps.uploadMeta.hint": "Nhập origin, resolution và ngưỡng occupancy — hoặc import file .yaml.",
|
||
"maps.uploadMeta.importYaml": "Import file YAML…",
|
||
"maps.uploadMeta.negate": "Negate",
|
||
"maps.uploadMeta.occupiedThresh": "Occupied thresh",
|
||
"maps.uploadMeta.freeThresh": "Free thresh",
|
||
"maps.uploadMeta.continue": "Tiếp tục — chọn PNG",
|
||
"maps.uploadMeta.invalidResolution": "Resolution phải lớn hơn 0.",
|
||
"maps.uploadMeta.invalidYaml": "Không đọc được file YAML.",
|
||
"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": "Ba lớp: View (pan/zoom màn hình) → Image (pixel floor plan, 20 px/m) → World (X,Y mét). Dùng Pan, zoom, Fit; di chuột để xem tọa độ.",
|
||
"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ó floor plan — menu ⋮ để upload PNG.",
|
||
"maps.editor.statusView": "zoom {zoom}% · pan ({panX}, {panY})",
|
||
"maps.editor.statusImageIdle": "— px (di chuột trên map)",
|
||
"maps.editor.statusImage": "({px}, {py}) px",
|
||
"maps.editor.statusWorldIdle": "— m",
|
||
"maps.editor.statusWorld": "X {x}, Y {y} m",
|
||
"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 1001–2000 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 (1001–2000).",
|
||
"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.originLabelShort": "Origin ({x}, {y})",
|
||
"maps.editor.originTooltip": "Map origin: X={x} m, Y={y} m, yaw={yaw}°",
|
||
"maps.uploadConfirm.title": "Overwrite floor plan?",
|
||
"maps.uploadConfirm.text": "The current map image will be replaced. Continue?",
|
||
"maps.uploadConfirm.yes": "Overwrite",
|
||
"maps.uploadMeta.title": "Map metadata (ROS)",
|
||
"maps.uploadMeta.hint": "Enter origin, resolution, and occupancy thresholds — or import a .yaml file.",
|
||
"maps.uploadMeta.importYaml": "Import YAML file…",
|
||
"maps.uploadMeta.negate": "Negate",
|
||
"maps.uploadMeta.occupiedThresh": "Occupied thresh",
|
||
"maps.uploadMeta.freeThresh": "Free thresh",
|
||
"maps.uploadMeta.continue": "Continue — choose PNG",
|
||
"maps.uploadMeta.invalidResolution": "Resolution must be greater than 0.",
|
||
"maps.uploadMeta.invalidYaml": "Could not read YAML file.",
|
||
"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": "Three layers: View (screen pan/zoom) → Image (floor plan pixels, 20 px/m) → World (X,Y in metres). Use Pan, zoom, Fit; hover for coordinates.",
|
||
"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 floor plan — ⋮ menu to upload PNG.",
|
||
"maps.editor.statusView": "zoom {zoom}% · pan ({panX}, {panY})",
|
||
"maps.editor.statusImageIdle": "— px (hover map)",
|
||
"maps.editor.statusImage": "({px}, {py}) px",
|
||
"maps.editor.statusWorldIdle": "— m",
|
||
"maps.editor.statusWorld": "X {x}, Y {y} m",
|
||
"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 1001–2000 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 (1001–2000).",
|
||
"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();
|
||
}
|
||
})();
|