/**
 * 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.sounds": "Sounds",
      "nav.transitions": "Transitions",
      "nav.user-groups": "User groups",
      "nav.io-modules": "I/O modules",
      "nav.paths": "Paths",
      "nav.path-guides": "Path guides",
      "nav.users": "Users",
      "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.io_connect": "Connect I/O module",
      "dashboard.widget.io_status": "I/O status",
      "dashboard.widget.io_connectAction": "Connect",
      "dashboard.widget.io_disconnect": "Disconnect",
      "dashboard.widget.io_configuration": "I/O configuration",
      "dashboard.widget.ioPort": "Port {port}",
      "dashboard.widget.ioOn": "ON",
      "dashboard.widget.ioOff": "OFF",
      "dashboard.widget.field.ioModule": "I/O module",
      "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.importNoSite": "Chọn site trước khi import.",
      "maps.importSuccess": "Import xong: {maps} map(s), {io} I/O module(s), {transitions} transition(s), {paths} path(s).",
      "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.deleteDialog.title": "Xóa map?",
      "maps.deleteDialog.text": "Xóa map \"{name}\"? Hành động không hoàn tác.",
      "maps.deleteDialog.activeWarning": "Map này đang là map hoạt động trên robot.",
      "maps.deleteForbidden": "Bạn không thể xóa map thuộc nhóm người dùng khác.",
      "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.preferredPathWarning": "Map này có path đã lưu. Thay đổi preferred zone có thể không áp dụng cho đến khi bạn xóa các path liên quan trong Setup → Paths. Tiếp tục lưu?",
      "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.origin": "Hiển thị gốc tọa độ",
      "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.editor.objectType.wall": "Walls",
      "maps.editor.objectType.floor": "Floors",
      "maps.editor.objectType.position": "Positions",
      "maps.editor.objectType.forbidden": "Forbidden zones",
      "maps.editor.objectType.preferred": "Preferred zones",
      "maps.editor.objectType.unpreferred": "Unpreferred zones",
      "maps.editor.objectType.speed": "Speed zones",
      "maps.editor.objectType.sound": "Sound zones",
      "maps.editor.objectType.directional": "Directional zones",
      "maps.editor.objectType.directionalLine": "Directional lines",
      "maps.editor.objectType.planner": "Planner zones",
      "maps.editor.objectType.io": "I/O zones",
      "maps.editor.filter.walls": "Walls",
      "maps.editor.filter.floors": "Floors",
      "maps.editor.filter.positions": "Positions",
      "maps.editor.filter.forbidden": "Forbidden",
      "maps.editor.filter.preferred": "Preferred",
      "maps.editor.filter.unpreferred": "Unpreferred",
      "maps.editor.filter.speed": "Speed",
      "maps.editor.filter.sound": "Sound",
      "maps.editor.tool.drawLine": "Vẽ line — polyline (wall, drive zone line…)",
      "maps.editor.tool.drawShape": "Vẽ shape — polygon (floor, zones…)",
      "maps.editor.drawLine.title": "Vẽ line mới",
      "maps.editor.drawLine.lineWidth": "Độ rộng line (cm)",
      "maps.editor.drawLine.hint": "Chỉnh độ rộng line (cm), xác nhận, rồi click trên map để đặt các điểm.",
      "maps.editor.drawLine.confirm": "Xác nhận",
      "maps.editor.drawLine.lineWidthInvalid": "Nhập độ rộng line hợp lệ (wall ≥ 5 cm, zone line ≥ 10 cm).",
      "maps.editor.tool.draw": "Vẽ — line hoặc polygon (walls, zones…)",
      "maps.editor.tool.select": "Chọn object",
      "maps.editor.tool.erase": "Tẩy object",
      "maps.editor.tool.eraser": "Tẩy pixel — xóa noise trên floor plan (Walls/Floors layer)",
      "maps.editor.tool.eraseShape": "Xóa line/shape đã vẽ",
      "maps.editor.tool.eraseSelection": "Xóa vùng chọn — tẩy pixel trong khung",
      "maps.editor.tool.confirmDraw": "Xác nhận hình vẽ",
      "maps.editor.drawNeedMorePoints": "Cần thêm điểm trước khi xác nhận (line ≥ 2, polygon ≥ 3).",
      "maps.editor.position.title": "Position",
      "maps.editor.position.name": "Tên",
      "maps.editor.position.x": "X (m)",
      "maps.editor.position.y": "Y (m)",
      "maps.editor.position.yaw": "Hướng (°)",
      "maps.editor.position.hint": "Click map và kéo để đặt hướng, rồi xác nhận.",
      "maps.editor.position.invalid": "Nhập X, Y và hướng hợp lệ.",
      "maps.editor.speed.title": "Speed zone",
      "maps.editor.speed.limit": "Giới hạn tốc độ (m/s)",
      "maps.editor.speed.hint": "Robot giảm tốc trong vùng này (0.1–1.5 m/s).",
      "maps.editor.speed.invalid": "Nhập tốc độ hợp lệ (0.1–1.5 m/s).",
      "maps.editor.sound.title": "Sound zone",
      "maps.editor.sound.select": "Sound",
      "maps.editor.sound.noSound": "— Chọn sound —",
      "maps.editor.sound.manage": "Quản lý sounds tại Setup → Sounds",
      "maps.editor.sound.invalid": "Chọn một sound.",
      "maps.editor.directional.title": "Directional zone",
      "maps.editor.directional.direction": "Hướng",
      "maps.editor.directional.degOption": "{deg}°",
      "maps.editor.directional.shapeHint": "Robot không được di chuyển ngược hướng mũi tên (bước 45°).",
      "maps.editor.directional.lineWidth": "Độ rộng line (cm)",
      "maps.editor.directional.reversed": "Đảo hướng",
      "maps.editor.directional.lineHint": "Hướng theo line từ điểm đầu đến điểm cuối.",
      "maps.editor.directional.lineWidthInvalid": "Nhập độ rộng line hợp lệ (≥ 10 cm).",
      "maps.editor.planner.title": "Planner zone",
      "maps.editor.planner.noLocalization": "No localization (chỉ encoder)",
      "maps.editor.planner.lookAhead": "Look-ahead (thu hẹp field of view)",
      "maps.editor.planner.ignoreObstacles": "Ignore obstacles",
      "maps.editor.planner.pathDeviation": "Path deviation (m)",
      "maps.editor.planner.pathTimeout": "Path timeout (s)",
      "maps.editor.io.title": "I/O zone",
      "maps.editor.io.module": "I/O module",
      "maps.editor.io.plcRegister": "PLC register",
      "maps.editor.io.plcValue": "Giá trị",
      "maps.editor.io.plcMode": "PLC mode",
      "maps.editor.io.plcModeSet": "Set",
      "maps.editor.io.plcModeAdd": "Add",
      "maps.editor.io.plcModeSubtract": "Subtract",
      "maps.editor.io.hint": "Robot kích hoạt I/O khi vào vùng này.",
      "maps.editor.io.moduleRequired": "Nhập tên I/O module.",
      "maps.menu.save": "Lưu map",

      "sounds.title": "Sounds",
      "sounds.subtitle": "Tạo và chỉnh sửa sounds.",
      "sounds.helpTitle": "Trợ giúp Sounds",
      "sounds.helpBody": "Upload âm thanh cho sound zones trên map. System sounds (Beep, Horn, Chime) không thể xóa. Dùng Listen hoặc Play on robot để kiểm tra volume.",
      "sounds.create": "Tạo sound",
      "sounds.clearFilters": "Xóa bộ lọc",
      "sounds.filterLabel": "Lọc:",
      "sounds.filterPlaceholder": "Lọc theo tên...",
      "sounds.itemsFound": "{n} mục",
      "sounds.pageOf": "Trang {page} / {total}",
      "sounds.empty": "Chưa có sound.",
      "sounds.emptyFilter": "Không có sound khớp bộ lọc.",
      "sounds.createTitle": "Tạo sound",
      "sounds.editTitle": "Sửa sound",
      "sounds.name": "Tên",
      "sounds.note": "Ghi chú",
      "sounds.description": "Mô tả",
      "sounds.enabled": "Bật",
      "sounds.volume": "Volume (0–100)",
      "sounds.volumeHint": "100% ≈ 80 dB. Kiểm tra volume bằng Play on robot.",
      "sounds.volumeShort": "{volume}%",
      "sounds.file": "File âm thanh",
      "sounds.noFile": "Chưa có file",
      "sounds.systemNoFile": "Âm thanh hệ thống (phát trên robot)",
      "sounds.systemBadge": "System sound",
      "sounds.systemShort": "System",
      "sounds.upload": "Upload file…",
      "sounds.listen": "Listen",
      "sounds.playOnRobot": "Play on robot",
      "sounds.play": "Phát",
      "sounds.playFailed": "Không phát được file.",
      "sounds.fileMeta": "{name} · {duration}",
      "sounds.saveChanges": "Lưu thay đổi",
      "sounds.nameRequired": "Nhập tên sound.",
      "sounds.nameDuplicate": "Đã có sound cùng tên. Hãy Edit bản ghi hiện có.",
      "sounds.reservedName": "Beep, Horn, Chime là system sounds — dùng Edit trên dòng System.",
      "sounds.deleteConfirm": "Xóa sound này?",
      "sounds.deleteConfirmTitle": "Xóa sound?",
      "sounds.deleteConfirmText": "Xóa \"{name}\"? Sound zones đang dùng có thể bị ảnh hưởng.",

      "transitions.title": "Transitions",
      "transitions.subtitle": "Tạo và chỉnh sửa transitions.",
      "transitions.helpTitle": "Trợ giúp Transitions",
      "transitions.helpBody": "Transition nối hai map trong cùng site. Start position trên map nguồn, Goal position trên map đích (cùng vị trí vật lý). Mission phải chứa các bước switch map.",
      "transitions.create": "Tạo transition",
      "transitions.clearFilters": "Xóa bộ lọc",
      "transitions.filterLabel": "Lọc:",
      "transitions.filterPlaceholder": "Lọc theo position hoặc mission...",
      "transitions.itemsFound": "{n} mục",
      "transitions.pageOf": "Trang {page} / {total}",
      "transitions.colStart": "Start",
      "transitions.colGoal": "Goal",
      "transitions.colMission": "Mission",
      "transitions.colCreatedBy": "Created by",
      "transitions.colFunctions": "Functions",
      "transitions.empty": "Chưa có transition.",
      "transitions.emptyFilter": "Không có transition khớp bộ lọc.",
      "transitions.createTitle": "Tạo transition",
      "transitions.editTitle": "Sửa transition",
      "transitions.site": "Site",
      "transitions.fromMap": "From map",
      "transitions.toMap": "To map",
      "transitions.startPosition": "Start position",
      "transitions.goalPosition": "Goal position",
      "transitions.mission": "Mission",
      "transitions.deleteTitle": "Xóa transition?",
      "transitions.deleteConfirmText": "Xóa transition {from} → {to}?",
      "transitions.error.missing": "Thiếu trường bắt buộc.",

      "users.title": "Users",
      "users.subtitle": "Tạo và chỉnh sửa users.",
      "users.helpTitle": "Trợ giúp Users",
      "users.helpBody": "Mỗi người cần user profile. Name hiển thị góc phải khi đăng nhập; Username dùng để login. Quyền theo User group (§4.6). PIN 4 số chỉ cho nhóm cho phép. User tự đổi password qua menu góc phải.",
      "users.create": "Tạo user",
      "users.clearFilters": "Xóa bộ lọc",
      "users.filterLabel": "Lọc:",
      "users.filterPlaceholder": "Lọc theo tên hoặc username...",
      "users.itemsFound": "{n} mục",
      "users.pageOf": "Trang {page} / {total}",
      "users.colName": "Name",
      "users.colUsername": "Username",
      "users.colGroup": "User group",
      "users.colEmail": "Email",
      "users.colPin": "PIN",
      "users.colFunctions": "Functions",
      "users.empty": "Chưa có user.",
      "users.emptyFilter": "Không có user khớp bộ lọc.",
      "users.createTitle": "Tạo user",
      "users.editTitle": "Sửa user",
      "users.fieldName": "Name",
      "users.fieldUsername": "Username",
      "users.fieldPassword": "Password",
      "users.fieldEmail": "Email address",
      "users.fieldGroup": "User group",
      "users.fieldEnabled": "Enabled",
      "users.fieldPinEnable": "Cho phép đăng nhập PIN",
      "users.fieldPin": "Mã PIN (4 số)",
      "users.passwordEditHint": "User tự đổi password qua menu góc phải trên.",
      "users.pinGroupHint": "PIN chỉ dùng được với user group cho phép PIN.",
      "users.pinYes": "Có",
      "users.pinNo": "Không",
      "users.save": "Lưu",
      "users.deleteTitle": "Xóa user?",
      "users.deleteConfirmText": "Xóa user \"{name}\"?",
      "users.deleteHint": "Chỉ xóa hồ sơ user. Map, mission và dữ liệu khác do user tạo vẫn giữ nguyên.",
      "users.error.missing": "Thiếu trường bắt buộc.",
      "users.error.passwordRequired": "Nhập password khi tạo user.",
      "users.error.pinInvalid": "PIN phải đúng 4 chữ số.",

      "userGroups.title": "User groups",
      "userGroups.subtitle": "Tạo và chỉnh sửa user groups.",
      "userGroups.helpTitle": "Trợ giúp User groups",
      "userGroups.helpBody": "Mỗi user thuộc một user group. Quyền (none/read/write) áp dụng cho từng module. Tạo group trước khi tạo user. PIN chỉ dùng khi bật Allow PIN sign-in.",
      "userGroups.create": "Tạo user group",
      "userGroups.clearFilters": "Xóa bộ lọc",
      "userGroups.filterLabel": "Lọc:",
      "userGroups.filterPlaceholder": "Lọc theo tên...",
      "userGroups.itemsFound": "{n} mục",
      "userGroups.pageOf": "Trang {page} / {total}",
      "userGroups.colName": "Name",
      "userGroups.colUsers": "Users",
      "userGroups.colPin": "PIN sign-in",
      "userGroups.colPermissions": "Permissions",
      "userGroups.colFunctions": "Functions",
      "userGroups.empty": "Chưa có user group.",
      "userGroups.emptyFilter": "Không có user group khớp bộ lọc.",
      "userGroups.createTitle": "Tạo user group",
      "userGroups.editTitle": "Sửa user group",
      "userGroups.fieldName": "Name",
      "userGroups.fieldAllowPin": "Cho phép đăng nhập PIN",
      "userGroups.permissionsTitle": "Permissions",
      "userGroups.permissionsHint": "Gán quyền read hoặc write cho từng module. None ẩn module khỏi menu.",
      "userGroups.permColModule": "Module",
      "userGroups.permColAccess": "Access",
      "userGroups.perm.dashboard": "Dashboards",
      "userGroups.perm.config": "Build robot",
      "userGroups.perm.maps": "Maps",
      "userGroups.perm.missions": "Missions",
      "userGroups.perm.sounds": "Sounds",
      "userGroups.perm.integrations": "Integrations",
      "userGroups.perm.users": "Users & User groups",
      "userGroups.permLevel.none": "None",
      "userGroups.permLevel.read": "Read",
      "userGroups.permLevel.write": "Write",
      "userGroups.permSummaryAllWrite": "Full write",
      "userGroups.permSummaryNone": "No access",
      "userGroups.permSummaryMixed": "{write} write, {read} read",
      "userGroups.pinYes": "Có",
      "userGroups.pinNo": "Không",
      "userGroups.userCount": "{n} user(s)",
      "userGroups.save": "Lưu",
      "userGroups.deleteTitle": "Xóa user group?",
      "userGroups.deleteConfirmText": "Xóa user group \"{name}\"?",
      "userGroups.deleteHint": "Phải chuyển users sang group khác trước khi xóa.",
      "userGroups.error.missing": "Thiếu trường bắt buộc.",

      "ioModules.title": "I/O modules",
      "ioModules.subtitle": "Tạo và quản lý kết nối I/O.",
      "ioModules.helpTitle": "Trợ giúp I/O modules",
      "ioModules.helpBody": "I/O module (Bluetooth hoặc WISE) dùng để giao tiếp cửa, thang nâng, băng tải… Nhập IP và chọn loại module. Dùng trong mission (Set output / Wait for input) và I/O zones trên map.",
      "ioModules.create": "Tạo I/O connection",
      "ioModules.clearFilters": "Xóa bộ lọc",
      "ioModules.filterLabel": "Lọc:",
      "ioModules.filterPlaceholder": "Lọc theo tên hoặc IP...",
      "ioModules.itemsFound": "{n} mục",
      "ioModules.pageOf": "Trang {page} / {total}",
      "ioModules.colName": "Name",
      "ioModules.colType": "Type",
      "ioModules.colIp": "IP address",
      "ioModules.colStatus": "Status",
      "ioModules.colFunctions": "Functions",
      "ioModules.empty": "Chưa có I/O module.",
      "ioModules.emptyFilter": "Không có I/O module khớp bộ lọc.",
      "ioModules.createTitle": "Tạo I/O connection",
      "ioModules.editTitle": "Sửa I/O connection",
      "ioModules.fieldSite": "Site",
      "ioModules.fieldName": "Name",
      "ioModules.fieldType": "I/O module type",
      "ioModules.fieldIp": "IP address",
      "ioModules.type.bluetooth": "Bluetooth",
      "ioModules.type.wise": "WISE",
      "ioModules.typeHint.bluetooth": "Output ports 1–4.",
      "ioModules.typeHint.wise": "Output ports 0–3.",
      "ioModules.status.connected": "Connected",
      "ioModules.status.disconnected": "Disconnected",
      "ioModules.save": "Lưu",
      "ioModules.deleteTitle": "Xóa I/O connection?",
      "ioModules.deleteConfirmText": "Xóa I/O module \"{name}\"?",
      "ioModules.error.missing": "Thiếu trường bắt buộc.",
      "ioModules.testConnection": "Kiểm tra kết nối",
      "ioModules.testOk": "Kết nối TCP thành công.",

      "paths.title": "Paths",
      "paths.subtitle": "Xem và xóa tuyến đường đã lưu giữa các position.",
      "paths.helpTitle": "Trợ giúp Paths",
      "paths.helpBody": "Path là tuyến đường giữa hai position trên cùng một map. Path được tạo tự động lần đầu robot chạy từ position A đến B và được tái sử dụng cho các lần sau. Xóa path để buộc tính lại (ví dụ sau khi thêm preferred zone). Không thể tạo path thủ công.",
      "paths.clearFilters": "Xóa bộ lọc",
      "paths.filterLabel": "Lọc:",
      "paths.filterPlaceholder": "Lọc theo position hoặc map...",
      "paths.itemsFound": "{n} mục",
      "paths.pageOf": "Trang {page} / {total}",
      "paths.colFrom": "From position",
      "paths.colTo": "To position",
      "paths.colMap": "Map",
      "paths.colFunctions": "Functions",
      "paths.empty": "Chưa có path. Path được tạo tự động khi robot di chuyển giữa hai position.",
      "paths.emptyFilter": "Không có path khớp bộ lọc.",
      "paths.view": "Xem trên map",
      "paths.viewUnavailable": "Không mở được map editor.",
      "paths.deleteTitle": "Xóa path?",
      "paths.deleteConfirmText": "Xóa path từ \"{from}\" đến \"{to}\"? Path sẽ được tạo lại khi robot chạy lại giữa hai position này.",

      "pathGuides.title": "Path guides",
      "pathGuides.subtitle": "Định nghĩa tuyến cố định giữa các position qua waypoint.",
      "pathGuides.helpTitle": "Trợ giúp Path guides",
      "pathGuides.helpBody": "Path guide buộc robot đi theo chuỗi start → waypoint → goal trên map. Tạo position trên map editor trước, sau đó thêm start, waypoint (có thứ tự) và goal. Robot tự dùng path guide khi mission nhắm tới các position đó (Phase D).",
      "pathGuides.create": "Tạo path guide",
      "pathGuides.clearFilters": "Xóa bộ lọc",
      "pathGuides.filterLabel": "Lọc:",
      "pathGuides.filterPlaceholder": "Lọc theo tên hoặc map...",
      "pathGuides.itemsFound": "{n} mục",
      "pathGuides.pageOf": "Trang {page} / {total}",
      "pathGuides.colName": "Tên",
      "pathGuides.colMap": "Map",
      "pathGuides.colStarts": "Starts",
      "pathGuides.colVias": "Waypoints",
      "pathGuides.colGoals": "Goals",
      "pathGuides.colFunctions": "Functions",
      "pathGuides.empty": "Chưa có path guide. Tạo waypoint trên map trước, sau đó thêm path guide.",
      "pathGuides.emptyFilter": "Không có path guide khớp bộ lọc.",
      "pathGuides.createTitle": "Tạo path guide",
      "pathGuides.editTitle": "Sửa path guide",
      "pathGuides.name": "Tên",
      "pathGuides.site": "Site",
      "pathGuides.map": "Map",
      "pathGuides.mapHint": "Tạo position trên map editor trước khi thêm start, waypoint và goal.",
      "pathGuides.starts": "Start positions",
      "pathGuides.vias": "Waypoints (có thứ tự)",
      "pathGuides.goals": "Goal positions",
      "pathGuides.addStart": "Thêm start",
      "pathGuides.addVia": "Thêm waypoint",
      "pathGuides.addGoal": "Thêm goal",
      "pathGuides.selectPosition": "Chọn position…",
      "pathGuides.noStarts": "Chưa có start",
      "pathGuides.noVias": "Chưa có waypoint",
      "pathGuides.noGoals": "Chưa có goal",
      "pathGuides.moveUp": "Lên",
      "pathGuides.moveDown": "Xuống",
      "pathGuides.deleteTitle": "Xóa path guide?",
      "pathGuides.deleteConfirmText": "Xóa path guide \"{name}\" trên map \"{map}\"?",
      "pathGuides.error.missing": "Thiếu tên, site hoặc map.",
      "pathGuides.error.positions": "Cần ít nhất một start và một goal.",
      "pathGuides.error.noPositions": "Không có position nào trên map này. Tạo position trong map editor trước.",
      "pathGuides.dragHandle": "Kéo để đổi thứ tự",
      "pathGuides.createPage.title": "Tạo path guide",
      "pathGuides.createPage.subtitle": "Nhập tên và chọn map cho path guide.",
      "pathGuides.createPage.goBack": "Quay lại",
      "pathGuides.createPage.namePlaceholder": "Nhập tên path guide...",
      "pathGuides.createPage.continue": "Tiếp tục",
      "pathGuides.editPage.title": "Sửa vị trí path guide",
      "pathGuides.editPage.subtitle": "Chỉnh start, waypoint và goal của path guide.",
      "pathGuides.editPage.helpTitle": "Trợ giúp vị trí path guide",
      "pathGuides.editPage.helpBody": "Thêm start, waypoint (kéo để đổi thứ tự) và goal. Robot sẽ đi theo chuỗi này khi mission dùng các position tương ứng.",
      "pathGuides.editPage.goBack": "Quay lại",
      "pathGuides.editPage.meta": "{name} — {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.connect_bluetooth": "Connect Bluetooth module",
      "missions.action.disconnect_bluetooth": "Disconnect Bluetooth module",
      "missions.action.while": "While",
      "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.action.switch_map": "Switch map",
      "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.sounds": "Sounds",
      "nav.transitions": "Transitions",
      "nav.user-groups": "User groups",
      "nav.io-modules": "I/O modules",
      "nav.paths": "Paths",
      "nav.path-guides": "Path guides",
      "nav.users": "Users",
      "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.io_connect": "Connect I/O module",
      "dashboard.widget.io_status": "I/O status",
      "dashboard.widget.io_connectAction": "Connect",
      "dashboard.widget.io_disconnect": "Disconnect",
      "dashboard.widget.io_configuration": "I/O configuration",
      "dashboard.widget.ioPort": "Port {port}",
      "dashboard.widget.ioOn": "ON",
      "dashboard.widget.ioOff": "OFF",
      "dashboard.widget.field.ioModule": "I/O module",
      "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.importNoSite": "Select a site before importing.",
      "maps.importSuccess": "Import complete: {maps} map(s), {io} I/O module(s), {transitions} transition(s), {paths} path(s).",
      "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.deleteDialog.title": "Delete map?",
      "maps.deleteDialog.text": "Delete map \"{name}\"? This cannot be undone.",
      "maps.deleteDialog.activeWarning": "This map is currently active on the robot.",
      "maps.deleteForbidden": "You cannot delete a map from another user group.",
      "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",
      "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.preferredPathWarning": "This map has cached paths. Preferred zone changes may not apply until you delete the related paths under Setup → Paths. Save anyway?",
      "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.origin": "Show map origin",
      "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.editor.objectType.wall": "Walls",
      "maps.editor.objectType.floor": "Floors",
      "maps.editor.objectType.position": "Positions",
      "maps.editor.objectType.forbidden": "Forbidden zones",
      "maps.editor.objectType.preferred": "Preferred zones",
      "maps.editor.objectType.unpreferred": "Unpreferred zones",
      "maps.editor.objectType.speed": "Speed zones",
      "maps.editor.objectType.sound": "Sound zones",
      "maps.editor.objectType.directional": "Directional zones",
      "maps.editor.objectType.directionalLine": "Directional lines",
      "maps.editor.objectType.planner": "Planner zones",
      "maps.editor.objectType.io": "I/O zones",
      "maps.editor.filter.walls": "Walls",
      "maps.editor.filter.floors": "Floors",
      "maps.editor.filter.positions": "Positions",
      "maps.editor.filter.forbidden": "Forbidden",
      "maps.editor.filter.preferred": "Preferred",
      "maps.editor.filter.unpreferred": "Unpreferred",
      "maps.editor.filter.speed": "Speed",
      "maps.editor.filter.sound": "Sound",
      "maps.editor.tool.drawLine": "Draw line — polyline (wall, drive zone line…)",
      "maps.editor.tool.drawShape": "Draw shape — polygon (floor, zones…)",
      "maps.editor.drawLine.title": "Draw a new line",
      "maps.editor.drawLine.lineWidth": "Line width (cm)",
      "maps.editor.drawLine.hint": "Set the line width in cm, confirm, then click on the map to place points.",
      "maps.editor.drawLine.confirm": "Confirm",
      "maps.editor.drawLine.lineWidthInvalid": "Enter a valid line width (wall ≥ 5 cm, zone line ≥ 10 cm).",
      "maps.editor.tool.draw": "Draw — line or polygon (walls, zones…)",
      "maps.editor.tool.select": "Select object",
      "maps.editor.tool.erase": "Eraser",
      "maps.editor.tool.eraser": "Pixel eraser — remove noise on floor plan (Walls/Floors layer)",
      "maps.editor.tool.eraseShape": "Erase shape or line",
      "maps.editor.tool.eraseSelection": "Erase by selection — clear pixels in rectangle",
      "maps.editor.tool.confirmDraw": "Confirm shape",
      "maps.editor.drawNeedMorePoints": "Add more points before confirming (line ≥ 2, polygon ≥ 3).",
      "maps.editor.position.title": "Position",
      "maps.editor.position.name": "Name",
      "maps.editor.position.x": "X (m)",
      "maps.editor.position.y": "Y (m)",
      "maps.editor.position.yaw": "Orientation (°)",
      "maps.editor.position.hint": "Click map and drag to set orientation, then confirm.",
      "maps.editor.position.invalid": "Enter valid X, Y and orientation.",
      "maps.editor.speed.title": "Speed zone",
      "maps.editor.speed.limit": "Speed limit (m/s)",
      "maps.editor.speed.hint": "Robot slows to this speed inside the zone (0.1–1.5 m/s).",
      "maps.editor.speed.invalid": "Enter a valid speed (0.1–1.5 m/s).",
      "maps.editor.sound.title": "Sound zone",
      "maps.editor.sound.select": "Sound",
      "maps.editor.sound.noSound": "— Select sound —",
      "maps.editor.sound.manage": "Manage sounds in Setup → Sounds",
      "maps.editor.sound.invalid": "Select a sound.",
      "maps.editor.directional.title": "Directional zone",
      "maps.editor.directional.direction": "Direction",
      "maps.editor.directional.degOption": "{deg}°",
      "maps.editor.directional.shapeHint": "Robot cannot move opposite to the arrow (45° steps).",
      "maps.editor.directional.lineWidth": "Line width (cm)",
      "maps.editor.directional.reversed": "Reverse direction",
      "maps.editor.directional.lineHint": "Direction follows the line from first to last point.",
      "maps.editor.directional.lineWidthInvalid": "Enter a valid line width (≥ 10 cm).",
      "maps.editor.planner.title": "Planner zone",
      "maps.editor.planner.noLocalization": "No localization (encoders only)",
      "maps.editor.planner.lookAhead": "Look-ahead (narrow field of view)",
      "maps.editor.planner.ignoreObstacles": "Ignore obstacles",
      "maps.editor.planner.pathDeviation": "Path deviation (m)",
      "maps.editor.planner.pathTimeout": "Path timeout (s)",
      "maps.editor.io.title": "I/O zone",
      "maps.editor.io.module": "I/O module",
      "maps.editor.io.plcRegister": "PLC register",
      "maps.editor.io.plcValue": "Value",
      "maps.editor.io.plcMode": "PLC mode",
      "maps.editor.io.plcModeSet": "Set",
      "maps.editor.io.plcModeAdd": "Add",
      "maps.editor.io.plcModeSubtract": "Subtract",
      "maps.editor.io.hint": "Robot activates I/O when entering the zone.",
      "maps.editor.io.moduleRequired": "Enter an I/O module name.",
      "maps.menu.save": "Save map",

      "sounds.title": "Sounds",
      "sounds.subtitle": "Create and edit sounds.",
      "sounds.helpTitle": "Sounds help",
      "sounds.helpBody": "Upload audio for sound zones on maps. System sounds (Beep, Horn, Chime) cannot be deleted. Use Listen or Play on robot to verify volume.",
      "sounds.create": "Create sound",
      "sounds.clearFilters": "Clear filters",
      "sounds.filterLabel": "Filter:",
      "sounds.filterPlaceholder": "Filter by name...",
      "sounds.itemsFound": "{n} item(s) found",
      "sounds.pageOf": "Page {page} of {total}",
      "sounds.empty": "No sounds yet.",
      "sounds.emptyFilter": "No sounds match the filter.",
      "sounds.createTitle": "Create sound",
      "sounds.editTitle": "Edit sound",
      "sounds.name": "Name",
      "sounds.note": "Note",
      "sounds.description": "Description",
      "sounds.enabled": "Enabled",
      "sounds.volume": "Volume (0–100)",
      "sounds.volumeHint": "100% is approximately 80 dB. Verify volume by playing on the robot.",
      "sounds.volumeShort": "{volume}%",
      "sounds.file": "Audio file",
      "sounds.noFile": "No file uploaded",
      "sounds.systemNoFile": "Built-in system sound (plays on robot)",
      "sounds.systemBadge": "System sound",
      "sounds.systemShort": "System",
      "sounds.upload": "Upload file…",
      "sounds.listen": "Listen",
      "sounds.playOnRobot": "Play on robot",
      "sounds.play": "Play",
      "sounds.playFailed": "Could not play file.",
      "sounds.fileMeta": "{name} · {duration}",
      "sounds.saveChanges": "Save changes",
      "sounds.nameRequired": "Enter a sound name.",
      "sounds.nameDuplicate": "A sound with this name already exists. Edit the existing entry instead.",
      "sounds.reservedName": "Beep, Horn, and Chime are built-in system sounds — use Edit on the System row.",
      "sounds.deleteConfirm": "Delete this sound?",
      "sounds.deleteConfirmTitle": "Delete sound?",
      "sounds.deleteConfirmText": "Delete \"{name}\"? Sound zones using it may be affected.",

      "transitions.title": "Transitions",
      "transitions.subtitle": "Create and edit transitions.",
      "transitions.helpTitle": "Transitions help",
      "transitions.helpBody": "A transition links two maps in the same site. Start position is on the source map; Goal position is on the destination map (same physical location). The mission should include switch map actions.",
      "transitions.create": "Create transition",
      "transitions.clearFilters": "Clear filters",
      "transitions.filterLabel": "Filter:",
      "transitions.filterPlaceholder": "Filter by position or mission...",
      "transitions.itemsFound": "{n} item(s) found",
      "transitions.pageOf": "Page {page} of {total}",
      "transitions.colStart": "Start",
      "transitions.colGoal": "Goal",
      "transitions.colMission": "Mission",
      "transitions.colCreatedBy": "Created by",
      "transitions.colFunctions": "Functions",
      "transitions.empty": "No transitions yet.",
      "transitions.emptyFilter": "No transitions match the filter.",
      "transitions.createTitle": "Create transition",
      "transitions.editTitle": "Edit transition",
      "transitions.site": "Site",
      "transitions.fromMap": "From map",
      "transitions.toMap": "To map",
      "transitions.startPosition": "Start position",
      "transitions.goalPosition": "Goal position",
      "transitions.mission": "Mission",
      "transitions.deleteTitle": "Delete transition?",
      "transitions.deleteConfirmText": "Delete transition {from} → {to}?",
      "transitions.error.missing": "Missing required fields.",

      "users.title": "Users",
      "users.subtitle": "Create and edit users.",
      "users.helpTitle": "Users help",
      "users.helpBody": "Everyone needs a user profile. Name is shown in the upper right when signed in; Username is used to log in. Permissions come from the user group (§4.6). PIN is only for groups that allow PIN sign-in. Users change their own password from the menu in the upper right.",
      "users.create": "Create user",
      "users.clearFilters": "Clear filters",
      "users.filterLabel": "Filter:",
      "users.filterPlaceholder": "Filter by name or username...",
      "users.itemsFound": "{n} item(s) found",
      "users.pageOf": "Page {page} of {total}",
      "users.colName": "Name",
      "users.colUsername": "Username",
      "users.colGroup": "User group",
      "users.colEmail": "Email",
      "users.colPin": "PIN",
      "users.colFunctions": "Functions",
      "users.empty": "No users yet.",
      "users.emptyFilter": "No users match the filter.",
      "users.createTitle": "Create user",
      "users.editTitle": "Edit user",
      "users.fieldName": "Name",
      "users.fieldUsername": "Username",
      "users.fieldPassword": "Password",
      "users.fieldEmail": "Email address",
      "users.fieldGroup": "User group",
      "users.fieldEnabled": "Enabled",
      "users.fieldPinEnable": "Allow PIN sign-in",
      "users.fieldPin": "PIN code (4 digits)",
      "users.passwordEditHint": "Users change their own password from the menu in the upper right corner.",
      "users.pinGroupHint": "PIN is only available for user groups that allow PIN sign-in.",
      "users.pinYes": "Yes",
      "users.pinNo": "No",
      "users.save": "Save",
      "users.deleteTitle": "Delete user?",
      "users.deleteConfirmText": "Delete user \"{name}\"?",
      "users.deleteHint": "Only the user profile is removed. Maps, missions and other data created by this user remain unchanged.",
      "users.error.missing": "Missing required fields.",
      "users.error.passwordRequired": "Enter a password when creating a user.",
      "users.error.pinInvalid": "PIN must be exactly 4 digits.",

      "userGroups.title": "User groups",
      "userGroups.subtitle": "Create and edit user groups.",
      "userGroups.helpTitle": "User groups help",
      "userGroups.helpBody": "Each user belongs to one user group. Permissions (none/read/write) apply per module. Create groups before users. PIN sign-in works only when Allow PIN sign-in is enabled.",
      "userGroups.create": "Create user group",
      "userGroups.clearFilters": "Clear filters",
      "userGroups.filterLabel": "Filter:",
      "userGroups.filterPlaceholder": "Filter by name...",
      "userGroups.itemsFound": "{n} item(s) found",
      "userGroups.pageOf": "Page {page} of {total}",
      "userGroups.colName": "Name",
      "userGroups.colUsers": "Users",
      "userGroups.colPin": "PIN sign-in",
      "userGroups.colPermissions": "Permissions",
      "userGroups.colFunctions": "Functions",
      "userGroups.empty": "No user groups yet.",
      "userGroups.emptyFilter": "No user groups match the filter.",
      "userGroups.createTitle": "Create user group",
      "userGroups.editTitle": "Edit user group",
      "userGroups.fieldName": "Name",
      "userGroups.fieldAllowPin": "Allow PIN sign-in",
      "userGroups.permissionsTitle": "Permissions",
      "userGroups.permissionsHint": "Set read or write access per module. None hides the module from the menu.",
      "userGroups.permColModule": "Module",
      "userGroups.permColAccess": "Access",
      "userGroups.perm.dashboard": "Dashboards",
      "userGroups.perm.config": "Build robot",
      "userGroups.perm.maps": "Maps",
      "userGroups.perm.missions": "Missions",
      "userGroups.perm.sounds": "Sounds",
      "userGroups.perm.integrations": "Integrations",
      "userGroups.perm.users": "Users & User groups",
      "userGroups.permLevel.none": "None",
      "userGroups.permLevel.read": "Read",
      "userGroups.permLevel.write": "Write",
      "userGroups.permSummaryAllWrite": "Full write",
      "userGroups.permSummaryNone": "No access",
      "userGroups.permSummaryMixed": "{write} write, {read} read",
      "userGroups.pinYes": "Yes",
      "userGroups.pinNo": "No",
      "userGroups.userCount": "{n} user(s)",
      "userGroups.save": "Save",
      "userGroups.deleteTitle": "Delete user group?",
      "userGroups.deleteConfirmText": "Delete user group \"{name}\"?",
      "userGroups.deleteHint": "Users must be moved to another group before this group can be deleted.",
      "userGroups.error.missing": "Missing required fields.",

      "ioModules.title": "I/O modules",
      "ioModules.subtitle": "Create and manage I/O connections.",
      "ioModules.helpTitle": "I/O modules help",
      "ioModules.helpBody": "I/O modules (Bluetooth or WISE) communicate with doors, lifts, conveyors, etc. Enter the IP and module type. Use in missions (Set output / Wait for input) and I/O zones on maps.",
      "ioModules.create": "Create I/O connection",
      "ioModules.clearFilters": "Clear filters",
      "ioModules.filterLabel": "Filter:",
      "ioModules.filterPlaceholder": "Filter by name or IP...",
      "ioModules.itemsFound": "{n} item(s) found",
      "ioModules.pageOf": "Page {page} of {total}",
      "ioModules.colName": "Name",
      "ioModules.colType": "Type",
      "ioModules.colIp": "IP address",
      "ioModules.colStatus": "Status",
      "ioModules.colFunctions": "Functions",
      "ioModules.empty": "No I/O modules yet.",
      "ioModules.emptyFilter": "No I/O modules match the filter.",
      "ioModules.createTitle": "Create I/O connection",
      "ioModules.editTitle": "Edit I/O connection",
      "ioModules.fieldSite": "Site",
      "ioModules.fieldName": "Name",
      "ioModules.fieldType": "I/O module type",
      "ioModules.fieldIp": "IP address",
      "ioModules.type.bluetooth": "Bluetooth",
      "ioModules.type.wise": "WISE",
      "ioModules.typeHint.bluetooth": "Output ports 1–4.",
      "ioModules.typeHint.wise": "Output ports 0–3.",
      "ioModules.status.connected": "Connected",
      "ioModules.status.disconnected": "Disconnected",
      "ioModules.save": "Save",
      "ioModules.deleteTitle": "Delete I/O connection?",
      "ioModules.deleteConfirmText": "Delete I/O module \"{name}\"?",
      "ioModules.error.missing": "Missing required fields.",
      "ioModules.testConnection": "Test connection",
      "ioModules.testOk": "TCP connection successful.",

      "paths.title": "Paths",
      "paths.subtitle": "View and delete cached routes between positions.",
      "paths.helpTitle": "Paths help",
      "paths.helpBody": "A path is a saved route between two positions on the same map. It is created automatically the first time the robot drives from position A to B and reused afterward. Delete a path to force recalculation (for example after adding preferred zones). Paths cannot be created manually.",
      "paths.clearFilters": "Clear filters",
      "paths.filterLabel": "Filter:",
      "paths.filterPlaceholder": "Filter by position or map...",
      "paths.itemsFound": "{n} item(s) found",
      "paths.pageOf": "Page {page} of {total}",
      "paths.colFrom": "From position",
      "paths.colTo": "To position",
      "paths.colMap": "Map",
      "paths.colFunctions": "Functions",
      "paths.empty": "No paths yet. Paths are created automatically when the robot drives between two positions.",
      "paths.emptyFilter": "No paths match the filter.",
      "paths.view": "View on map",
      "paths.viewUnavailable": "Could not open the map editor.",
      "paths.deleteTitle": "Delete path?",
      "paths.deleteConfirmText": "Delete path from \"{from}\" to \"{to}\"? It will be recreated when the robot drives between these positions again.",

      "pathGuides.title": "Path guides",
      "pathGuides.subtitle": "Define fixed routes between positions using waypoints.",
      "pathGuides.helpTitle": "Path guides help",
      "pathGuides.helpBody": "A path guide forces the robot to follow a start → waypoint → goal chain on the map. Create positions in the map editor first, then add starts, ordered waypoints, and goals. The robot will use the guide automatically when missions target those positions (planner integration in a later phase).",
      "pathGuides.create": "Create path guide",
      "pathGuides.clearFilters": "Clear filters",
      "pathGuides.filterLabel": "Filter:",
      "pathGuides.filterPlaceholder": "Filter by name or map...",
      "pathGuides.itemsFound": "{n} item(s) found",
      "pathGuides.pageOf": "Page {page} of {total}",
      "pathGuides.colName": "Name",
      "pathGuides.colMap": "Map",
      "pathGuides.colStarts": "Starts",
      "pathGuides.colVias": "Waypoints",
      "pathGuides.colGoals": "Goals",
      "pathGuides.colFunctions": "Functions",
      "pathGuides.empty": "No path guides yet. Create waypoint positions on the map first, then add a path guide.",
      "pathGuides.emptyFilter": "No path guides match the filter.",
      "pathGuides.createTitle": "Create path guide",
      "pathGuides.editTitle": "Edit path guide",
      "pathGuides.name": "Name",
      "pathGuides.site": "Site",
      "pathGuides.map": "Map",
      "pathGuides.mapHint": "Create positions on the map editor before adding starts, waypoints, and goals.",
      "pathGuides.starts": "Start positions",
      "pathGuides.vias": "Waypoints (ordered)",
      "pathGuides.goals": "Goal positions",
      "pathGuides.addStart": "Add start",
      "pathGuides.addVia": "Add waypoint",
      "pathGuides.addGoal": "Add goal",
      "pathGuides.selectPosition": "Select position…",
      "pathGuides.noStarts": "No starts yet",
      "pathGuides.noVias": "No waypoints yet",
      "pathGuides.noGoals": "No goals yet",
      "pathGuides.moveUp": "Move up",
      "pathGuides.moveDown": "Move down",
      "pathGuides.deleteTitle": "Delete path guide?",
      "pathGuides.deleteConfirmText": "Delete path guide \"{name}\" on map \"{map}\"?",
      "pathGuides.error.missing": "Name, site, and map are required.",
      "pathGuides.error.positions": "At least one start and one goal are required.",
      "pathGuides.error.noPositions": "No positions on this map. Create positions in the map editor first.",
      "pathGuides.dragHandle": "Drag to reorder",
      "pathGuides.createPage.title": "Create path guide",
      "pathGuides.createPage.subtitle": "Enter a name and select the map for this path guide.",
      "pathGuides.createPage.goBack": "Go back",
      "pathGuides.createPage.namePlaceholder": "Enter path guide name...",
      "pathGuides.createPage.continue": "Continue",
      "pathGuides.editPage.title": "Edit path guide positions",
      "pathGuides.editPage.subtitle": "Edit the path guide's positions.",
      "pathGuides.editPage.helpTitle": "Path guide positions help",
      "pathGuides.editPage.helpBody": "Add starts, waypoints (drag to reorder), and goals. The robot follows this chain when missions use the matching positions.",
      "pathGuides.editPage.goBack": "Go back",
      "pathGuides.editPage.meta": "{name} — {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.connect_bluetooth": "Connect Bluetooth module",
      "missions.action.disconnect_bluetooth": "Disconnect Bluetooth module",
      "missions.action.while": "While",
      "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.action.switch_map": "Switch map",
      "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();
  }
})();
