diff --git a/.gitignore b/.gitignore index f69f69a60..b354052dc 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,9 @@ data/ # JWT secret file (should be in data dir, but protect project root too) .jwt_secret +# SpoolBuddy SSH keys (generated at runtime for remote updates) +spoolbuddy/ssh/ + # Security scan output *.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c975e9e..6bcc24de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to Bambuddy will be documented in this file. ### Fixed - **SpoolBuddy Update Check Always Shows "Up to Date"** — The SpoolBuddy daemon update check compared the device's firmware version against GitHub releases instead of the running Bambuddy backend version. This meant the check could incorrectly report "up to date" even when the daemon was behind. Fixed by comparing directly against `APP_VERSION` from the backend config. +- **SpoolBuddy Updates Now Use SSH** — Replaced the fragile self-update mechanism (daemon pulls its own code via git, permission errors on `.git/`, hardcoded `main` branch) with SSH-based updates driven by the Bambuddy backend. Bambuddy now SSHes into the SpoolBuddy Pi and runs git fetch/checkout, pip install, and systemctl restart remotely. Updates automatically use the same branch as Bambuddy. The install script now creates the `spoolbuddy` user with SSH access and a narrow sudoers entry for service restart only. A "Force Update" button allows re-deploying even when versions match. The SSH public key is shown in SpoolBuddy Settings for easy pairing. - **Virtual Printer Proxy A1 Printing Fails** ([#757](https://github.com/maziggy/bambuddy/issues/757)) — BambuStudio could not send prints to A1 (and potentially P1S) virtual printers in proxy mode. The slicer connects to undocumented proprietary ports 2024-2026 on these models, which the proxy was not forwarding, causing BambuStudio to show an access code dialog instead of printing. Added transparent TCP pass-through proxying for ports 2024-2026. These ports are silently ignored on models that don't use them (X1C, H2C, P2S). Reported by @Utility9298. - **Spool Assignment on Empty AMS Slots** ([#784](https://github.com/maziggy/bambuddy/issues/784)) — Empty AMS slots (no physical spool detected) showed "Assign Spool" and "Configure" buttons in the hover popup. Assigning a spool to an empty slot created a stuck state because no "Unassign" button is available for empty slots. Removed both buttons from empty AMS and HT AMS slot popups. External spool holders are unaffected. Reported by @RosdasHH. diff --git a/Dockerfile b/Dockerfile index ab4f26f7f..d369f299b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ffmpeg \ iproute2 \ libcap2-bin \ + openssh-client \ && rm -rf /var/lib/apt/lists/* # Allow binding to privileged ports (e.g. 990/FTPS) as non-root user. diff --git a/backend/app/api/routes/spoolbuddy.py b/backend/app/api/routes/spoolbuddy.py index 945cb0bda..99cfa59d8 100644 --- a/backend/app/api/routes/spoolbuddy.py +++ b/backend/app/api/routes/spoolbuddy.py @@ -1,5 +1,6 @@ """SpoolBuddy device management API routes.""" +import asyncio import logging from datetime import datetime, timedelta, timezone @@ -597,10 +598,17 @@ async def check_daemon_update( @router.post("/devices/{device_id}/update") async def trigger_daemon_update( device_id: str, + req: dict | None = None, db: AsyncSession = Depends(get_db), _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_UPDATE), ): - """Trigger a daemon update on the SpoolBuddy device via pending_command.""" + """Trigger a SpoolBuddy update over SSH. + + Bambuddy SSHes into the device, pulls the matching branch, installs deps, + and restarts the daemon. Progress is broadcast via WebSocket. + """ + from backend.app.services.spoolbuddy_ssh import perform_ssh_update + result = await db.execute(select(SpoolBuddyDevice).where(SpoolBuddyDevice.device_id == device_id)) device = result.scalar_one_or_none() if not device: @@ -612,12 +620,11 @@ async def trigger_daemon_update( if device.update_status == "updating": return {"status": "already_updating", "message": "Update already in progress"} - device.pending_command = "update" device.update_status = "pending" - device.update_message = "Waiting for device to pick up update command..." + device.update_message = "Starting SSH update..." await db.commit() - logger.info("SpoolBuddy %s: update command queued", device_id) + logger.info("SpoolBuddy %s: SSH update triggered (ip=%s)", device_id, device.ip_address) await ws_manager.broadcast( { "type": "spoolbuddy_update", @@ -626,7 +633,24 @@ async def trigger_daemon_update( } ) - return {"status": "ok", "message": "Update command sent to device"} + # Run the SSH update in the background + asyncio.create_task(perform_ssh_update(device_id, device.ip_address)) + + return {"status": "ok", "message": "SSH update started"} + + +@router.get("/ssh/public-key") +async def get_ssh_public_key( + _: User | None = RequirePermissionIfAuthEnabled(Permission.SETTINGS_READ), +): + """Return the SSH public key for SpoolBuddy pairing.""" + from backend.app.services.spoolbuddy_ssh import get_public_key + + try: + key = await get_public_key() + return {"public_key": key} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to get SSH key: {e}") from e @router.post("/devices/{device_id}/update-status") diff --git a/backend/app/services/spoolbuddy_ssh.py b/backend/app/services/spoolbuddy_ssh.py new file mode 100644 index 000000000..31b1c08a1 --- /dev/null +++ b/backend/app/services/spoolbuddy_ssh.py @@ -0,0 +1,233 @@ +"""SSH-based update service for SpoolBuddy devices. + +Instead of the daemon updating itself (fragile: permission issues, self-modifying +code, hardcoded branch), Bambuddy SSHes into the SpoolBuddy Pi and drives the +update remotely: git fetch/checkout, pip install, systemctl restart. +""" + +import asyncio +import logging +import os +import shutil +from pathlib import Path + +from backend.app.core.config import settings + +logger = logging.getLogger(__name__) + +SSH_USER = "spoolbuddy" +DEFAULT_INSTALL_PATH = "/opt/bambuddy" + + +def _get_ssh_key_dir() -> Path: + """Return (and create if needed) the directory for SpoolBuddy SSH keys.""" + key_dir = settings.base_dir / "spoolbuddy" / "ssh" + if not key_dir.exists(): + key_dir.mkdir(mode=0o700, parents=True) + return key_dir + + +async def get_or_create_keypair() -> tuple[Path, Path]: + """Return (private_key_path, public_key_path), generating if missing.""" + key_dir = _get_ssh_key_dir() + private_key = key_dir / "id_ed25519" + public_key = key_dir / "id_ed25519.pub" + + if private_key.exists() and public_key.exists(): + return private_key, public_key + + logger.info("Generating SSH keypair for SpoolBuddy updates") + proc = await asyncio.create_subprocess_exec( + "ssh-keygen", + "-t", + "ed25519", + "-f", + str(private_key), + "-N", + "", # no passphrase + "-C", + "bambuddy-spoolbuddy", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + _, stderr = await proc.communicate() + if proc.returncode != 0: + raise RuntimeError(f"ssh-keygen failed: {stderr.decode()[:200]}") + + private_key.chmod(0o600) + logger.info("SSH keypair generated at %s", key_dir) + return private_key, public_key + + +async def get_public_key() -> str: + """Return the SSH public key content for pairing.""" + _, public_key = await get_or_create_keypair() + return public_key.read_text().strip() + + +def detect_current_branch() -> str: + """Detect the git branch Bambuddy is running on. + + For native installs, reads from the .git directory. + For Docker (no .git), falls back to GIT_BRANCH env var, then "main". + """ + git_dir = settings.base_dir / ".git" + if git_dir.exists(): + git_path = shutil.which("git") or "/usr/bin/git" + try: + import subprocess + + result = subprocess.run( + [git_path, "rev-parse", "--abbrev-ref", "HEAD"], + cwd=str(settings.base_dir), + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0 and result.stdout.strip(): + return result.stdout.strip() + except Exception: + pass + + return os.environ.get("GIT_BRANCH", "main") + + +async def _run_ssh_command( + ip: str, + command: str, + private_key: Path, + timeout: int = 60, +) -> tuple[int, str, str]: + """Execute a command on a SpoolBuddy device via SSH. + + Returns (returncode, stdout, stderr). + """ + ssh_path = shutil.which("ssh") or "/usr/bin/ssh" + proc = await asyncio.create_subprocess_exec( + ssh_path, + "-i", + str(private_key), + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "ConnectTimeout=10", + "-o", + "BatchMode=yes", + "-o", + "LogLevel=ERROR", + f"{SSH_USER}@{ip}", + command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + try: + stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout) + except TimeoutError: + proc.kill() + await proc.communicate() + return -1, "", "SSH command timed out" + + return proc.returncode, stdout.decode(), stderr.decode() + + +async def perform_ssh_update(device_id: str, ip_address: str, install_path: str | None = None) -> None: + """SSH into a SpoolBuddy device and update it to match Bambuddy's branch. + + Updates device.update_status/update_message in the DB and broadcasts + progress via WebSocket at each step. + """ + from sqlalchemy import select + + from backend.app.api.routes.spoolbuddy import ws_manager + from backend.app.core.database import async_session + from backend.app.models.spoolbuddy_device import SpoolBuddyDevice + + install_path = install_path or DEFAULT_INSTALL_PATH + branch = detect_current_branch() + + async def _update_progress(status: str, message: str) -> None: + """Update device status in DB and broadcast via WebSocket.""" + async with async_session() as db: + result = await db.execute(select(SpoolBuddyDevice).where(SpoolBuddyDevice.device_id == device_id)) + device = result.scalar_one_or_none() + if device: + device.update_status = status + device.update_message = message[:255] if message else None + if status in ("complete", "error"): + device.pending_command = None + await db.commit() + + await ws_manager.broadcast( + { + "type": "spoolbuddy_update", + "device_id": device_id, + "update_status": status, + "update_message": message[:255] if message else None, + } + ) + + try: + private_key, _ = await get_or_create_keypair() + + # Step 1: Test SSH connectivity + await _update_progress("updating", "Connecting via SSH...") + rc, _, stderr = await _run_ssh_command(ip_address, "echo ok", private_key) + if rc != 0: + await _update_progress("error", f"SSH connection failed: {stderr[:200]}") + return + + # Step 2: Git fetch + await _update_progress("updating", f"Fetching latest code (branch: {branch})...") + rc, _, stderr = await _run_ssh_command( + ip_address, + f"cd {install_path} && git -c safe.directory={install_path} fetch origin {branch}", + private_key, + timeout=120, + ) + if rc != 0: + await _update_progress("error", f"git fetch failed: {stderr[:200]}") + return + + # Step 3: Git checkout + reset + await _update_progress("updating", "Applying update...") + rc, _, stderr = await _run_ssh_command( + ip_address, + f"cd {install_path} && git -c safe.directory={install_path} checkout {branch} " + f"&& git -c safe.directory={install_path} reset --hard origin/{branch}", + private_key, + ) + if rc != 0: + await _update_progress("error", f"git checkout/reset failed: {stderr[:200]}") + return + + # Step 4: Install dependencies + await _update_progress("updating", "Installing dependencies...") + venv_pip = f"{install_path}/spoolbuddy/venv/bin/pip" + rc, _, stderr = await _run_ssh_command( + ip_address, + f"{venv_pip} install --upgrade spidev gpiod smbus2 httpx 2>&1", + private_key, + timeout=120, + ) + if rc != 0: + logger.warning("SpoolBuddy %s: pip install returned non-zero (continuing): %s", device_id, stderr[:200]) + + # Step 5: Restart service + await _update_progress("updating", "Restarting daemon...") + rc, _, stderr = await _run_ssh_command( + ip_address, + "sudo /usr/bin/systemctl restart spoolbuddy.service", + private_key, + ) + if rc != 0: + await _update_progress("error", f"Service restart failed: {stderr[:200]}") + return + + await _update_progress("complete", "Update complete, daemon restarting...") + logger.info("SpoolBuddy %s: SSH update complete (branch=%s)", device_id, branch) + + except Exception as e: + logger.error("SpoolBuddy %s: SSH update failed: %s", device_id, e) + await _update_progress("error", f"Update failed: {str(e)[:200]}") diff --git a/backend/tests/integration/test_spoolbuddy.py b/backend/tests/integration/test_spoolbuddy.py index ce6606e81..5b046d568 100644 --- a/backend/tests/integration/test_spoolbuddy.py +++ b/backend/tests/integration/test_spoolbuddy.py @@ -813,27 +813,19 @@ class TestDisplayEndpoints: class TestUpdateEndpoints: @pytest.mark.asyncio @pytest.mark.integration - async def test_trigger_update_queues_command(self, async_client: AsyncClient, device_factory): + async def test_trigger_update_starts_ssh_update(self, async_client: AsyncClient, device_factory): await device_factory(device_id="sb-upd") - with patch("backend.app.api.routes.spoolbuddy.ws_manager") as mock_ws: + with ( + patch("backend.app.api.routes.spoolbuddy.ws_manager") as mock_ws, + patch("backend.app.services.spoolbuddy_ssh.perform_ssh_update", new_callable=AsyncMock), + ): mock_ws.broadcast = AsyncMock() resp = await async_client.post(f"{API}/devices/sb-upd/update") assert resp.status_code == 200 assert resp.json()["status"] == "ok" - # Verify heartbeat returns update command - with patch("backend.app.api.routes.spoolbuddy.ws_manager") as mock_ws: - mock_ws.broadcast = AsyncMock() - hb = await async_client.post( - f"{API}/devices/sb-upd/heartbeat", - json={"nfc_ok": True, "scale_ok": True, "uptime_s": 10}, - ) - - # update command is NOT cleared by heartbeat (cleared by update-status) - assert hb.json()["pending_command"] == "update" - @pytest.mark.asyncio @pytest.mark.integration async def test_trigger_update_offline_device_409(self, async_client: AsyncClient, device_factory): @@ -943,50 +935,25 @@ class TestUpdateEndpoints: @pytest.mark.asyncio @pytest.mark.integration async def test_update_check_returns_version_info(self, async_client: AsyncClient, device_factory): - """GET /devices/{id}/update-check queries GitHub and returns version comparison.""" + """GET /devices/{id}/update-check compares device version against APP_VERSION.""" await device_factory(device_id="sb-uc", firmware_version="0.1.0") - mock_releases = [{"tag_name": "v0.2.0", "html_url": "https://github.com/test/releases/0.2.0"}] - - mock_resp = MagicMock() - mock_resp.status_code = 200 - mock_resp.json.return_value = mock_releases - mock_resp.raise_for_status = MagicMock() - - mock_client = AsyncMock() - mock_client.get.return_value = mock_resp - mock_client.__aenter__ = AsyncMock(return_value=mock_client) - mock_client.__aexit__ = AsyncMock(return_value=False) - - with patch("httpx.AsyncClient", return_value=mock_client): - resp = await async_client.get(f"{API}/devices/sb-uc/update-check") + resp = await async_client.get(f"{API}/devices/sb-uc/update-check") assert resp.status_code == 200 data = resp.json() assert data["current_version"] == "0.1.0" - assert data["latest_version"] == "0.2.0" + assert data["latest_version"] is not None assert data["update_available"] is True - assert data["release_url"] == "https://github.com/test/releases/0.2.0" @pytest.mark.asyncio @pytest.mark.integration async def test_update_check_up_to_date(self, async_client: AsyncClient, device_factory): - await device_factory(device_id="sb-uc2", firmware_version="0.2.0") + from backend.app.core.config import APP_VERSION - mock_releases = [{"tag_name": "v0.2.0", "html_url": "https://github.com/test/releases/0.2.0"}] + await device_factory(device_id="sb-uc2", firmware_version=APP_VERSION) - mock_resp = MagicMock() - mock_resp.status_code = 200 - mock_resp.json.return_value = mock_releases - mock_resp.raise_for_status = MagicMock() - - mock_client = AsyncMock() - mock_client.get.return_value = mock_resp - mock_client.__aenter__ = AsyncMock(return_value=mock_client) - mock_client.__aexit__ = AsyncMock(return_value=False) - - with patch("httpx.AsyncClient", return_value=mock_client): - resp = await async_client.get(f"{API}/devices/sb-uc2/update-check") + resp = await async_client.get(f"{API}/devices/sb-uc2/update-check") assert resp.status_code == 200 assert resp.json()["update_available"] is False @@ -1002,7 +969,10 @@ class TestUpdateEndpoints: async def test_trigger_update_broadcasts_websocket(self, async_client: AsyncClient, device_factory): await device_factory(device_id="sb-upd-ws") - with patch("backend.app.api.routes.spoolbuddy.ws_manager") as mock_ws: + with ( + patch("backend.app.api.routes.spoolbuddy.ws_manager") as mock_ws, + patch("backend.app.services.spoolbuddy_ssh.perform_ssh_update", new_callable=AsyncMock), + ): mock_ws.broadcast = AsyncMock() await async_client.post(f"{API}/devices/sb-upd-ws/update") diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 1a5de0961..e42b36c49 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -5035,6 +5035,9 @@ export const spoolbuddyApi = { body: '{}', }), + getSSHPublicKey: () => + request<{ public_key: string }>('/spoolbuddy/ssh/public-key'), + writeTag: (deviceId: string, spoolId: number) => request<{ status: string }>('/spoolbuddy/nfc/write-tag', { method: 'POST', diff --git a/frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx b/frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx index 0ee98cb13..abff2c8a3 100644 --- a/frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx +++ b/frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx @@ -504,6 +504,8 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) { const { t } = useTranslation(); const [applying, setApplying] = useState(false); const [error, setError] = useState(null); + const [sshExpanded, setSSHExpanded] = useState(false); + const [copied, setCopied] = useState(false); const isUpdating = device.update_status === 'pending' || device.update_status === 'updating'; @@ -513,6 +515,13 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) { staleTime: 4 * 60 * 1000, }); + const { data: sshKeyData } = useQuery({ + queryKey: ['spoolbuddy-ssh-key'], + queryFn: () => spoolbuddyApi.getSSHPublicKey(), + enabled: sshExpanded, + staleTime: Infinity, + }); + const applyUpdate = async () => { setApplying(true); setError(null); @@ -525,6 +534,14 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) { } }; + const copyKey = () => { + if (sshKeyData?.public_key) { + navigator.clipboard.writeText(sshKeyData.public_key); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + const displayVersion = device.firmware_version || (updateResult?.current_version && updateResult.current_version !== '0.0.0' ? updateResult.current_version : null); @@ -641,17 +658,72 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) { ) : ( -
- - - -

{t('spoolbuddy.settings.upToDate', 'Up to date')}

+
+
+ + + +

{t('spoolbuddy.settings.upToDate', 'Up to date')}

+
+ {/* Force update button — re-deploy even when versions match */} +
)}
)} + + {/* SSH Setup */} +
+ + + {sshExpanded && ( +
+

+ {t('spoolbuddy.settings.sshDescription', 'Add this public key to your SpoolBuddy device to enable remote updates. Run the install script with --ssh-pubkey or add it to ~/.ssh/authorized_keys on the device.')} +

+ {sshKeyData?.public_key ? ( +
+
+                  {sshKeyData.public_key}
+                
+ +
+ ) : ( +
+ {t('spoolbuddy.settings.sshKeyLoading', 'Loading SSH key...')} +
+ )} +
+ )} +
); } diff --git a/spoolbuddy/daemon/main.py b/spoolbuddy/daemon/main.py index 5a5988e6f..1b327b92e 100644 --- a/spoolbuddy/daemon/main.py +++ b/spoolbuddy/daemon/main.py @@ -3,7 +3,6 @@ import asyncio import logging -import shutil import socket import sys import time @@ -123,93 +122,6 @@ async def scale_poll_loop(config: Config, api: APIClient, shared: dict): scale.close() -async def _perform_update(config: Config, api: APIClient): - """Pull latest code from git, install deps, then exit for systemd restart.""" - # Determine repo root (install path) — daemon runs from /spoolbuddy/ - repo_root = Path(__file__).resolve().parent.parent.parent - - await api.report_update_status(config.device_id, "updating", "Fetching latest code...") - - git_path = shutil.which("git") or "/usr/bin/git" - git_config = ["-c", f"safe.directory={repo_root}"] - - # git fetch origin main - proc = await asyncio.create_subprocess_exec( - git_path, - *git_config, - "fetch", - "origin", - "main", - cwd=str(repo_root), - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - _, stderr = await proc.communicate() - if proc.returncode != 0: - msg = f"git fetch failed: {stderr.decode()[:200]}" - logger.error(msg) - await api.report_update_status(config.device_id, "error", msg) - return - - await api.report_update_status(config.device_id, "updating", "Applying update...") - - # git reset --hard origin/main - proc = await asyncio.create_subprocess_exec( - git_path, - *git_config, - "reset", - "--hard", - "origin/main", - cwd=str(repo_root), - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - _, stderr = await proc.communicate() - if proc.returncode != 0: - msg = f"git reset failed: {stderr.decode()[:200]}" - logger.error(msg) - await api.report_update_status(config.device_id, "error", msg) - return - - await api.report_update_status(config.device_id, "updating", "Installing dependencies...") - - # pip install daemon deps (use the venv pip) - venv_pip = repo_root / "spoolbuddy" / "venv" / "bin" / "pip" - pip_packages = ["spidev", "gpiod", "smbus2", "httpx"] - - if venv_pip.exists(): - proc = await asyncio.create_subprocess_exec( - str(venv_pip), - "install", - "--upgrade", - *pip_packages, - cwd=str(repo_root), - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - else: - proc = await asyncio.create_subprocess_exec( - sys.executable, - "-m", - "pip", - "install", - "--upgrade", - *pip_packages, - cwd=str(repo_root), - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - await proc.communicate() - if proc.returncode != 0: - logger.warning("pip install returned non-zero (continuing anyway)") - - await api.report_update_status(config.device_id, "complete", "Update complete, restarting...") - logger.info("Update complete, exiting for systemd restart") - - # Exit cleanly — systemd Restart=always will bring us back with the new code - sys.exit(0) - - async def heartbeat_loop(config: Config, api: APIClient, start_time: float, shared: dict): """Periodic heartbeat to keep device registered and pick up commands.""" display: DisplayControl = shared["display"] @@ -234,15 +146,7 @@ async def heartbeat_loop(config: Config, api: APIClient, start_time: float, shar if result: cmd = result.get("pending_command") - if cmd == "update": - logger.info("Update command received, starting update...") - try: - await _perform_update(config, api) - except Exception as e: - logger.error("Update failed: %s", e) - await api.report_update_status(config.device_id, "error", str(e)[:255]) - continue - elif cmd == "tare": + if cmd == "tare": scale = shared.get("scale") if scale and scale.ok: new_offset = await asyncio.to_thread(scale.tare) diff --git a/spoolbuddy/install/install.sh b/spoolbuddy/install/install.sh index c6291ed74..cd0c435d7 100755 --- a/spoolbuddy/install/install.sh +++ b/spoolbuddy/install/install.sh @@ -16,6 +16,7 @@ # --api-key KEY Bambuddy API key (required for spoolbuddy mode) # --path PATH Installation directory (default: /opt/spoolbuddy or /opt/bambuddy) # --port PORT Bambuddy port (full mode only, default: 8000) +# --ssh-pubkey KEY Bambuddy SSH public key for remote updates # --yes, -y Non-interactive mode, accept defaults # --help, -h Show this help message # @@ -56,6 +57,7 @@ NON_INTERACTIVE="false" REBOOT_NEEDED="false" KIOSK_USER="" # auto-detected from $SUDO_USER KIOSK_URL="" # derived from $BAMBUDDY_URL/spoolbuddy?token=$API_KEY +SSH_PUBKEY="" # Bambuddy's SSH public key for remote updates # ───────────────────────────────────────────────────────────────────────────── # Helpers @@ -190,6 +192,7 @@ show_help() { echo " --api-key KEY Bambuddy API key (required for spoolbuddy mode)" echo " --path PATH Installation directory (default: /opt/spoolbuddy or /opt/bambuddy)" echo " --port PORT Bambuddy port (full mode only, default: 8000)" + echo " --ssh-pubkey KEY Bambuddy SSH public key for remote updates" echo " --yes, -y Non-interactive mode, accept defaults" echo " --help, -h Show this help message" echo "" @@ -354,9 +357,11 @@ install_system_packages() { create_spoolbuddy_user() { if id "$SPOOLBUDDY_SERVICE_USER" &>/dev/null; then info "User '$SPOOLBUDDY_SERVICE_USER' already exists" + # Ensure existing installs get a real shell for SSH access + usermod --shell /bin/bash "$SPOOLBUDDY_SERVICE_USER" 2>/dev/null || true else info "Creating service user '$SPOOLBUDDY_SERVICE_USER'..." - useradd --system --shell /usr/sbin/nologin --home-dir "$INSTALL_PATH" "$SPOOLBUDDY_SERVICE_USER" + useradd --system --shell /bin/bash --home-dir "$INSTALL_PATH" "$SPOOLBUDDY_SERVICE_USER" success "Service user created" fi @@ -367,6 +372,12 @@ create_spoolbuddy_user() { fi done success "User added to gpio, spi, i2c, video groups" + + # Allow passwordless restart of the daemon (needed for SSH-based updates from Bambuddy) + echo "$SPOOLBUDDY_SERVICE_USER ALL=(root) NOPASSWD: /usr/bin/systemctl restart spoolbuddy.service" \ + > /etc/sudoers.d/spoolbuddy + chmod 440 /etc/sudoers.d/spoolbuddy + success "Sudoers entry created for service restart" } download_spoolbuddy() { @@ -376,13 +387,9 @@ download_spoolbuddy() { cd "$INSTALL_PATH" run_with_progress "Fetching updates" git fetch origin git reset --hard origin/main > /dev/null 2>&1 - cd "$INSTALL_PATH" - git checkout 0.2.2b1 else mkdir -p "$INSTALL_PATH" run_with_progress "Cloning repository" git clone "$GITHUB_REPO" "$INSTALL_PATH" - cd "$INSTALL_PATH" - git checkout 0.2.2b1 fi chown -R "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$INSTALL_PATH" @@ -419,6 +426,50 @@ EOF success "Configuration saved to $env_file" } +setup_ssh_key() { + info "Setting up SSH access for Bambuddy remote updates..." + + local ssh_dir="$INSTALL_PATH/.ssh" + local auth_keys="$ssh_dir/authorized_keys" + + mkdir -p "$ssh_dir" + chmod 700 "$ssh_dir" + + # Get the public key from flag, prompt, or skip + local pubkey="$SSH_PUBKEY" + + if [[ -z "$pubkey" && "$NON_INTERACTIVE" != "true" ]]; then + echo "" + echo -e "${BOLD}SSH Public Key for Remote Updates${NC}" + echo "" + echo " Bambuddy can update SpoolBuddy remotely over SSH." + echo " To enable this, paste the SSH public key from:" + echo " Bambuddy → SpoolBuddy Settings → SSH Setup" + echo "" + echo " (You can also set this up later by re-running the installer" + echo " with --ssh-pubkey or by manually adding the key)" + echo "" + read -rp " Paste SSH public key (or press Enter to skip): " pubkey + fi + + if [[ -n "$pubkey" ]]; then + # Append key if not already present + if [[ -f "$auth_keys" ]] && grep -qF "$pubkey" "$auth_keys" 2>/dev/null; then + info "SSH key already present in authorized_keys" + else + echo "$pubkey" >> "$auth_keys" + success "SSH public key added" + fi + else + info "No SSH key provided, skipping (can be added later)" + # Ensure the file exists even if empty + touch "$auth_keys" + fi + + chmod 600 "$auth_keys" + chown -R "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$ssh_dir" +} + create_spoolbuddy_service() { info "Creating SpoolBuddy systemd service..." @@ -871,6 +922,10 @@ parse_args() { BAMBUDDY_PORT="$2" shift 2 ;; + --ssh-pubkey) + SSH_PUBKEY="$2" + shift 2 + ;; --yes|-y) NON_INTERACTIVE="true" shift @@ -1058,6 +1113,7 @@ main() { info "Setting up SpoolBuddy..." setup_spoolbuddy_venv create_spoolbuddy_env + setup_ssh_key create_spoolbuddy_service echo "" diff --git a/static/assets/index-Dn6SXRdj.js b/static/assets/index-ABN02nc3.js similarity index 95% rename from static/assets/index-Dn6SXRdj.js rename to static/assets/index-ABN02nc3.js index 72390166f..78f14eb38 100644 --- a/static/assets/index-Dn6SXRdj.js +++ b/static/assets/index-ABN02nc3.js @@ -5,7 +5,7 @@ function jce(t,e){for(var n=0;nB||tt[M]!==St[B]){var Mt=` `+tt[M].replace(" at new "," at ");return h.displayName&&Mt.includes("")&&(Mt=Mt.replace("",h.displayName)),Mt}while(1<=M&&0<=B);break}}}finally{$=!1,Error.prepareStackTrace=S}return(S=h?h.displayName||h.name:"")?O(S):""}function W(h,x){switch(h.tag){case 26:case 27:case 5:return O(h.type);case 16:return O("Lazy");case 13:return h.child!==x&&x!==null?O("Suspense Fallback"):O("Suspense");case 19:return O("SuspenseList");case 0:case 15:return ee(h.type,!1);case 11:return ee(h.type.render,!1);case 1:return ee(h.type,!0);case 31:return O("Activity");default:return""}}function ie(h){try{var x="",S=null;do x+=W(h,S),S=h,h=h.return;while(h);return x}catch(M){return` Error generating stack: `+M.message+` -`+M.stack}}var ce=Object.prototype.hasOwnProperty,be=t.unstable_scheduleCallback,xe=t.unstable_cancelCallback,oe=t.unstable_shouldYield,L=t.unstable_requestPaint,F=t.unstable_now,te=t.unstable_getCurrentPriorityLevel,ve=t.unstable_ImmediatePriority,fe=t.unstable_UserBlockingPriority,me=t.unstable_NormalPriority,je=t.unstable_LowPriority,we=t.unstable_IdlePriority,Fe=t.log,He=t.unstable_setDisableYieldValue,Oe=null,_e=null;function Ae(h){if(typeof Fe=="function"&&He(h),_e&&typeof _e.setStrictMode=="function")try{_e.setStrictMode(Oe,h)}catch{}}var Be=Math.clz32?Math.clz32:ye,Me=Math.log,Xe=Math.LN2;function ye(h){return h>>>=0,h===0?32:31-(Me(h)/Xe|0)|0}var We=256,Pe=262144,Ke=4194304;function Ve(h){var x=h&42;if(x!==0)return x;switch(h&-h){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return h&261888;case 262144:case 524288:case 1048576:case 2097152:return h&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return h&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return h}}function ke(h,x,S){var M=h.pendingLanes;if(M===0)return 0;var B=0,Q=h.suspendedLanes,Se=h.pingedLanes;h=h.warmLanes;var Ue=M&134217727;return Ue!==0?(M=Ue&~Q,M!==0?B=Ve(M):(Se&=Ue,Se!==0?B=Ve(Se):S||(S=Ue&~h,S!==0&&(B=Ve(S))))):(Ue=M&~Q,Ue!==0?B=Ve(Ue):Se!==0?B=Ve(Se):S||(S=M&~h,S!==0&&(B=Ve(S)))),B===0?0:x!==0&&x!==B&&(x&Q)===0&&(Q=B&-B,S=x&-x,Q>=S||Q===32&&(S&4194048)!==0)?x:B}function Ce(h,x){return(h.pendingLanes&~(h.suspendedLanes&~h.pingedLanes)&x)===0}function $e(h,x){switch(h){case 1:case 2:case 4:case 8:case 64:return x+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return x+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function nt(){var h=Ke;return Ke<<=1,(Ke&62914560)===0&&(Ke=4194304),h}function xt(h){for(var x=[],S=0;31>S;S++)x.push(h);return x}function vt(h,x){h.pendingLanes|=x,x!==268435456&&(h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0)}function mt(h,x,S,M,B,Q){var Se=h.pendingLanes;h.pendingLanes=S,h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0,h.expiredLanes&=S,h.entangledLanes&=S,h.errorRecoveryDisabledLanes&=S,h.shellSuspendCounter=0;var Ue=h.entanglements,tt=h.expirationTimes,St=h.hiddenUpdates;for(S=Se&~S;0"u")return null;try{return h.activeElement||h.body}catch{return h.body}}var Gr=/[\n"\\]/g;function lr(h){return h.replace(Gr,function(x){return"\\"+x.charCodeAt(0).toString(16)+" "})}function zi(h,x,S,M,B,Q,Se,Ue){h.name="",Se!=null&&typeof Se!="function"&&typeof Se!="symbol"&&typeof Se!="boolean"?h.type=Se:h.removeAttribute("type"),x!=null?Se==="number"?(x===0&&h.value===""||h.value!=x)&&(h.value=""+wn(x)):h.value!==""+wn(x)&&(h.value=""+wn(x)):Se!=="submit"&&Se!=="reset"||h.removeAttribute("value"),x!=null?Ni(h,Se,wn(x)):S!=null?Ni(h,Se,wn(S)):M!=null&&h.removeAttribute("value"),B==null&&Q!=null&&(h.defaultChecked=!!Q),B!=null&&(h.checked=B&&typeof B!="function"&&typeof B!="symbol"),Ue!=null&&typeof Ue!="function"&&typeof Ue!="symbol"&&typeof Ue!="boolean"?h.name=""+wn(Ue):h.removeAttribute("name")}function fn(h,x,S,M,B,Q,Se,Ue){if(Q!=null&&typeof Q!="function"&&typeof Q!="symbol"&&typeof Q!="boolean"&&(h.type=Q),x!=null||S!=null){if(!(Q!=="submit"&&Q!=="reset"||x!=null)){Sn(h);return}S=S!=null?""+wn(S):"",x=x!=null?""+wn(x):S,Ue||x===h.value||(h.value=x),h.defaultValue=x}M=M??B,M=typeof M!="function"&&typeof M!="symbol"&&!!M,h.checked=Ue?h.checked:!!M,h.defaultChecked=!!M,Se!=null&&typeof Se!="function"&&typeof Se!="symbol"&&typeof Se!="boolean"&&(h.name=Se),Sn(h)}function Ni(h,x,S){x==="number"&&ei(h.ownerDocument)===h||h.defaultValue===""+S||(h.defaultValue=""+S)}function ui(h,x,S,M){if(h=h.options,x){x={};for(var B=0;B"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ps=!1;if(As)try{var io={};Object.defineProperty(io,"passive",{get:function(){Ps=!0}}),window.addEventListener("test",io,io),window.removeEventListener("test",io,io)}catch{Ps=!1}var Na=null,cl=null,Ms=null;function Ic(){if(Ms)return Ms;var h,x=cl,S=x.length,M,B="value"in Na?Na.value:Na.textContent,Q=B.length;for(h=0;h=Pr),Cn=" ",BA=!1;function hy(h,x){switch(h){case"keyup":return Do.indexOf(x.keyCode)!==-1;case"keydown":return x.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function op(h){return h=h.detail,typeof h=="object"&&"data"in h?h.data:null}var Lu=!1;function iS(h,x){switch(h){case"compositionend":return op(x);case"keypress":return x.which!==32?null:(BA=!0,Cn);case"textInput":return h=x.data,h===Cn&&BA?null:h;default:return null}}function HA(h,x){if(Lu)return h==="compositionend"||!sp&&hy(h,x)?(h=Ic(),Ms=cl=Na=null,Lu=!1,h):null;switch(h){case"paste":return null;case"keypress":if(!(x.ctrlKey||x.altKey||x.metaKey)||x.ctrlKey&&x.altKey){if(x.char&&1=x)return{node:S,offset:x-h};h=M}e:{for(;S;){if(S.nextSibling){S=S.nextSibling;break e}S=S.parentNode}S=void 0}S=U5(S)}}function H5(h,x){return h&&x?h===x?!0:h&&h.nodeType===3?!1:x&&x.nodeType===3?H5(h,x.parentNode):"contains"in h?h.contains(x):h.compareDocumentPosition?!!(h.compareDocumentPosition(x)&16):!1:!1}function $5(h){h=h!=null&&h.ownerDocument!=null&&h.ownerDocument.defaultView!=null?h.ownerDocument.defaultView:window;for(var x=ei(h.document);x instanceof h.HTMLIFrameElement;){try{var S=typeof x.contentWindow.location.href=="string"}catch{S=!1}if(S)h=x.contentWindow;else break;x=ei(h.document)}return x}function VA(h){var x=h&&h.nodeName&&h.nodeName.toLowerCase();return x&&(x==="input"&&(h.type==="text"||h.type==="search"||h.type==="tel"||h.type==="url"||h.type==="password")||x==="textarea"||h.contentEditable==="true")}var dle=As&&"documentMode"in document&&11>=document.documentMode,ug=null,GA=null,by=null,WA=!1;function q5(h,x,S){var M=S.window===S?S.document:S.nodeType===9?S:S.ownerDocument;WA||ug==null||ug!==ei(M)||(M=ug,"selectionStart"in M&&VA(M)?M={start:M.selectionStart,end:M.selectionEnd}:(M=(M.ownerDocument&&M.ownerDocument.defaultView||window).getSelection(),M={anchorNode:M.anchorNode,anchorOffset:M.anchorOffset,focusNode:M.focusNode,focusOffset:M.focusOffset}),by&&gy(by,M)||(by=M,M=WS(GA,"onSelect"),0>=Se,B-=Se,Bc=1<<32-Be(x)+B|S<$n?(or=ln,ln=null):or=ln.sibling;var xr=kt(bt,ln,wt[$n],jt);if(xr===null){ln===null&&(ln=or);break}h&&ln&&xr.alternate===null&&x(bt,ln),it=Q(xr,it,$n),br===null?gn=xr:br.sibling=xr,br=xr,ln=or}if($n===wt.length)return S(bt,ln),cr&&Iu(bt,$n),gn;if(ln===null){for(;$n$n?(or=ln,ln=null):or=ln.sibling;var jm=kt(bt,ln,xr.value,jt);if(jm===null){ln===null&&(ln=or);break}h&&ln&&jm.alternate===null&&x(bt,ln),it=Q(jm,it,$n),br===null?gn=jm:br.sibling=jm,br=jm,ln=or}if(xr.done)return S(bt,ln),cr&&Iu(bt,$n),gn;if(ln===null){for(;!xr.done;$n++,xr=wt.next())xr=Lt(bt,xr.value,jt),xr!==null&&(it=Q(xr,it,$n),br===null?gn=xr:br.sibling=xr,br=xr);return cr&&Iu(bt,$n),gn}for(ln=M(ln);!xr.done;$n++,xr=wt.next())xr=At(ln,bt,$n,xr.value,jt),xr!==null&&(h&&xr.alternate!==null&&ln.delete(xr.key===null?$n:xr.key),it=Q(xr,it,$n),br===null?gn=xr:br.sibling=xr,br=xr);return h&&ln.forEach(function(Ece){return x(bt,Ece)}),cr&&Iu(bt,$n),gn}function Ir(bt,it,wt,jt){if(typeof wt=="object"&&wt!==null&&wt.type===v&&wt.key===null&&(wt=wt.props.children),typeof wt=="object"&&wt!==null){switch(wt.$$typeof){case f:e:{for(var gn=wt.key;it!==null;){if(it.key===gn){if(gn=wt.type,gn===v){if(it.tag===7){S(bt,it.sibling),jt=B(it,wt.props.children),jt.return=bt,bt=jt;break e}}else if(it.elementType===gn||typeof gn=="object"&&gn!==null&&gn.$$typeof===D&&gp(gn)===it.type){S(bt,it.sibling),jt=B(it,wt.props),_y(jt,wt),jt.return=bt,bt=jt;break e}S(bt,it);break}else x(bt,it);it=it.sibling}wt.type===v?(jt=dp(wt.props.children,bt.mode,jt,wt.key),jt.return=bt,bt=jt):(jt=uS(wt.type,wt.key,wt.props,null,bt.mode,jt),_y(jt,wt),jt.return=bt,bt=jt)}return Se(bt);case y:e:{for(gn=wt.key;it!==null;){if(it.key===gn)if(it.tag===4&&it.stateNode.containerInfo===wt.containerInfo&&it.stateNode.implementation===wt.implementation){S(bt,it.sibling),jt=B(it,wt.children||[]),jt.return=bt,bt=jt;break e}else{S(bt,it);break}else x(bt,it);it=it.sibling}jt=eP(wt,bt.mode,jt),jt.return=bt,bt=jt}return Se(bt);case D:return wt=gp(wt),Ir(bt,it,wt,jt)}if(J(wt))return an(bt,it,wt,jt);if(z(wt)){if(gn=z(wt),typeof gn!="function")throw Error(r(150));return wt=gn.call(wt),_n(bt,it,wt,jt)}if(typeof wt.then=="function")return Ir(bt,it,bS(wt),jt);if(wt.$$typeof===N)return Ir(bt,it,hS(bt,wt),jt);xS(bt,wt)}return typeof wt=="string"&&wt!==""||typeof wt=="number"||typeof wt=="bigint"?(wt=""+wt,it!==null&&it.tag===6?(S(bt,it.sibling),jt=B(it,wt),jt.return=bt,bt=jt):(S(bt,it),jt=JA(wt,bt.mode,jt),jt.return=bt,bt=jt),Se(bt)):S(bt,it)}return function(bt,it,wt,jt){try{Sy=0;var gn=Ir(bt,it,wt,jt);return wg=null,gn}catch(ln){if(ln===vg||ln===fS)throw ln;var br=Fo(29,ln,null,bt.mode);return br.lanes=jt,br.return=bt,br}}}var xp=hz(!0),pz=hz(!1),fm=!1;function mP(h){h.updateQueue={baseState:h.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function hP(h,x){h=h.updateQueue,x.updateQueue===h&&(x.updateQueue={baseState:h.baseState,firstBaseUpdate:h.firstBaseUpdate,lastBaseUpdate:h.lastBaseUpdate,shared:h.shared,callbacks:null})}function gm(h){return{lane:h,tag:0,payload:null,callback:null,next:null}}function bm(h,x,S){var M=h.updateQueue;if(M===null)return null;if(M=M.shared,(wr&2)!==0){var B=M.pending;return B===null?x.next=x:(x.next=B.next,B.next=x),M.pending=x,x=cS(h),Q5(h,null,S),x}return lS(h,M,x,S),cS(h)}function Ny(h,x,S){if(x=x.updateQueue,x!==null&&(x=x.shared,(S&4194048)!==0)){var M=x.lanes;M&=h.pendingLanes,S|=M,x.lanes=S,Nt(h,S)}}function pP(h,x){var S=h.updateQueue,M=h.alternate;if(M!==null&&(M=M.updateQueue,S===M)){var B=null,Q=null;if(S=S.firstBaseUpdate,S!==null){do{var Se={lane:S.lane,tag:S.tag,payload:S.payload,callback:null,next:null};Q===null?B=Q=Se:Q=Q.next=Se,S=S.next}while(S!==null);Q===null?B=Q=x:Q=Q.next=x}else B=Q=x;S={baseState:M.baseState,firstBaseUpdate:B,lastBaseUpdate:Q,shared:M.shared,callbacks:M.callbacks},h.updateQueue=S;return}h=S.lastBaseUpdate,h===null?S.firstBaseUpdate=x:h.next=x,S.lastBaseUpdate=x}var fP=!1;function ky(){if(fP){var h=yg;if(h!==null)throw h}}function Cy(h,x,S,M){fP=!1;var B=h.updateQueue;fm=!1;var Q=B.firstBaseUpdate,Se=B.lastBaseUpdate,Ue=B.shared.pending;if(Ue!==null){B.shared.pending=null;var tt=Ue,St=tt.next;tt.next=null,Se===null?Q=St:Se.next=St,Se=tt;var Mt=h.alternate;Mt!==null&&(Mt=Mt.updateQueue,Ue=Mt.lastBaseUpdate,Ue!==Se&&(Ue===null?Mt.firstBaseUpdate=St:Ue.next=St,Mt.lastBaseUpdate=tt))}if(Q!==null){var Lt=B.baseState;Se=0,Mt=St=tt=null,Ue=Q;do{var kt=Ue.lane&-536870913,At=kt!==Ue.lane;if(At?(sr&kt)===kt:(M&kt)===kt){kt!==0&&kt===xg&&(fP=!0),Mt!==null&&(Mt=Mt.next={lane:0,tag:Ue.tag,payload:Ue.payload,callback:null,next:null});e:{var an=h,_n=Ue;kt=x;var Ir=S;switch(_n.tag){case 1:if(an=_n.payload,typeof an=="function"){Lt=an.call(Ir,Lt,kt);break e}Lt=an;break e;case 3:an.flags=an.flags&-65537|128;case 0:if(an=_n.payload,kt=typeof an=="function"?an.call(Ir,Lt,kt):an,kt==null)break e;Lt=m({},Lt,kt);break e;case 2:fm=!0}}kt=Ue.callback,kt!==null&&(h.flags|=64,At&&(h.flags|=8192),At=B.callbacks,At===null?B.callbacks=[kt]:At.push(kt))}else At={lane:kt,tag:Ue.tag,payload:Ue.payload,callback:Ue.callback,next:null},Mt===null?(St=Mt=At,tt=Lt):Mt=Mt.next=At,Se|=kt;if(Ue=Ue.next,Ue===null){if(Ue=B.shared.pending,Ue===null)break;At=Ue,Ue=At.next,At.next=null,B.lastBaseUpdate=At,B.shared.pending=null}}while(!0);Mt===null&&(tt=Lt),B.baseState=tt,B.firstBaseUpdate=St,B.lastBaseUpdate=Mt,Q===null&&(B.shared.lanes=0),Sm|=Se,h.lanes=Se,h.memoizedState=Lt}}function fz(h,x){if(typeof h!="function")throw Error(r(191,h));h.call(x)}function gz(h,x){var S=h.callbacks;if(S!==null)for(h.callbacks=null,h=0;hQ?Q:8;var Se=ae.T,Ue={};ae.T=Ue,RP(h,!1,x,S);try{var tt=B(),St=ae.S;if(St!==null&&St(Ue,tt),tt!==null&&typeof tt=="object"&&typeof tt.then=="function"){var Mt=vle(tt,M);Py(h,x,Mt,Uo(h))}else Py(h,x,M,Uo(h))}catch(Lt){Py(h,x,{then:function(){},status:"rejected",reason:Lt},Uo())}finally{Y.p=Q,Se!==null&&Ue.types!==null&&(Se.types=Ue.types),ae.T=Se}}function Cle(){}function jP(h,x,S,M){if(h.tag!==5)throw Error(r(476));var B=Kz(h).queue;Wz(h,B,x,ue,S===null?Cle:function(){return Yz(h),S(M)})}function Kz(h){var x=h.memoizedState;if(x!==null)return x;x={memoizedState:ue,baseState:ue,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Hu,lastRenderedState:ue},next:null};var S={};return x.next={memoizedState:S,baseState:S,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Hu,lastRenderedState:S},next:null},h.memoizedState=x,h=h.alternate,h!==null&&(h.memoizedState=x),x}function Yz(h){var x=Kz(h);x.next===null&&(x=h.alternate.memoizedState),Py(h,x.next.queue,{},Uo())}function DP(){return Va(Gy)}function Xz(){return $i().memoizedState}function Qz(){return $i().memoizedState}function Tle(h){for(var x=h.return;x!==null;){switch(x.tag){case 24:case 3:var S=Uo();h=gm(S);var M=bm(x,h,S);M!==null&&(mo(M,x,S),Ny(M,x,S)),x={cache:lP()},h.payload=x;return}x=x.return}}function Ale(h,x,S){var M=Uo();S={lane:M,revertLane:0,gesture:null,action:S,hasEagerState:!1,eagerState:null,next:null},AS(h)?Jz(x,S):(S=QA(h,x,S,M),S!==null&&(mo(S,h,M),eU(S,x,M)))}function Zz(h,x,S){var M=Uo();Py(h,x,S,M)}function Py(h,x,S,M){var B={lane:M,revertLane:0,gesture:null,action:S,hasEagerState:!1,eagerState:null,next:null};if(AS(h))Jz(x,B);else{var Q=h.alternate;if(h.lanes===0&&(Q===null||Q.lanes===0)&&(Q=x.lastRenderedReducer,Q!==null))try{var Se=x.lastRenderedState,Ue=Q(Se,S);if(B.hasEagerState=!0,B.eagerState=Ue,Ro(Ue,Se))return lS(h,x,B,0),Wr===null&&oS(),!1}catch{}if(S=QA(h,x,B,M),S!==null)return mo(S,h,M),eU(S,x,M),!0}return!1}function RP(h,x,S,M){if(M={lane:2,revertLane:mM(),gesture:null,action:M,hasEagerState:!1,eagerState:null,next:null},AS(h)){if(x)throw Error(r(479))}else x=QA(h,S,M,2),x!==null&&mo(x,h,2)}function AS(h){var x=h.alternate;return h===Un||x!==null&&x===Un}function Jz(h,x){_g=wS=!0;var S=h.pending;S===null?x.next=x:(x.next=S.next,S.next=x),h.pending=x}function eU(h,x,S){if((S&4194048)!==0){var M=x.lanes;M&=h.pendingLanes,S|=M,x.lanes=S,Nt(h,S)}}var My={readContext:Va,use:NS,useCallback:ki,useContext:ki,useEffect:ki,useImperativeHandle:ki,useLayoutEffect:ki,useInsertionEffect:ki,useMemo:ki,useReducer:ki,useRef:ki,useState:ki,useDebugValue:ki,useDeferredValue:ki,useTransition:ki,useSyncExternalStore:ki,useId:ki,useHostTransitionStatus:ki,useFormState:ki,useActionState:ki,useOptimistic:ki,useMemoCache:ki,useCacheRefresh:ki};My.useEffectEvent=ki;var tU={readContext:Va,use:NS,useCallback:function(h,x){return Rs().memoizedState=[h,x===void 0?null:x],h},useContext:Va,useEffect:Iz,useImperativeHandle:function(h,x,S){S=S!=null?S.concat([h]):null,CS(4194308,4,Hz.bind(null,x,h),S)},useLayoutEffect:function(h,x){return CS(4194308,4,h,x)},useInsertionEffect:function(h,x){CS(4,2,h,x)},useMemo:function(h,x){var S=Rs();x=x===void 0?null:x;var M=h();if(yp){Ae(!0);try{h()}finally{Ae(!1)}}return S.memoizedState=[M,x],M},useReducer:function(h,x,S){var M=Rs();if(S!==void 0){var B=S(x);if(yp){Ae(!0);try{S(x)}finally{Ae(!1)}}}else B=x;return M.memoizedState=M.baseState=B,h={pending:null,lanes:0,dispatch:null,lastRenderedReducer:h,lastRenderedState:B},M.queue=h,h=h.dispatch=Ale.bind(null,Un,h),[M.memoizedState,h]},useRef:function(h){var x=Rs();return h={current:h},x.memoizedState=h},useState:function(h){h=TP(h);var x=h.queue,S=Zz.bind(null,Un,x);return x.dispatch=S,[h.memoizedState,S]},useDebugValue:MP,useDeferredValue:function(h,x){var S=Rs();return EP(S,h,x)},useTransition:function(){var h=TP(!1);return h=Wz.bind(null,Un,h.queue,!0,!1),Rs().memoizedState=h,[!1,h]},useSyncExternalStore:function(h,x,S){var M=Un,B=Rs();if(cr){if(S===void 0)throw Error(r(407));S=S()}else{if(S=x(),Wr===null)throw Error(r(349));(sr&127)!==0||Sz(M,x,S)}B.memoizedState=S;var Q={value:S,getSnapshot:x};return B.queue=Q,Iz(Nz.bind(null,M,Q,h),[h]),M.flags|=2048,kg(9,{destroy:void 0},_z.bind(null,M,Q,S,x),null),S},useId:function(){var h=Rs(),x=Wr.identifierPrefix;if(cr){var S=Hc,M=Bc;S=(M&~(1<<32-Be(M)-1)).toString(32)+S,x="_"+x+"R_"+S,S=SS++,0<\/script>",Q=Q.removeChild(Q.firstChild);break;case"select":Q=typeof M.is=="string"?Se.createElement("select",{is:M.is}):Se.createElement("select"),M.multiple?Q.multiple=!0:M.size&&(Q.size=M.size);break;default:Q=typeof M.is=="string"?Se.createElement(B,{is:M.is}):Se.createElement(B)}}Q[mn]=x,Q[hn]=M;e:for(Se=x.child;Se!==null;){if(Se.tag===5||Se.tag===6)Q.appendChild(Se.stateNode);else if(Se.tag!==4&&Se.tag!==27&&Se.child!==null){Se.child.return=Se,Se=Se.child;continue}if(Se===x)break e;for(;Se.sibling===null;){if(Se.return===null||Se.return===x)break e;Se=Se.return}Se.sibling.return=Se.return,Se=Se.sibling}x.stateNode=Q;e:switch(Wa(Q,B,M),B){case"button":case"input":case"select":case"textarea":M=!!M.autoFocus;break e;case"img":M=!0;break e;default:M=!1}M&&qu(x)}}return si(x),KP(x,x.type,h===null?null:h.memoizedProps,x.pendingProps,S),null;case 6:if(h&&x.stateNode!=null)h.memoizedProps!==M&&qu(x);else{if(typeof M!="string"&&x.stateNode===null)throw Error(r(166));if(h=q.current,gg(x)){if(h=x.stateNode,S=x.memoizedProps,M=null,B=qa,B!==null)switch(B.tag){case 27:case 5:M=B.memoizedProps}h[mn]=x,h=!!(h.nodeValue===S||M!==null&&M.suppressHydrationWarning===!0||vB(h.nodeValue,S)),h||hm(x,!0)}else h=KS(h).createTextNode(M),h[mn]=x,x.stateNode=h}return si(x),null;case 31:if(S=x.memoizedState,h===null||h.memoizedState!==null){if(M=gg(x),S!==null){if(h===null){if(!M)throw Error(r(318));if(h=x.memoizedState,h=h!==null?h.dehydrated:null,!h)throw Error(r(557));h[mn]=x}else mp(),(x.flags&128)===0&&(x.memoizedState=null),x.flags|=4;si(x),h=!1}else S=iP(),h!==null&&h.memoizedState!==null&&(h.memoizedState.hydrationErrors=S),h=!0;if(!h)return x.flags&256?(Oo(x),x):(Oo(x),null);if((x.flags&128)!==0)throw Error(r(558))}return si(x),null;case 13:if(M=x.memoizedState,h===null||h.memoizedState!==null&&h.memoizedState.dehydrated!==null){if(B=gg(x),M!==null&&M.dehydrated!==null){if(h===null){if(!B)throw Error(r(318));if(B=x.memoizedState,B=B!==null?B.dehydrated:null,!B)throw Error(r(317));B[mn]=x}else mp(),(x.flags&128)===0&&(x.memoizedState=null),x.flags|=4;si(x),B=!1}else B=iP(),h!==null&&h.memoizedState!==null&&(h.memoizedState.hydrationErrors=B),B=!0;if(!B)return x.flags&256?(Oo(x),x):(Oo(x),null)}return Oo(x),(x.flags&128)!==0?(x.lanes=S,x):(S=M!==null,h=h!==null&&h.memoizedState!==null,S&&(M=x.child,B=null,M.alternate!==null&&M.alternate.memoizedState!==null&&M.alternate.memoizedState.cachePool!==null&&(B=M.alternate.memoizedState.cachePool.pool),Q=null,M.memoizedState!==null&&M.memoizedState.cachePool!==null&&(Q=M.memoizedState.cachePool.pool),Q!==B&&(M.flags|=2048)),S!==h&&S&&(x.child.flags|=8192),DS(x,x.updateQueue),si(x),null);case 4:return E(),h===null&&gM(x.stateNode.containerInfo),si(x),null;case 10:return Uu(x.type),si(x),null;case 19:if(ne(Hi),M=x.memoizedState,M===null)return si(x),null;if(B=(x.flags&128)!==0,Q=M.rendering,Q===null)if(B)jy(M,!1);else{if(Ci!==0||h!==null&&(h.flags&128)!==0)for(h=x.child;h!==null;){if(Q=vS(h),Q!==null){for(x.flags|=128,jy(M,!1),h=Q.updateQueue,x.updateQueue=h,DS(x,h),x.subtreeFlags=0,h=S,S=x.child;S!==null;)Z5(S,h),S=S.sibling;return se(Hi,Hi.current&1|2),cr&&Iu(x,M.treeForkCount),x.child}h=h.sibling}M.tail!==null&&F()>IS&&(x.flags|=128,B=!0,jy(M,!1),x.lanes=4194304)}else{if(!B)if(h=vS(Q),h!==null){if(x.flags|=128,B=!0,h=h.updateQueue,x.updateQueue=h,DS(x,h),jy(M,!0),M.tail===null&&M.tailMode==="hidden"&&!Q.alternate&&!cr)return si(x),null}else 2*F()-M.renderingStartTime>IS&&S!==536870912&&(x.flags|=128,B=!0,jy(M,!1),x.lanes=4194304);M.isBackwards?(Q.sibling=x.child,x.child=Q):(h=M.last,h!==null?h.sibling=Q:x.child=Q,M.last=Q)}return M.tail!==null?(h=M.tail,M.rendering=h,M.tail=h.sibling,M.renderingStartTime=F(),h.sibling=null,S=Hi.current,se(Hi,B?S&1|2:S&1),cr&&Iu(x,M.treeForkCount),h):(si(x),null);case 22:case 23:return Oo(x),bP(),M=x.memoizedState!==null,h!==null?h.memoizedState!==null!==M&&(x.flags|=8192):M&&(x.flags|=8192),M?(S&536870912)!==0&&(x.flags&128)===0&&(si(x),x.subtreeFlags&6&&(x.flags|=8192)):si(x),S=x.updateQueue,S!==null&&DS(x,S.retryQueue),S=null,h!==null&&h.memoizedState!==null&&h.memoizedState.cachePool!==null&&(S=h.memoizedState.cachePool.pool),M=null,x.memoizedState!==null&&x.memoizedState.cachePool!==null&&(M=x.memoizedState.cachePool.pool),M!==S&&(x.flags|=2048),h!==null&&ne(fp),null;case 24:return S=null,h!==null&&(S=h.memoizedState.cache),x.memoizedState.cache!==S&&(x.flags|=2048),Uu(Qi),si(x),null;case 25:return null;case 30:return null}throw Error(r(156,x.tag))}function Dle(h,x){switch(nP(x),x.tag){case 1:return h=x.flags,h&65536?(x.flags=h&-65537|128,x):null;case 3:return Uu(Qi),E(),h=x.flags,(h&65536)!==0&&(h&128)===0?(x.flags=h&-65537|128,x):null;case 26:case 27:case 5:return K(x),null;case 31:if(x.memoizedState!==null){if(Oo(x),x.alternate===null)throw Error(r(340));mp()}return h=x.flags,h&65536?(x.flags=h&-65537|128,x):null;case 13:if(Oo(x),h=x.memoizedState,h!==null&&h.dehydrated!==null){if(x.alternate===null)throw Error(r(340));mp()}return h=x.flags,h&65536?(x.flags=h&-65537|128,x):null;case 19:return ne(Hi),null;case 4:return E(),null;case 10:return Uu(x.type),null;case 22:case 23:return Oo(x),bP(),h!==null&&ne(fp),h=x.flags,h&65536?(x.flags=h&-65537|128,x):null;case 24:return Uu(Qi),null;case 25:return null;default:return null}}function kU(h,x){switch(nP(x),x.tag){case 3:Uu(Qi),E();break;case 26:case 27:case 5:K(x);break;case 4:E();break;case 31:x.memoizedState!==null&&Oo(x);break;case 13:Oo(x);break;case 19:ne(Hi);break;case 10:Uu(x.type);break;case 22:case 23:Oo(x),bP(),h!==null&&ne(fp);break;case 24:Uu(Qi)}}function Dy(h,x){try{var S=x.updateQueue,M=S!==null?S.lastEffect:null;if(M!==null){var B=M.next;S=B;do{if((S.tag&h)===h){M=void 0;var Q=S.create,Se=S.inst;M=Q(),Se.destroy=M}S=S.next}while(S!==B)}}catch(Ue){Er(x,x.return,Ue)}}function vm(h,x,S){try{var M=x.updateQueue,B=M!==null?M.lastEffect:null;if(B!==null){var Q=B.next;M=Q;do{if((M.tag&h)===h){var Se=M.inst,Ue=Se.destroy;if(Ue!==void 0){Se.destroy=void 0,B=x;var tt=S,St=Ue;try{St()}catch(Mt){Er(B,tt,Mt)}}}M=M.next}while(M!==Q)}}catch(Mt){Er(x,x.return,Mt)}}function CU(h){var x=h.updateQueue;if(x!==null){var S=h.stateNode;try{gz(x,S)}catch(M){Er(h,h.return,M)}}}function TU(h,x,S){S.props=vp(h.type,h.memoizedProps),S.state=h.memoizedState;try{S.componentWillUnmount()}catch(M){Er(h,x,M)}}function Ry(h,x){try{var S=h.ref;if(S!==null){switch(h.tag){case 26:case 27:case 5:var M=h.stateNode;break;case 30:M=h.stateNode;break;default:M=h.stateNode}typeof S=="function"?h.refCleanup=S(M):S.current=M}}catch(B){Er(h,x,B)}}function $c(h,x){var S=h.ref,M=h.refCleanup;if(S!==null)if(typeof M=="function")try{M()}catch(B){Er(h,x,B)}finally{h.refCleanup=null,h=h.alternate,h!=null&&(h.refCleanup=null)}else if(typeof S=="function")try{S(null)}catch(B){Er(h,x,B)}else S.current=null}function AU(h){var x=h.type,S=h.memoizedProps,M=h.stateNode;try{e:switch(x){case"button":case"input":case"select":case"textarea":S.autoFocus&&M.focus();break e;case"img":S.src?M.src=S.src:S.srcSet&&(M.srcset=S.srcSet)}}catch(B){Er(h,h.return,B)}}function YP(h,x,S){try{var M=h.stateNode;tce(M,h.type,S,x),M[hn]=x}catch(B){Er(h,h.return,B)}}function PU(h){return h.tag===5||h.tag===3||h.tag===26||h.tag===27&&Tm(h.type)||h.tag===4}function XP(h){e:for(;;){for(;h.sibling===null;){if(h.return===null||PU(h.return))return null;h=h.return}for(h.sibling.return=h.return,h=h.sibling;h.tag!==5&&h.tag!==6&&h.tag!==18;){if(h.tag===27&&Tm(h.type)||h.flags&2||h.child===null||h.tag===4)continue e;h.child.return=h,h=h.child}if(!(h.flags&2))return h.stateNode}}function QP(h,x,S){var M=h.tag;if(M===5||M===6)h=h.stateNode,x?(S.nodeType===9?S.body:S.nodeName==="HTML"?S.ownerDocument.body:S).insertBefore(h,x):(x=S.nodeType===9?S.body:S.nodeName==="HTML"?S.ownerDocument.body:S,x.appendChild(h),S=S._reactRootContainer,S!=null||x.onclick!==null||(x.onclick=Ba));else if(M!==4&&(M===27&&Tm(h.type)&&(S=h.stateNode,x=null),h=h.child,h!==null))for(QP(h,x,S),h=h.sibling;h!==null;)QP(h,x,S),h=h.sibling}function RS(h,x,S){var M=h.tag;if(M===5||M===6)h=h.stateNode,x?S.insertBefore(h,x):S.appendChild(h);else if(M!==4&&(M===27&&Tm(h.type)&&(S=h.stateNode),h=h.child,h!==null))for(RS(h,x,S),h=h.sibling;h!==null;)RS(h,x,S),h=h.sibling}function MU(h){var x=h.stateNode,S=h.memoizedProps;try{for(var M=h.type,B=x.attributes;B.length;)x.removeAttributeNode(B[0]);Wa(x,M,S),x[mn]=h,x[hn]=S}catch(Q){Er(h,h.return,Q)}}var Vu=!1,ea=!1,ZP=!1,EU=typeof WeakSet=="function"?WeakSet:Set,Ca=null;function Rle(h,x){if(h=h.containerInfo,yM=t_,h=$5(h),VA(h)){if("selectionStart"in h)var S={start:h.selectionStart,end:h.selectionEnd};else e:{S=(S=h.ownerDocument)&&S.defaultView||window;var M=S.getSelection&&S.getSelection();if(M&&M.rangeCount!==0){S=M.anchorNode;var B=M.anchorOffset,Q=M.focusNode;M=M.focusOffset;try{S.nodeType,Q.nodeType}catch{S=null;break e}var Se=0,Ue=-1,tt=-1,St=0,Mt=0,Lt=h,kt=null;t:for(;;){for(var At;Lt!==S||B!==0&&Lt.nodeType!==3||(Ue=Se+B),Lt!==Q||M!==0&&Lt.nodeType!==3||(tt=Se+M),Lt.nodeType===3&&(Se+=Lt.nodeValue.length),(At=Lt.firstChild)!==null;)kt=Lt,Lt=At;for(;;){if(Lt===h)break t;if(kt===S&&++St===B&&(Ue=Se),kt===Q&&++Mt===M&&(tt=Se),(At=Lt.nextSibling)!==null)break;Lt=kt,kt=Lt.parentNode}Lt=At}S=Ue===-1||tt===-1?null:{start:Ue,end:tt}}else S=null}S=S||{start:0,end:0}}else S=null;for(vM={focusedElem:h,selectionRange:S},t_=!1,Ca=x;Ca!==null;)if(x=Ca,h=x.child,(x.subtreeFlags&1028)!==0&&h!==null)h.return=x,Ca=h;else for(;Ca!==null;){switch(x=Ca,Q=x.alternate,h=x.flags,x.tag){case 0:if((h&4)!==0&&(h=x.updateQueue,h=h!==null?h.events:null,h!==null))for(S=0;S title"))),Wa(Q,M,S),Q[mn]=h,Ge(Q),M=Q;break e;case"link":var Se=OB("link","href",B).get(M+(S.href||""));if(Se){for(var Ue=0;UeIr&&(Se=Ir,Ir=_n,_n=Se);var bt=B5(Ue,_n),it=B5(Ue,Ir);if(bt&&it&&(At.rangeCount!==1||At.anchorNode!==bt.node||At.anchorOffset!==bt.offset||At.focusNode!==it.node||At.focusOffset!==it.offset)){var wt=Lt.createRange();wt.setStart(bt.node,bt.offset),At.removeAllRanges(),_n>Ir?(At.addRange(wt),At.extend(it.node,it.offset)):(wt.setEnd(it.node,it.offset),At.addRange(wt))}}}}for(Lt=[],At=Ue;At=At.parentNode;)At.nodeType===1&&Lt.push({element:At,left:At.scrollLeft,top:At.scrollTop});for(typeof Ue.focus=="function"&&Ue.focus(),Ue=0;UeS?32:S,ae.T=null,S=aM,aM=null;var Q=Nm,Se=Xu;if(ua=0,Mg=Nm=null,Xu=0,(wr&6)!==0)throw Error(r(331));var Ue=wr;if(wr|=4,HU(Q.current),zU(Q,Q.current,Se,S),wr=Ue,Uy(0,!1),_e&&typeof _e.onPostCommitFiberRoot=="function")try{_e.onPostCommitFiberRoot(Oe,Q)}catch{}return!0}finally{Y.p=B,ae.T=M,sB(h,x)}}function lB(h,x,S){x=pl(S,x),x=IP(h.stateNode,x,2),h=bm(h,x,2),h!==null&&(vt(h,2),qc(h))}function Er(h,x,S){if(h.tag===3)lB(h,h,S);else for(;x!==null;){if(x.tag===3){lB(x,h,S);break}else if(x.tag===1){var M=x.stateNode;if(typeof x.type.getDerivedStateFromError=="function"||typeof M.componentDidCatch=="function"&&(_m===null||!_m.has(M))){h=pl(S,h),S=cU(2),M=bm(x,S,2),M!==null&&(uU(S,M,x,h),vt(M,2),qc(M));break}}x=x.return}}function cM(h,x,S){var M=h.pingCache;if(M===null){M=h.pingCache=new Ole;var B=new Set;M.set(x,B)}else B=M.get(x),B===void 0&&(B=new Set,M.set(x,B));B.has(S)||(tM=!0,B.add(S),h=Hle.bind(null,h,x,S),x.then(h,h))}function Hle(h,x,S){var M=h.pingCache;M!==null&&M.delete(x),h.pingedLanes|=h.suspendedLanes&S,h.warmLanes&=~S,Wr===h&&(sr&S)===S&&(Ci===4||Ci===3&&(sr&62914560)===sr&&300>F()-OS?(wr&2)===0&&Eg(h,0):nM|=S,Pg===sr&&(Pg=0)),qc(h)}function cB(h,x){x===0&&(x=nt()),h=up(h,x),h!==null&&(vt(h,x),qc(h))}function $le(h){var x=h.memoizedState,S=0;x!==null&&(S=x.retryLane),cB(h,S)}function qle(h,x){var S=0;switch(h.tag){case 31:case 13:var M=h.stateNode,B=h.memoizedState;B!==null&&(S=B.retryLane);break;case 19:M=h.stateNode;break;case 22:M=h.stateNode._retryCache;break;default:throw Error(r(314))}M!==null&&M.delete(x),cB(h,S)}function Vle(h,x){return be(h,x)}var qS=null,Dg=null,uM=!1,VS=!1,dM=!1,Cm=0;function qc(h){h!==Dg&&h.next===null&&(Dg===null?qS=Dg=h:Dg=Dg.next=h),VS=!0,uM||(uM=!0,Wle())}function Uy(h,x){if(!dM&&VS){dM=!0;do for(var S=!1,M=qS;M!==null;){if(h!==0){var B=M.pendingLanes;if(B===0)var Q=0;else{var Se=M.suspendedLanes,Ue=M.pingedLanes;Q=(1<<31-Be(42|h)+1)-1,Q&=B&~(Se&~Ue),Q=Q&201326741?Q&201326741|1:Q?Q|2:0}Q!==0&&(S=!0,hB(M,Q))}else Q=sr,Q=ke(M,M===Wr?Q:0,M.cancelPendingCommit!==null||M.timeoutHandle!==-1),(Q&3)===0||Ce(M,Q)||(S=!0,hB(M,Q));M=M.next}while(S);dM=!1}}function Gle(){uB()}function uB(){VS=uM=!1;var h=0;Cm!==0&&rce()&&(h=Cm);for(var x=F(),S=null,M=qS;M!==null;){var B=M.next,Q=dB(M,x);Q===0?(M.next=null,S===null?qS=B:S.next=B,B===null&&(Dg=S)):(S=M,(h!==0||(Q&3)!==0)&&(VS=!0)),M=B}ua!==0&&ua!==5||Uy(h),Cm!==0&&(Cm=0)}function dB(h,x){for(var S=h.suspendedLanes,M=h.pingedLanes,B=h.expirationTimes,Q=h.pendingLanes&-62914561;0>>=0,h===0?32:31-(Me(h)/Xe|0)|0}var We=256,Pe=262144,Ke=4194304;function Ve(h){var x=h&42;if(x!==0)return x;switch(h&-h){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return h&261888;case 262144:case 524288:case 1048576:case 2097152:return h&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return h&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return h}}function ke(h,x,S){var M=h.pendingLanes;if(M===0)return 0;var B=0,Q=h.suspendedLanes,Se=h.pingedLanes;h=h.warmLanes;var Ue=M&134217727;return Ue!==0?(M=Ue&~Q,M!==0?B=Ve(M):(Se&=Ue,Se!==0?B=Ve(Se):S||(S=Ue&~h,S!==0&&(B=Ve(S))))):(Ue=M&~Q,Ue!==0?B=Ve(Ue):Se!==0?B=Ve(Se):S||(S=M&~h,S!==0&&(B=Ve(S)))),B===0?0:x!==0&&x!==B&&(x&Q)===0&&(Q=B&-B,S=x&-x,Q>=S||Q===32&&(S&4194048)!==0)?x:B}function Ce(h,x){return(h.pendingLanes&~(h.suspendedLanes&~h.pingedLanes)&x)===0}function $e(h,x){switch(h){case 1:case 2:case 4:case 8:case 64:return x+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return x+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function nt(){var h=Ke;return Ke<<=1,(Ke&62914560)===0&&(Ke=4194304),h}function xt(h){for(var x=[],S=0;31>S;S++)x.push(h);return x}function vt(h,x){h.pendingLanes|=x,x!==268435456&&(h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0)}function mt(h,x,S,M,B,Q){var Se=h.pendingLanes;h.pendingLanes=S,h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0,h.expiredLanes&=S,h.entangledLanes&=S,h.errorRecoveryDisabledLanes&=S,h.shellSuspendCounter=0;var Ue=h.entanglements,tt=h.expirationTimes,St=h.hiddenUpdates;for(S=Se&~S;0"u")return null;try{return h.activeElement||h.body}catch{return h.body}}var Gr=/[\n"\\]/g;function lr(h){return h.replace(Gr,function(x){return"\\"+x.charCodeAt(0).toString(16)+" "})}function zi(h,x,S,M,B,Q,Se,Ue){h.name="",Se!=null&&typeof Se!="function"&&typeof Se!="symbol"&&typeof Se!="boolean"?h.type=Se:h.removeAttribute("type"),x!=null?Se==="number"?(x===0&&h.value===""||h.value!=x)&&(h.value=""+wn(x)):h.value!==""+wn(x)&&(h.value=""+wn(x)):Se!=="submit"&&Se!=="reset"||h.removeAttribute("value"),x!=null?Ni(h,Se,wn(x)):S!=null?Ni(h,Se,wn(S)):M!=null&&h.removeAttribute("value"),B==null&&Q!=null&&(h.defaultChecked=!!Q),B!=null&&(h.checked=B&&typeof B!="function"&&typeof B!="symbol"),Ue!=null&&typeof Ue!="function"&&typeof Ue!="symbol"&&typeof Ue!="boolean"?h.name=""+wn(Ue):h.removeAttribute("name")}function fn(h,x,S,M,B,Q,Se,Ue){if(Q!=null&&typeof Q!="function"&&typeof Q!="symbol"&&typeof Q!="boolean"&&(h.type=Q),x!=null||S!=null){if(!(Q!=="submit"&&Q!=="reset"||x!=null)){Sn(h);return}S=S!=null?""+wn(S):"",x=x!=null?""+wn(x):S,Ue||x===h.value||(h.value=x),h.defaultValue=x}M=M??B,M=typeof M!="function"&&typeof M!="symbol"&&!!M,h.checked=Ue?h.checked:!!M,h.defaultChecked=!!M,Se!=null&&typeof Se!="function"&&typeof Se!="symbol"&&typeof Se!="boolean"&&(h.name=Se),Sn(h)}function Ni(h,x,S){x==="number"&&ei(h.ownerDocument)===h||h.defaultValue===""+S||(h.defaultValue=""+S)}function ui(h,x,S,M){if(h=h.options,x){x={};for(var B=0;B"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ps=!1;if(As)try{var io={};Object.defineProperty(io,"passive",{get:function(){Ps=!0}}),window.addEventListener("test",io,io),window.removeEventListener("test",io,io)}catch{Ps=!1}var ka=null,cl=null,Ms=null;function Ic(){if(Ms)return Ms;var h,x=cl,S=x.length,M,B="value"in ka?ka.value:ka.textContent,Q=B.length;for(h=0;h=Pr),Cn=" ",BA=!1;function hy(h,x){switch(h){case"keyup":return Do.indexOf(x.keyCode)!==-1;case"keydown":return x.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function op(h){return h=h.detail,typeof h=="object"&&"data"in h?h.data:null}var Lu=!1;function iS(h,x){switch(h){case"compositionend":return op(x);case"keypress":return x.which!==32?null:(BA=!0,Cn);case"textInput":return h=x.data,h===Cn&&BA?null:h;default:return null}}function HA(h,x){if(Lu)return h==="compositionend"||!sp&&hy(h,x)?(h=Ic(),Ms=cl=ka=null,Lu=!1,h):null;switch(h){case"paste":return null;case"keypress":if(!(x.ctrlKey||x.altKey||x.metaKey)||x.ctrlKey&&x.altKey){if(x.char&&1=x)return{node:S,offset:x-h};h=M}e:{for(;S;){if(S.nextSibling){S=S.nextSibling;break e}S=S.parentNode}S=void 0}S=U5(S)}}function H5(h,x){return h&&x?h===x?!0:h&&h.nodeType===3?!1:x&&x.nodeType===3?H5(h,x.parentNode):"contains"in h?h.contains(x):h.compareDocumentPosition?!!(h.compareDocumentPosition(x)&16):!1:!1}function $5(h){h=h!=null&&h.ownerDocument!=null&&h.ownerDocument.defaultView!=null?h.ownerDocument.defaultView:window;for(var x=ei(h.document);x instanceof h.HTMLIFrameElement;){try{var S=typeof x.contentWindow.location.href=="string"}catch{S=!1}if(S)h=x.contentWindow;else break;x=ei(h.document)}return x}function VA(h){var x=h&&h.nodeName&&h.nodeName.toLowerCase();return x&&(x==="input"&&(h.type==="text"||h.type==="search"||h.type==="tel"||h.type==="url"||h.type==="password")||x==="textarea"||h.contentEditable==="true")}var dle=As&&"documentMode"in document&&11>=document.documentMode,ug=null,GA=null,by=null,WA=!1;function q5(h,x,S){var M=S.window===S?S.document:S.nodeType===9?S:S.ownerDocument;WA||ug==null||ug!==ei(M)||(M=ug,"selectionStart"in M&&VA(M)?M={start:M.selectionStart,end:M.selectionEnd}:(M=(M.ownerDocument&&M.ownerDocument.defaultView||window).getSelection(),M={anchorNode:M.anchorNode,anchorOffset:M.anchorOffset,focusNode:M.focusNode,focusOffset:M.focusOffset}),by&&gy(by,M)||(by=M,M=WS(GA,"onSelect"),0>=Se,B-=Se,Bc=1<<32-Be(x)+B|S<$n?(or=ln,ln=null):or=ln.sibling;var xr=kt(bt,ln,wt[$n],jt);if(xr===null){ln===null&&(ln=or);break}h&&ln&&xr.alternate===null&&x(bt,ln),it=Q(xr,it,$n),br===null?gn=xr:br.sibling=xr,br=xr,ln=or}if($n===wt.length)return S(bt,ln),cr&&Iu(bt,$n),gn;if(ln===null){for(;$n$n?(or=ln,ln=null):or=ln.sibling;var jm=kt(bt,ln,xr.value,jt);if(jm===null){ln===null&&(ln=or);break}h&&ln&&jm.alternate===null&&x(bt,ln),it=Q(jm,it,$n),br===null?gn=jm:br.sibling=jm,br=jm,ln=or}if(xr.done)return S(bt,ln),cr&&Iu(bt,$n),gn;if(ln===null){for(;!xr.done;$n++,xr=wt.next())xr=Lt(bt,xr.value,jt),xr!==null&&(it=Q(xr,it,$n),br===null?gn=xr:br.sibling=xr,br=xr);return cr&&Iu(bt,$n),gn}for(ln=M(ln);!xr.done;$n++,xr=wt.next())xr=At(ln,bt,$n,xr.value,jt),xr!==null&&(h&&xr.alternate!==null&&ln.delete(xr.key===null?$n:xr.key),it=Q(xr,it,$n),br===null?gn=xr:br.sibling=xr,br=xr);return h&&ln.forEach(function(Ece){return x(bt,Ece)}),cr&&Iu(bt,$n),gn}function Ir(bt,it,wt,jt){if(typeof wt=="object"&&wt!==null&&wt.type===v&&wt.key===null&&(wt=wt.props.children),typeof wt=="object"&&wt!==null){switch(wt.$$typeof){case f:e:{for(var gn=wt.key;it!==null;){if(it.key===gn){if(gn=wt.type,gn===v){if(it.tag===7){S(bt,it.sibling),jt=B(it,wt.props.children),jt.return=bt,bt=jt;break e}}else if(it.elementType===gn||typeof gn=="object"&&gn!==null&&gn.$$typeof===D&&gp(gn)===it.type){S(bt,it.sibling),jt=B(it,wt.props),_y(jt,wt),jt.return=bt,bt=jt;break e}S(bt,it);break}else x(bt,it);it=it.sibling}wt.type===v?(jt=dp(wt.props.children,bt.mode,jt,wt.key),jt.return=bt,bt=jt):(jt=uS(wt.type,wt.key,wt.props,null,bt.mode,jt),_y(jt,wt),jt.return=bt,bt=jt)}return Se(bt);case y:e:{for(gn=wt.key;it!==null;){if(it.key===gn)if(it.tag===4&&it.stateNode.containerInfo===wt.containerInfo&&it.stateNode.implementation===wt.implementation){S(bt,it.sibling),jt=B(it,wt.children||[]),jt.return=bt,bt=jt;break e}else{S(bt,it);break}else x(bt,it);it=it.sibling}jt=eP(wt,bt.mode,jt),jt.return=bt,bt=jt}return Se(bt);case D:return wt=gp(wt),Ir(bt,it,wt,jt)}if(J(wt))return an(bt,it,wt,jt);if(z(wt)){if(gn=z(wt),typeof gn!="function")throw Error(r(150));return wt=gn.call(wt),_n(bt,it,wt,jt)}if(typeof wt.then=="function")return Ir(bt,it,bS(wt),jt);if(wt.$$typeof===N)return Ir(bt,it,hS(bt,wt),jt);xS(bt,wt)}return typeof wt=="string"&&wt!==""||typeof wt=="number"||typeof wt=="bigint"?(wt=""+wt,it!==null&&it.tag===6?(S(bt,it.sibling),jt=B(it,wt),jt.return=bt,bt=jt):(S(bt,it),jt=JA(wt,bt.mode,jt),jt.return=bt,bt=jt),Se(bt)):S(bt,it)}return function(bt,it,wt,jt){try{Sy=0;var gn=Ir(bt,it,wt,jt);return wg=null,gn}catch(ln){if(ln===vg||ln===fS)throw ln;var br=Fo(29,ln,null,bt.mode);return br.lanes=jt,br.return=bt,br}}}var xp=hz(!0),pz=hz(!1),fm=!1;function mP(h){h.updateQueue={baseState:h.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function hP(h,x){h=h.updateQueue,x.updateQueue===h&&(x.updateQueue={baseState:h.baseState,firstBaseUpdate:h.firstBaseUpdate,lastBaseUpdate:h.lastBaseUpdate,shared:h.shared,callbacks:null})}function gm(h){return{lane:h,tag:0,payload:null,callback:null,next:null}}function bm(h,x,S){var M=h.updateQueue;if(M===null)return null;if(M=M.shared,(wr&2)!==0){var B=M.pending;return B===null?x.next=x:(x.next=B.next,B.next=x),M.pending=x,x=cS(h),Q5(h,null,S),x}return lS(h,M,x,S),cS(h)}function Ny(h,x,S){if(x=x.updateQueue,x!==null&&(x=x.shared,(S&4194048)!==0)){var M=x.lanes;M&=h.pendingLanes,S|=M,x.lanes=S,Nt(h,S)}}function pP(h,x){var S=h.updateQueue,M=h.alternate;if(M!==null&&(M=M.updateQueue,S===M)){var B=null,Q=null;if(S=S.firstBaseUpdate,S!==null){do{var Se={lane:S.lane,tag:S.tag,payload:S.payload,callback:null,next:null};Q===null?B=Q=Se:Q=Q.next=Se,S=S.next}while(S!==null);Q===null?B=Q=x:Q=Q.next=x}else B=Q=x;S={baseState:M.baseState,firstBaseUpdate:B,lastBaseUpdate:Q,shared:M.shared,callbacks:M.callbacks},h.updateQueue=S;return}h=S.lastBaseUpdate,h===null?S.firstBaseUpdate=x:h.next=x,S.lastBaseUpdate=x}var fP=!1;function ky(){if(fP){var h=yg;if(h!==null)throw h}}function Cy(h,x,S,M){fP=!1;var B=h.updateQueue;fm=!1;var Q=B.firstBaseUpdate,Se=B.lastBaseUpdate,Ue=B.shared.pending;if(Ue!==null){B.shared.pending=null;var tt=Ue,St=tt.next;tt.next=null,Se===null?Q=St:Se.next=St,Se=tt;var Mt=h.alternate;Mt!==null&&(Mt=Mt.updateQueue,Ue=Mt.lastBaseUpdate,Ue!==Se&&(Ue===null?Mt.firstBaseUpdate=St:Ue.next=St,Mt.lastBaseUpdate=tt))}if(Q!==null){var Lt=B.baseState;Se=0,Mt=St=tt=null,Ue=Q;do{var kt=Ue.lane&-536870913,At=kt!==Ue.lane;if(At?(sr&kt)===kt:(M&kt)===kt){kt!==0&&kt===xg&&(fP=!0),Mt!==null&&(Mt=Mt.next={lane:0,tag:Ue.tag,payload:Ue.payload,callback:null,next:null});e:{var an=h,_n=Ue;kt=x;var Ir=S;switch(_n.tag){case 1:if(an=_n.payload,typeof an=="function"){Lt=an.call(Ir,Lt,kt);break e}Lt=an;break e;case 3:an.flags=an.flags&-65537|128;case 0:if(an=_n.payload,kt=typeof an=="function"?an.call(Ir,Lt,kt):an,kt==null)break e;Lt=m({},Lt,kt);break e;case 2:fm=!0}}kt=Ue.callback,kt!==null&&(h.flags|=64,At&&(h.flags|=8192),At=B.callbacks,At===null?B.callbacks=[kt]:At.push(kt))}else At={lane:kt,tag:Ue.tag,payload:Ue.payload,callback:Ue.callback,next:null},Mt===null?(St=Mt=At,tt=Lt):Mt=Mt.next=At,Se|=kt;if(Ue=Ue.next,Ue===null){if(Ue=B.shared.pending,Ue===null)break;At=Ue,Ue=At.next,At.next=null,B.lastBaseUpdate=At,B.shared.pending=null}}while(!0);Mt===null&&(tt=Lt),B.baseState=tt,B.firstBaseUpdate=St,B.lastBaseUpdate=Mt,Q===null&&(B.shared.lanes=0),Sm|=Se,h.lanes=Se,h.memoizedState=Lt}}function fz(h,x){if(typeof h!="function")throw Error(r(191,h));h.call(x)}function gz(h,x){var S=h.callbacks;if(S!==null)for(h.callbacks=null,h=0;hQ?Q:8;var Se=ae.T,Ue={};ae.T=Ue,RP(h,!1,x,S);try{var tt=B(),St=ae.S;if(St!==null&&St(Ue,tt),tt!==null&&typeof tt=="object"&&typeof tt.then=="function"){var Mt=vle(tt,M);Py(h,x,Mt,Uo(h))}else Py(h,x,M,Uo(h))}catch(Lt){Py(h,x,{then:function(){},status:"rejected",reason:Lt},Uo())}finally{Y.p=Q,Se!==null&&Ue.types!==null&&(Se.types=Ue.types),ae.T=Se}}function Cle(){}function jP(h,x,S,M){if(h.tag!==5)throw Error(r(476));var B=Kz(h).queue;Wz(h,B,x,ue,S===null?Cle:function(){return Yz(h),S(M)})}function Kz(h){var x=h.memoizedState;if(x!==null)return x;x={memoizedState:ue,baseState:ue,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Hu,lastRenderedState:ue},next:null};var S={};return x.next={memoizedState:S,baseState:S,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Hu,lastRenderedState:S},next:null},h.memoizedState=x,h=h.alternate,h!==null&&(h.memoizedState=x),x}function Yz(h){var x=Kz(h);x.next===null&&(x=h.alternate.memoizedState),Py(h,x.next.queue,{},Uo())}function DP(){return Va(Gy)}function Xz(){return $i().memoizedState}function Qz(){return $i().memoizedState}function Tle(h){for(var x=h.return;x!==null;){switch(x.tag){case 24:case 3:var S=Uo();h=gm(S);var M=bm(x,h,S);M!==null&&(mo(M,x,S),Ny(M,x,S)),x={cache:lP()},h.payload=x;return}x=x.return}}function Ale(h,x,S){var M=Uo();S={lane:M,revertLane:0,gesture:null,action:S,hasEagerState:!1,eagerState:null,next:null},AS(h)?Jz(x,S):(S=QA(h,x,S,M),S!==null&&(mo(S,h,M),eU(S,x,M)))}function Zz(h,x,S){var M=Uo();Py(h,x,S,M)}function Py(h,x,S,M){var B={lane:M,revertLane:0,gesture:null,action:S,hasEagerState:!1,eagerState:null,next:null};if(AS(h))Jz(x,B);else{var Q=h.alternate;if(h.lanes===0&&(Q===null||Q.lanes===0)&&(Q=x.lastRenderedReducer,Q!==null))try{var Se=x.lastRenderedState,Ue=Q(Se,S);if(B.hasEagerState=!0,B.eagerState=Ue,Ro(Ue,Se))return lS(h,x,B,0),Wr===null&&oS(),!1}catch{}if(S=QA(h,x,B,M),S!==null)return mo(S,h,M),eU(S,x,M),!0}return!1}function RP(h,x,S,M){if(M={lane:2,revertLane:mM(),gesture:null,action:M,hasEagerState:!1,eagerState:null,next:null},AS(h)){if(x)throw Error(r(479))}else x=QA(h,S,M,2),x!==null&&mo(x,h,2)}function AS(h){var x=h.alternate;return h===Un||x!==null&&x===Un}function Jz(h,x){_g=wS=!0;var S=h.pending;S===null?x.next=x:(x.next=S.next,S.next=x),h.pending=x}function eU(h,x,S){if((S&4194048)!==0){var M=x.lanes;M&=h.pendingLanes,S|=M,x.lanes=S,Nt(h,S)}}var My={readContext:Va,use:NS,useCallback:ki,useContext:ki,useEffect:ki,useImperativeHandle:ki,useLayoutEffect:ki,useInsertionEffect:ki,useMemo:ki,useReducer:ki,useRef:ki,useState:ki,useDebugValue:ki,useDeferredValue:ki,useTransition:ki,useSyncExternalStore:ki,useId:ki,useHostTransitionStatus:ki,useFormState:ki,useActionState:ki,useOptimistic:ki,useMemoCache:ki,useCacheRefresh:ki};My.useEffectEvent=ki;var tU={readContext:Va,use:NS,useCallback:function(h,x){return Rs().memoizedState=[h,x===void 0?null:x],h},useContext:Va,useEffect:Iz,useImperativeHandle:function(h,x,S){S=S!=null?S.concat([h]):null,CS(4194308,4,Hz.bind(null,x,h),S)},useLayoutEffect:function(h,x){return CS(4194308,4,h,x)},useInsertionEffect:function(h,x){CS(4,2,h,x)},useMemo:function(h,x){var S=Rs();x=x===void 0?null:x;var M=h();if(yp){Ae(!0);try{h()}finally{Ae(!1)}}return S.memoizedState=[M,x],M},useReducer:function(h,x,S){var M=Rs();if(S!==void 0){var B=S(x);if(yp){Ae(!0);try{S(x)}finally{Ae(!1)}}}else B=x;return M.memoizedState=M.baseState=B,h={pending:null,lanes:0,dispatch:null,lastRenderedReducer:h,lastRenderedState:B},M.queue=h,h=h.dispatch=Ale.bind(null,Un,h),[M.memoizedState,h]},useRef:function(h){var x=Rs();return h={current:h},x.memoizedState=h},useState:function(h){h=TP(h);var x=h.queue,S=Zz.bind(null,Un,x);return x.dispatch=S,[h.memoizedState,S]},useDebugValue:MP,useDeferredValue:function(h,x){var S=Rs();return EP(S,h,x)},useTransition:function(){var h=TP(!1);return h=Wz.bind(null,Un,h.queue,!0,!1),Rs().memoizedState=h,[!1,h]},useSyncExternalStore:function(h,x,S){var M=Un,B=Rs();if(cr){if(S===void 0)throw Error(r(407));S=S()}else{if(S=x(),Wr===null)throw Error(r(349));(sr&127)!==0||Sz(M,x,S)}B.memoizedState=S;var Q={value:S,getSnapshot:x};return B.queue=Q,Iz(Nz.bind(null,M,Q,h),[h]),M.flags|=2048,kg(9,{destroy:void 0},_z.bind(null,M,Q,S,x),null),S},useId:function(){var h=Rs(),x=Wr.identifierPrefix;if(cr){var S=Hc,M=Bc;S=(M&~(1<<32-Be(M)-1)).toString(32)+S,x="_"+x+"R_"+S,S=SS++,0<\/script>",Q=Q.removeChild(Q.firstChild);break;case"select":Q=typeof M.is=="string"?Se.createElement("select",{is:M.is}):Se.createElement("select"),M.multiple?Q.multiple=!0:M.size&&(Q.size=M.size);break;default:Q=typeof M.is=="string"?Se.createElement(B,{is:M.is}):Se.createElement(B)}}Q[mn]=x,Q[hn]=M;e:for(Se=x.child;Se!==null;){if(Se.tag===5||Se.tag===6)Q.appendChild(Se.stateNode);else if(Se.tag!==4&&Se.tag!==27&&Se.child!==null){Se.child.return=Se,Se=Se.child;continue}if(Se===x)break e;for(;Se.sibling===null;){if(Se.return===null||Se.return===x)break e;Se=Se.return}Se.sibling.return=Se.return,Se=Se.sibling}x.stateNode=Q;e:switch(Wa(Q,B,M),B){case"button":case"input":case"select":case"textarea":M=!!M.autoFocus;break e;case"img":M=!0;break e;default:M=!1}M&&qu(x)}}return si(x),KP(x,x.type,h===null?null:h.memoizedProps,x.pendingProps,S),null;case 6:if(h&&x.stateNode!=null)h.memoizedProps!==M&&qu(x);else{if(typeof M!="string"&&x.stateNode===null)throw Error(r(166));if(h=q.current,gg(x)){if(h=x.stateNode,S=x.memoizedProps,M=null,B=qa,B!==null)switch(B.tag){case 27:case 5:M=B.memoizedProps}h[mn]=x,h=!!(h.nodeValue===S||M!==null&&M.suppressHydrationWarning===!0||vB(h.nodeValue,S)),h||hm(x,!0)}else h=KS(h).createTextNode(M),h[mn]=x,x.stateNode=h}return si(x),null;case 31:if(S=x.memoizedState,h===null||h.memoizedState!==null){if(M=gg(x),S!==null){if(h===null){if(!M)throw Error(r(318));if(h=x.memoizedState,h=h!==null?h.dehydrated:null,!h)throw Error(r(557));h[mn]=x}else mp(),(x.flags&128)===0&&(x.memoizedState=null),x.flags|=4;si(x),h=!1}else S=iP(),h!==null&&h.memoizedState!==null&&(h.memoizedState.hydrationErrors=S),h=!0;if(!h)return x.flags&256?(Oo(x),x):(Oo(x),null);if((x.flags&128)!==0)throw Error(r(558))}return si(x),null;case 13:if(M=x.memoizedState,h===null||h.memoizedState!==null&&h.memoizedState.dehydrated!==null){if(B=gg(x),M!==null&&M.dehydrated!==null){if(h===null){if(!B)throw Error(r(318));if(B=x.memoizedState,B=B!==null?B.dehydrated:null,!B)throw Error(r(317));B[mn]=x}else mp(),(x.flags&128)===0&&(x.memoizedState=null),x.flags|=4;si(x),B=!1}else B=iP(),h!==null&&h.memoizedState!==null&&(h.memoizedState.hydrationErrors=B),B=!0;if(!B)return x.flags&256?(Oo(x),x):(Oo(x),null)}return Oo(x),(x.flags&128)!==0?(x.lanes=S,x):(S=M!==null,h=h!==null&&h.memoizedState!==null,S&&(M=x.child,B=null,M.alternate!==null&&M.alternate.memoizedState!==null&&M.alternate.memoizedState.cachePool!==null&&(B=M.alternate.memoizedState.cachePool.pool),Q=null,M.memoizedState!==null&&M.memoizedState.cachePool!==null&&(Q=M.memoizedState.cachePool.pool),Q!==B&&(M.flags|=2048)),S!==h&&S&&(x.child.flags|=8192),DS(x,x.updateQueue),si(x),null);case 4:return E(),h===null&&gM(x.stateNode.containerInfo),si(x),null;case 10:return Uu(x.type),si(x),null;case 19:if(ne(Hi),M=x.memoizedState,M===null)return si(x),null;if(B=(x.flags&128)!==0,Q=M.rendering,Q===null)if(B)jy(M,!1);else{if(Ci!==0||h!==null&&(h.flags&128)!==0)for(h=x.child;h!==null;){if(Q=vS(h),Q!==null){for(x.flags|=128,jy(M,!1),h=Q.updateQueue,x.updateQueue=h,DS(x,h),x.subtreeFlags=0,h=S,S=x.child;S!==null;)Z5(S,h),S=S.sibling;return se(Hi,Hi.current&1|2),cr&&Iu(x,M.treeForkCount),x.child}h=h.sibling}M.tail!==null&&F()>IS&&(x.flags|=128,B=!0,jy(M,!1),x.lanes=4194304)}else{if(!B)if(h=vS(Q),h!==null){if(x.flags|=128,B=!0,h=h.updateQueue,x.updateQueue=h,DS(x,h),jy(M,!0),M.tail===null&&M.tailMode==="hidden"&&!Q.alternate&&!cr)return si(x),null}else 2*F()-M.renderingStartTime>IS&&S!==536870912&&(x.flags|=128,B=!0,jy(M,!1),x.lanes=4194304);M.isBackwards?(Q.sibling=x.child,x.child=Q):(h=M.last,h!==null?h.sibling=Q:x.child=Q,M.last=Q)}return M.tail!==null?(h=M.tail,M.rendering=h,M.tail=h.sibling,M.renderingStartTime=F(),h.sibling=null,S=Hi.current,se(Hi,B?S&1|2:S&1),cr&&Iu(x,M.treeForkCount),h):(si(x),null);case 22:case 23:return Oo(x),bP(),M=x.memoizedState!==null,h!==null?h.memoizedState!==null!==M&&(x.flags|=8192):M&&(x.flags|=8192),M?(S&536870912)!==0&&(x.flags&128)===0&&(si(x),x.subtreeFlags&6&&(x.flags|=8192)):si(x),S=x.updateQueue,S!==null&&DS(x,S.retryQueue),S=null,h!==null&&h.memoizedState!==null&&h.memoizedState.cachePool!==null&&(S=h.memoizedState.cachePool.pool),M=null,x.memoizedState!==null&&x.memoizedState.cachePool!==null&&(M=x.memoizedState.cachePool.pool),M!==S&&(x.flags|=2048),h!==null&&ne(fp),null;case 24:return S=null,h!==null&&(S=h.memoizedState.cache),x.memoizedState.cache!==S&&(x.flags|=2048),Uu(Qi),si(x),null;case 25:return null;case 30:return null}throw Error(r(156,x.tag))}function Dle(h,x){switch(nP(x),x.tag){case 1:return h=x.flags,h&65536?(x.flags=h&-65537|128,x):null;case 3:return Uu(Qi),E(),h=x.flags,(h&65536)!==0&&(h&128)===0?(x.flags=h&-65537|128,x):null;case 26:case 27:case 5:return K(x),null;case 31:if(x.memoizedState!==null){if(Oo(x),x.alternate===null)throw Error(r(340));mp()}return h=x.flags,h&65536?(x.flags=h&-65537|128,x):null;case 13:if(Oo(x),h=x.memoizedState,h!==null&&h.dehydrated!==null){if(x.alternate===null)throw Error(r(340));mp()}return h=x.flags,h&65536?(x.flags=h&-65537|128,x):null;case 19:return ne(Hi),null;case 4:return E(),null;case 10:return Uu(x.type),null;case 22:case 23:return Oo(x),bP(),h!==null&&ne(fp),h=x.flags,h&65536?(x.flags=h&-65537|128,x):null;case 24:return Uu(Qi),null;case 25:return null;default:return null}}function kU(h,x){switch(nP(x),x.tag){case 3:Uu(Qi),E();break;case 26:case 27:case 5:K(x);break;case 4:E();break;case 31:x.memoizedState!==null&&Oo(x);break;case 13:Oo(x);break;case 19:ne(Hi);break;case 10:Uu(x.type);break;case 22:case 23:Oo(x),bP(),h!==null&&ne(fp);break;case 24:Uu(Qi)}}function Dy(h,x){try{var S=x.updateQueue,M=S!==null?S.lastEffect:null;if(M!==null){var B=M.next;S=B;do{if((S.tag&h)===h){M=void 0;var Q=S.create,Se=S.inst;M=Q(),Se.destroy=M}S=S.next}while(S!==B)}}catch(Ue){Er(x,x.return,Ue)}}function vm(h,x,S){try{var M=x.updateQueue,B=M!==null?M.lastEffect:null;if(B!==null){var Q=B.next;M=Q;do{if((M.tag&h)===h){var Se=M.inst,Ue=Se.destroy;if(Ue!==void 0){Se.destroy=void 0,B=x;var tt=S,St=Ue;try{St()}catch(Mt){Er(B,tt,Mt)}}}M=M.next}while(M!==Q)}}catch(Mt){Er(x,x.return,Mt)}}function CU(h){var x=h.updateQueue;if(x!==null){var S=h.stateNode;try{gz(x,S)}catch(M){Er(h,h.return,M)}}}function TU(h,x,S){S.props=vp(h.type,h.memoizedProps),S.state=h.memoizedState;try{S.componentWillUnmount()}catch(M){Er(h,x,M)}}function Ry(h,x){try{var S=h.ref;if(S!==null){switch(h.tag){case 26:case 27:case 5:var M=h.stateNode;break;case 30:M=h.stateNode;break;default:M=h.stateNode}typeof S=="function"?h.refCleanup=S(M):S.current=M}}catch(B){Er(h,x,B)}}function $c(h,x){var S=h.ref,M=h.refCleanup;if(S!==null)if(typeof M=="function")try{M()}catch(B){Er(h,x,B)}finally{h.refCleanup=null,h=h.alternate,h!=null&&(h.refCleanup=null)}else if(typeof S=="function")try{S(null)}catch(B){Er(h,x,B)}else S.current=null}function AU(h){var x=h.type,S=h.memoizedProps,M=h.stateNode;try{e:switch(x){case"button":case"input":case"select":case"textarea":S.autoFocus&&M.focus();break e;case"img":S.src?M.src=S.src:S.srcSet&&(M.srcset=S.srcSet)}}catch(B){Er(h,h.return,B)}}function YP(h,x,S){try{var M=h.stateNode;tce(M,h.type,S,x),M[hn]=x}catch(B){Er(h,h.return,B)}}function PU(h){return h.tag===5||h.tag===3||h.tag===26||h.tag===27&&Tm(h.type)||h.tag===4}function XP(h){e:for(;;){for(;h.sibling===null;){if(h.return===null||PU(h.return))return null;h=h.return}for(h.sibling.return=h.return,h=h.sibling;h.tag!==5&&h.tag!==6&&h.tag!==18;){if(h.tag===27&&Tm(h.type)||h.flags&2||h.child===null||h.tag===4)continue e;h.child.return=h,h=h.child}if(!(h.flags&2))return h.stateNode}}function QP(h,x,S){var M=h.tag;if(M===5||M===6)h=h.stateNode,x?(S.nodeType===9?S.body:S.nodeName==="HTML"?S.ownerDocument.body:S).insertBefore(h,x):(x=S.nodeType===9?S.body:S.nodeName==="HTML"?S.ownerDocument.body:S,x.appendChild(h),S=S._reactRootContainer,S!=null||x.onclick!==null||(x.onclick=Ba));else if(M!==4&&(M===27&&Tm(h.type)&&(S=h.stateNode,x=null),h=h.child,h!==null))for(QP(h,x,S),h=h.sibling;h!==null;)QP(h,x,S),h=h.sibling}function RS(h,x,S){var M=h.tag;if(M===5||M===6)h=h.stateNode,x?S.insertBefore(h,x):S.appendChild(h);else if(M!==4&&(M===27&&Tm(h.type)&&(S=h.stateNode),h=h.child,h!==null))for(RS(h,x,S),h=h.sibling;h!==null;)RS(h,x,S),h=h.sibling}function MU(h){var x=h.stateNode,S=h.memoizedProps;try{for(var M=h.type,B=x.attributes;B.length;)x.removeAttributeNode(B[0]);Wa(x,M,S),x[mn]=h,x[hn]=S}catch(Q){Er(h,h.return,Q)}}var Vu=!1,ea=!1,ZP=!1,EU=typeof WeakSet=="function"?WeakSet:Set,Ta=null;function Rle(h,x){if(h=h.containerInfo,yM=t_,h=$5(h),VA(h)){if("selectionStart"in h)var S={start:h.selectionStart,end:h.selectionEnd};else e:{S=(S=h.ownerDocument)&&S.defaultView||window;var M=S.getSelection&&S.getSelection();if(M&&M.rangeCount!==0){S=M.anchorNode;var B=M.anchorOffset,Q=M.focusNode;M=M.focusOffset;try{S.nodeType,Q.nodeType}catch{S=null;break e}var Se=0,Ue=-1,tt=-1,St=0,Mt=0,Lt=h,kt=null;t:for(;;){for(var At;Lt!==S||B!==0&&Lt.nodeType!==3||(Ue=Se+B),Lt!==Q||M!==0&&Lt.nodeType!==3||(tt=Se+M),Lt.nodeType===3&&(Se+=Lt.nodeValue.length),(At=Lt.firstChild)!==null;)kt=Lt,Lt=At;for(;;){if(Lt===h)break t;if(kt===S&&++St===B&&(Ue=Se),kt===Q&&++Mt===M&&(tt=Se),(At=Lt.nextSibling)!==null)break;Lt=kt,kt=Lt.parentNode}Lt=At}S=Ue===-1||tt===-1?null:{start:Ue,end:tt}}else S=null}S=S||{start:0,end:0}}else S=null;for(vM={focusedElem:h,selectionRange:S},t_=!1,Ta=x;Ta!==null;)if(x=Ta,h=x.child,(x.subtreeFlags&1028)!==0&&h!==null)h.return=x,Ta=h;else for(;Ta!==null;){switch(x=Ta,Q=x.alternate,h=x.flags,x.tag){case 0:if((h&4)!==0&&(h=x.updateQueue,h=h!==null?h.events:null,h!==null))for(S=0;S title"))),Wa(Q,M,S),Q[mn]=h,Ge(Q),M=Q;break e;case"link":var Se=OB("link","href",B).get(M+(S.href||""));if(Se){for(var Ue=0;UeIr&&(Se=Ir,Ir=_n,_n=Se);var bt=B5(Ue,_n),it=B5(Ue,Ir);if(bt&&it&&(At.rangeCount!==1||At.anchorNode!==bt.node||At.anchorOffset!==bt.offset||At.focusNode!==it.node||At.focusOffset!==it.offset)){var wt=Lt.createRange();wt.setStart(bt.node,bt.offset),At.removeAllRanges(),_n>Ir?(At.addRange(wt),At.extend(it.node,it.offset)):(wt.setEnd(it.node,it.offset),At.addRange(wt))}}}}for(Lt=[],At=Ue;At=At.parentNode;)At.nodeType===1&&Lt.push({element:At,left:At.scrollLeft,top:At.scrollTop});for(typeof Ue.focus=="function"&&Ue.focus(),Ue=0;UeS?32:S,ae.T=null,S=aM,aM=null;var Q=Nm,Se=Xu;if(ua=0,Mg=Nm=null,Xu=0,(wr&6)!==0)throw Error(r(331));var Ue=wr;if(wr|=4,HU(Q.current),zU(Q,Q.current,Se,S),wr=Ue,Uy(0,!1),_e&&typeof _e.onPostCommitFiberRoot=="function")try{_e.onPostCommitFiberRoot(Oe,Q)}catch{}return!0}finally{Y.p=B,ae.T=M,sB(h,x)}}function lB(h,x,S){x=pl(S,x),x=IP(h.stateNode,x,2),h=bm(h,x,2),h!==null&&(vt(h,2),qc(h))}function Er(h,x,S){if(h.tag===3)lB(h,h,S);else for(;x!==null;){if(x.tag===3){lB(x,h,S);break}else if(x.tag===1){var M=x.stateNode;if(typeof x.type.getDerivedStateFromError=="function"||typeof M.componentDidCatch=="function"&&(_m===null||!_m.has(M))){h=pl(S,h),S=cU(2),M=bm(x,S,2),M!==null&&(uU(S,M,x,h),vt(M,2),qc(M));break}}x=x.return}}function cM(h,x,S){var M=h.pingCache;if(M===null){M=h.pingCache=new Ole;var B=new Set;M.set(x,B)}else B=M.get(x),B===void 0&&(B=new Set,M.set(x,B));B.has(S)||(tM=!0,B.add(S),h=Hle.bind(null,h,x,S),x.then(h,h))}function Hle(h,x,S){var M=h.pingCache;M!==null&&M.delete(x),h.pingedLanes|=h.suspendedLanes&S,h.warmLanes&=~S,Wr===h&&(sr&S)===S&&(Ci===4||Ci===3&&(sr&62914560)===sr&&300>F()-OS?(wr&2)===0&&Eg(h,0):nM|=S,Pg===sr&&(Pg=0)),qc(h)}function cB(h,x){x===0&&(x=nt()),h=up(h,x),h!==null&&(vt(h,x),qc(h))}function $le(h){var x=h.memoizedState,S=0;x!==null&&(S=x.retryLane),cB(h,S)}function qle(h,x){var S=0;switch(h.tag){case 31:case 13:var M=h.stateNode,B=h.memoizedState;B!==null&&(S=B.retryLane);break;case 19:M=h.stateNode;break;case 22:M=h.stateNode._retryCache;break;default:throw Error(r(314))}M!==null&&M.delete(x),cB(h,S)}function Vle(h,x){return be(h,x)}var qS=null,Dg=null,uM=!1,VS=!1,dM=!1,Cm=0;function qc(h){h!==Dg&&h.next===null&&(Dg===null?qS=Dg=h:Dg=Dg.next=h),VS=!0,uM||(uM=!0,Wle())}function Uy(h,x){if(!dM&&VS){dM=!0;do for(var S=!1,M=qS;M!==null;){if(h!==0){var B=M.pendingLanes;if(B===0)var Q=0;else{var Se=M.suspendedLanes,Ue=M.pingedLanes;Q=(1<<31-Be(42|h)+1)-1,Q&=B&~(Se&~Ue),Q=Q&201326741?Q&201326741|1:Q?Q|2:0}Q!==0&&(S=!0,hB(M,Q))}else Q=sr,Q=ke(M,M===Wr?Q:0,M.cancelPendingCommit!==null||M.timeoutHandle!==-1),(Q&3)===0||Ce(M,Q)||(S=!0,hB(M,Q));M=M.next}while(S);dM=!1}}function Gle(){uB()}function uB(){VS=uM=!1;var h=0;Cm!==0&&rce()&&(h=Cm);for(var x=F(),S=null,M=qS;M!==null;){var B=M.next,Q=dB(M,x);Q===0?(M.next=null,S===null?qS=B:S.next=B,B===null&&(Dg=S)):(S=M,(h!==0||(Q&3)!==0)&&(VS=!0)),M=B}ua!==0&&ua!==5||Uy(h),Cm!==0&&(Cm=0)}function dB(h,x){for(var S=h.suspendedLanes,M=h.pingedLanes,B=h.expirationTimes,Q=h.pendingLanes&-62914561;0Ue)break;var Mt=tt.transferSize,Lt=tt.initiatorType;Mt&&wB(Lt)&&(tt=tt.responseEnd,Se+=Mt*(tt"u"?null:document;function DB(h,x,S){var M=Rg;if(M&&typeof x=="string"&&x){var B=lr(x);B='link[rel="'+h+'"][href="'+B+'"]',typeof S=="string"&&(B+='[crossorigin="'+S+'"]'),jB.has(B)||(jB.add(B),h={rel:h,crossOrigin:S,href:x},M.querySelector(B)===null&&(x=M.createElement("link"),Wa(x,"link",h),Ge(x),M.head.appendChild(x)))}}function mce(h){Qu.D(h),DB("dns-prefetch",h,null)}function hce(h,x){Qu.C(h,x),DB("preconnect",h,x)}function pce(h,x,S){Qu.L(h,x,S);var M=Rg;if(M&&h&&x){var B='link[rel="preload"][as="'+lr(x)+'"]';x==="image"&&S&&S.imageSrcSet?(B+='[imagesrcset="'+lr(S.imageSrcSet)+'"]',typeof S.imageSizes=="string"&&(B+='[imagesizes="'+lr(S.imageSizes)+'"]')):B+='[href="'+lr(h)+'"]';var Q=B;switch(x){case"style":Q=Fg(h);break;case"script":Q=Lg(h)}vl.has(Q)||(h=m({rel:"preload",href:x==="image"&&S&&S.imageSrcSet?void 0:h,as:x},S),vl.set(Q,h),M.querySelector(B)!==null||x==="style"&&M.querySelector(qy(Q))||x==="script"&&M.querySelector(Vy(Q))||(x=M.createElement("link"),Wa(x,"link",h),Ge(x),M.head.appendChild(x)))}}function fce(h,x){Qu.m(h,x);var S=Rg;if(S&&h){var M=x&&typeof x.as=="string"?x.as:"script",B='link[rel="modulepreload"][as="'+lr(M)+'"][href="'+lr(h)+'"]',Q=B;switch(M){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":Q=Lg(h)}if(!vl.has(Q)&&(h=m({rel:"modulepreload",href:h},x),vl.set(Q,h),S.querySelector(B)===null)){switch(M){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(S.querySelector(Vy(Q)))return}M=S.createElement("link"),Wa(M,"link",h),Ge(M),S.head.appendChild(M)}}}function gce(h,x,S){Qu.S(h,x,S);var M=Rg;if(M&&h){var B=Ye(M).hoistableStyles,Q=Fg(h);x=x||"default";var Se=B.get(Q);if(!Se){var Ue={loading:0,preload:null};if(Se=M.querySelector(qy(Q)))Ue.loading=5;else{h=m({rel:"stylesheet",href:h,"data-precedence":x},S),(S=vl.get(Q))&&TM(h,S);var tt=Se=M.createElement("link");Ge(tt),Wa(tt,"link",h),tt._p=new Promise(function(St,Mt){tt.onload=St,tt.onerror=Mt}),tt.addEventListener("load",function(){Ue.loading|=1}),tt.addEventListener("error",function(){Ue.loading|=2}),Ue.loading|=4,XS(Se,x,M)}Se={type:"stylesheet",instance:Se,count:1,state:Ue},B.set(Q,Se)}}}function bce(h,x){Qu.X(h,x);var S=Rg;if(S&&h){var M=Ye(S).hoistableScripts,B=Lg(h),Q=M.get(B);Q||(Q=S.querySelector(Vy(B)),Q||(h=m({src:h,async:!0},x),(x=vl.get(B))&&AM(h,x),Q=S.createElement("script"),Ge(Q),Wa(Q,"link",h),S.head.appendChild(Q)),Q={type:"script",instance:Q,count:1,state:null},M.set(B,Q))}}function xce(h,x){Qu.M(h,x);var S=Rg;if(S&&h){var M=Ye(S).hoistableScripts,B=Lg(h),Q=M.get(B);Q||(Q=S.querySelector(Vy(B)),Q||(h=m({src:h,async:!0,type:"module"},x),(x=vl.get(B))&&AM(h,x),Q=S.createElement("script"),Ge(Q),Wa(Q,"link",h),S.head.appendChild(Q)),Q={type:"script",instance:Q,count:1,state:null},M.set(B,Q))}}function RB(h,x,S,M){var B=(B=q.current)?YS(B):null;if(!B)throw Error(r(446));switch(h){case"meta":case"title":return null;case"style":return typeof S.precedence=="string"&&typeof S.href=="string"?(x=Fg(S.href),S=Ye(B).hoistableStyles,M=S.get(x),M||(M={type:"style",instance:null,count:0,state:null},S.set(x,M)),M):{type:"void",instance:null,count:0,state:null};case"link":if(S.rel==="stylesheet"&&typeof S.href=="string"&&typeof S.precedence=="string"){h=Fg(S.href);var Q=Ye(B).hoistableStyles,Se=Q.get(h);if(Se||(B=B.ownerDocument||B,Se={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},Q.set(h,Se),(Q=B.querySelector(qy(h)))&&!Q._p&&(Se.instance=Q,Se.state.loading=5),vl.has(h)||(S={rel:"preload",as:"style",href:S.href,crossOrigin:S.crossOrigin,integrity:S.integrity,media:S.media,hrefLang:S.hrefLang,referrerPolicy:S.referrerPolicy},vl.set(h,S),Q||yce(B,h,S,Se.state))),x&&M===null)throw Error(r(528,""));return Se}if(x&&M!==null)throw Error(r(529,""));return null;case"script":return x=S.async,S=S.src,typeof S=="string"&&x&&typeof x!="function"&&typeof x!="symbol"?(x=Lg(S),S=Ye(B).hoistableScripts,M=S.get(x),M||(M={type:"script",instance:null,count:0,state:null},S.set(x,M)),M):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,h))}}function Fg(h){return'href="'+lr(h)+'"'}function qy(h){return'link[rel="stylesheet"]['+h+"]"}function FB(h){return m({},h,{"data-precedence":h.precedence,precedence:null})}function yce(h,x,S,M){h.querySelector('link[rel="preload"][as="style"]['+x+"]")?M.loading=1:(x=h.createElement("link"),M.preload=x,x.addEventListener("load",function(){return M.loading|=1}),x.addEventListener("error",function(){return M.loading|=2}),Wa(x,"link",S),Ge(x),h.head.appendChild(x))}function Lg(h){return'[src="'+lr(h)+'"]'}function Vy(h){return"script[async]"+h}function LB(h,x,S){if(x.count++,x.instance===null)switch(x.type){case"style":var M=h.querySelector('style[data-href~="'+lr(S.href)+'"]');if(M)return x.instance=M,Ge(M),M;var B=m({},S,{"data-href":S.href,"data-precedence":S.precedence,href:null,precedence:null});return M=(h.ownerDocument||h).createElement("style"),Ge(M),Wa(M,"style",B),XS(M,S.precedence,h),x.instance=M;case"stylesheet":B=Fg(S.href);var Q=h.querySelector(qy(B));if(Q)return x.state.loading|=4,x.instance=Q,Ge(Q),Q;M=FB(S),(B=vl.get(B))&&TM(M,B),Q=(h.ownerDocument||h).createElement("link"),Ge(Q);var Se=Q;return Se._p=new Promise(function(Ue,tt){Se.onload=Ue,Se.onerror=tt}),Wa(Q,"link",M),x.state.loading|=4,XS(Q,S.precedence,h),x.instance=Q;case"script":return Q=Lg(S.src),(B=h.querySelector(Vy(Q)))?(x.instance=B,Ge(B),B):(M=S,(B=vl.get(Q))&&(M=m({},S),AM(M,B)),h=h.ownerDocument||h,B=h.createElement("script"),Ge(B),Wa(B,"link",M),h.head.appendChild(B),x.instance=B);case"void":return null;default:throw Error(r(443,x.type))}else x.type==="stylesheet"&&(x.state.loading&4)===0&&(M=x.instance,x.state.loading|=4,XS(M,S.precedence,h));return x.instance}function XS(h,x,S){for(var M=S.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),B=M.length?M[M.length-1]:null,Q=B,Se=0;Se title"):null)}function vce(h,x,S){if(S===1||x.itemProp!=null)return!1;switch(h){case"meta":case"title":return!0;case"style":if(typeof x.precedence!="string"||typeof x.href!="string"||x.href==="")break;return!0;case"link":if(typeof x.rel!="string"||typeof x.href!="string"||x.href===""||x.onLoad||x.onError)break;return x.rel==="stylesheet"?(h=x.disabled,typeof x.precedence=="string"&&h==null):!0;case"script":if(x.async&&typeof x.async!="function"&&typeof x.async!="symbol"&&!x.onLoad&&!x.onError&&x.src&&typeof x.src=="string")return!0}return!1}function zB(h){return!(h.type==="stylesheet"&&(h.state.loading&3)===0)}function wce(h,x,S,M){if(S.type==="stylesheet"&&(typeof M.media!="string"||matchMedia(M.media).matches!==!1)&&(S.state.loading&4)===0){if(S.instance===null){var B=Fg(M.href),Q=x.querySelector(qy(B));if(Q){x=Q._p,x!==null&&typeof x=="object"&&typeof x.then=="function"&&(h.count++,h=ZS.bind(h),x.then(h,h)),S.state.loading|=4,S.instance=Q,Ge(Q);return}Q=x.ownerDocument||x,M=FB(M),(B=vl.get(B))&&TM(M,B),Q=Q.createElement("link"),Ge(Q);var Se=Q;Se._p=new Promise(function(Ue,tt){Se.onload=Ue,Se.onerror=tt}),Wa(Q,"link",M),S.instance=Q}h.stylesheets===null&&(h.stylesheets=new Map),h.stylesheets.set(S,x),(x=S.state.preload)&&(S.state.loading&3)===0&&(h.count++,S=ZS.bind(h),x.addEventListener("load",S),x.addEventListener("error",S))}}var PM=0;function Sce(h,x){return h.stylesheets&&h.count===0&&e_(h,h.stylesheets),0PM?50:800)+x);return h.unsuspend=S,function(){h.unsuspend=null,clearTimeout(M),clearTimeout(B)}}:null}function ZS(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)e_(this,this.stylesheets);else if(this.unsuspend){var h=this.unsuspend;this.unsuspend=null,h()}}}var JS=null;function e_(h,x){h.stylesheets=null,h.unsuspend!==null&&(h.count++,JS=new Map,x.forEach(_ce,h),JS=null,ZS.call(h))}function _ce(h,x){if(!(x.state.loading&4)){var S=JS.get(h);if(S)var M=S.get(null);else{S=new Map,JS.set(h,S);for(var B=h.querySelectorAll("link[data-precedence],style[data-precedence]"),Q=0;Q"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),IM.exports=Uce(),IM.exports}var Hce=Bce();const Mn=t=>typeof t=="string",Zy=()=>{let t,e;const n=new Promise((r,a)=>{t=r,e=a});return n.resolve=t,n.reject=e,n},c8=t=>t==null?"":""+t,$ce=(t,e,n)=>{t.forEach(r=>{e[r]&&(n[r]=e[r])})},qce=/###/g,u8=t=>t&&t.indexOf("###")>-1?t.replace(qce,"."):t,d8=t=>!t||Mn(t),Gv=(t,e,n)=>{const r=Mn(e)?e.split("."):e;let a=0;for(;a{const{obj:r,k:a}=Gv(t,e,Object);if(r!==void 0||e.length===1){r[a]=n;return}let s=e[e.length-1],o=e.slice(0,e.length-1),l=Gv(t,o,Object);for(;l.obj===void 0&&o.length;)s=`${o[o.length-1]}.${s}`,o=o.slice(0,o.length-1),l=Gv(t,o,Object),l?.obj&&typeof l.obj[`${l.k}.${s}`]<"u"&&(l.obj=void 0);l.obj[`${l.k}.${s}`]=n},Vce=(t,e,n,r)=>{const{obj:a,k:s}=Gv(t,e,Object);a[s]=a[s]||[],a[s].push(n)},MN=(t,e)=>{const{obj:n,k:r}=Gv(t,e);if(n&&Object.prototype.hasOwnProperty.call(n,r))return n[r]},Gce=(t,e,n)=>{const r=MN(t,n);return r!==void 0?r:MN(e,n)},QY=(t,e,n)=>{for(const r in e)r!=="__proto__"&&r!=="constructor"&&(r in t?Mn(t[r])||t[r]instanceof String||Mn(e[r])||e[r]instanceof String?n&&(t[r]=e[r]):QY(t[r],e[r],n):t[r]=e[r]);return t},Ig=t=>t.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&");var Wce={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};const Kce=t=>Mn(t)?t.replace(/[&<>"'\/]/g,e=>Wce[e]):t;class Yce{constructor(e){this.capacity=e,this.regExpMap=new Map,this.regExpQueue=[]}getRegExp(e){const n=this.regExpMap.get(e);if(n!==void 0)return n;const r=new RegExp(e);return this.regExpQueue.length===this.capacity&&this.regExpMap.delete(this.regExpQueue.shift()),this.regExpMap.set(e,r),this.regExpQueue.push(e),r}}const Xce=[" ",",","?","!",";"],Qce=new Yce(20),Zce=(t,e,n)=>{e=e||"",n=n||"";const r=Xce.filter(o=>e.indexOf(o)<0&&n.indexOf(o)<0);if(r.length===0)return!0;const a=Qce.getRegExp(`(${r.map(o=>o==="?"?"\\?":o).join("|")})`);let s=!a.test(t);if(!s){const o=t.indexOf(n);o>0&&!a.test(t.substring(0,o))&&(s=!0)}return s},a3=(t,e,n=".")=>{if(!t)return;if(t[e])return Object.prototype.hasOwnProperty.call(t,e)?t[e]:void 0;const r=e.split(n);let a=t;for(let s=0;s-1&&ct?.replace("_","-"),Jce={type:"logger",log(t){this.output("log",t)},warn(t){this.output("warn",t)},error(t){this.output("error",t)},output(t,e){console?.[t]?.apply?.(console,e)}};class EN{constructor(e,n={}){this.init(e,n)}init(e,n={}){this.prefix=n.prefix||"i18next:",this.logger=e||Jce,this.options=n,this.debug=n.debug}log(...e){return this.forward(e,"log","",!0)}warn(...e){return this.forward(e,"warn","",!0)}error(...e){return this.forward(e,"error","")}deprecate(...e){return this.forward(e,"warn","WARNING DEPRECATED: ",!0)}forward(e,n,r,a){return a&&!this.debug?null:(Mn(e[0])&&(e[0]=`${r}${this.prefix} ${e[0]}`),this.logger[n](e))}create(e){return new EN(this.logger,{prefix:`${this.prefix}:${e}:`,...this.options})}clone(e){return e=e||this.options,e.prefix=e.prefix||this.prefix,new EN(this.logger,e)}}var ou=new EN;let eT=class{constructor(){this.observers={}}on(e,n){return e.split(" ").forEach(r=>{this.observers[r]||(this.observers[r]=new Map);const a=this.observers[r].get(n)||0;this.observers[r].set(n,a+1)}),this}off(e,n){if(this.observers[e]){if(!n){delete this.observers[e];return}this.observers[e].delete(n)}}emit(e,...n){this.observers[e]&&Array.from(this.observers[e].entries()).forEach(([a,s])=>{for(let o=0;o{for(let o=0;o-1&&this.options.ns.splice(n,1)}getResource(e,n,r,a={}){const s=a.keySeparator!==void 0?a.keySeparator:this.options.keySeparator,o=a.ignoreJSONStructure!==void 0?a.ignoreJSONStructure:this.options.ignoreJSONStructure;let l;e.indexOf(".")>-1?l=e.split("."):(l=[e,n],r&&(Array.isArray(r)?l.push(...r):Mn(r)&&s?l.push(...r.split(s)):l.push(r)));const c=MN(this.data,l);return!c&&!n&&!r&&e.indexOf(".")>-1&&(e=l[0],n=l[1],r=l.slice(2).join(".")),c||!o||!Mn(r)?c:a3(this.data?.[e]?.[n],r,s)}addResource(e,n,r,a,s={silent:!1}){const o=s.keySeparator!==void 0?s.keySeparator:this.options.keySeparator;let l=[e,n];r&&(l=l.concat(o?r.split(o):r)),e.indexOf(".")>-1&&(l=e.split("."),a=n,n=l[1]),this.addNamespaces(n),m8(this.data,l,a),s.silent||this.emit("added",e,n,r,a)}addResources(e,n,r,a={silent:!1}){for(const s in r)(Mn(r[s])||Array.isArray(r[s]))&&this.addResource(e,n,s,r[s],{silent:!0});a.silent||this.emit("added",e,n,r)}addResourceBundle(e,n,r,a,s,o={silent:!1,skipCopy:!1}){let l=[e,n];e.indexOf(".")>-1&&(l=e.split("."),a=r,r=n,n=l[1]),this.addNamespaces(n);let c=MN(this.data,l)||{};o.skipCopy||(r=JSON.parse(JSON.stringify(r))),a?QY(c,r,s):c={...c,...r},m8(this.data,l,c),o.silent||this.emit("added",e,n,r)}removeResourceBundle(e,n){this.hasResourceBundle(e,n)&&delete this.data[e][n],this.removeNamespaces(n),this.emit("removed",e,n)}hasResourceBundle(e,n){return this.getResource(e,n)!==void 0}getResourceBundle(e,n){return n||(n=this.options.defaultNS),this.getResource(e,n)}getDataByLanguage(e){return this.data[e]}hasLanguageSomeTranslations(e){const n=this.getDataByLanguage(e);return!!(n&&Object.keys(n)||[]).find(a=>n[a]&&Object.keys(n[a]).length>0)}toJSON(){return this.data}}var ZY={processors:{},addPostProcessor(t){this.processors[t.name]=t},handle(t,e,n,r,a){return t.forEach(s=>{e=this.processors[s]?.process(e,n,r,a)??e}),e}};const JY=Symbol("i18next/PATH_KEY");function eue(){const t=[],e=Object.create(null);let n;return e.get=(r,a)=>(n?.revoke?.(),a===JY?t:(t.push(a),n=Proxy.revocable(r,e),n.proxy)),Proxy.revocable(Object.create(null),e).proxy}function s3(t,e){const{[JY]:n}=t(eue());return n.join(e?.keySeparator??".")}const p8={},HM=t=>!Mn(t)&&typeof t!="boolean"&&typeof t!="number";class jN extends eT{constructor(e,n={}){super(),$ce(["resourceStore","languageUtils","pluralResolver","interpolator","backendConnector","i18nFormat","utils"],e,this),this.options=n,this.options.keySeparator===void 0&&(this.options.keySeparator="."),this.logger=ou.create("translator")}changeLanguage(e){e&&(this.language=e)}exists(e,n={interpolation:{}}){const r={...n};if(e==null)return!1;const a=this.resolve(e,r);if(a?.res===void 0)return!1;const s=HM(a.res);return!(r.returnObjects===!1&&s)}extractFromKey(e,n){let r=n.nsSeparator!==void 0?n.nsSeparator:this.options.nsSeparator;r===void 0&&(r=":");const a=n.keySeparator!==void 0?n.keySeparator:this.options.keySeparator;let s=n.ns||this.options.defaultNS||[];const o=r&&e.indexOf(r)>-1,l=!this.options.userDefinedKeySeparator&&!n.keySeparator&&!this.options.userDefinedNsSeparator&&!n.nsSeparator&&!Zce(e,r,a);if(o&&!l){const c=e.match(this.interpolator.nestingRegexp);if(c&&c.length>0)return{key:e,namespaces:Mn(s)?[s]:s};const u=e.split(r);(r!==a||r===a&&this.options.ns.indexOf(u[0])>-1)&&(s=u.shift()),e=u.join(a)}return{key:e,namespaces:Mn(s)?[s]:s}}translate(e,n,r){let a=typeof n=="object"?{...n}:n;if(typeof a!="object"&&this.options.overloadTranslationOptionHandler&&(a=this.options.overloadTranslationOptionHandler(arguments)),typeof a=="object"&&(a={...a}),a||(a={}),e==null)return"";typeof e=="function"&&(e=s3(e,{...this.options,...a})),Array.isArray(e)||(e=[String(e)]);const s=a.returnDetails!==void 0?a.returnDetails:this.options.returnDetails,o=a.keySeparator!==void 0?a.keySeparator:this.options.keySeparator,{key:l,namespaces:c}=this.extractFromKey(e[e.length-1],a),u=c[c.length-1];let d=a.nsSeparator!==void 0?a.nsSeparator:this.options.nsSeparator;d===void 0&&(d=":");const m=a.lng||this.language,p=a.appendNamespaceToCIMode||this.options.appendNamespaceToCIMode;if(m?.toLowerCase()==="cimode")return p?s?{res:`${u}${d}${l}`,usedKey:l,exactUsedKey:l,usedLng:m,usedNS:u,usedParams:this.getUsedParamsDetails(a)}:`${u}${d}${l}`:s?{res:l,usedKey:l,exactUsedKey:l,usedLng:m,usedNS:u,usedParams:this.getUsedParamsDetails(a)}:l;const f=this.resolve(e,a);let y=f?.res;const v=f?.usedKey||l,b=f?.exactUsedKey||l,g=["[object Number]","[object Function]","[object RegExp]"],_=a.joinArrays!==void 0?a.joinArrays:this.options.joinArrays,N=!this.i18nFormat||this.i18nFormat.handleAsObject,C=a.count!==void 0&&!Mn(a.count),k=jN.hasDefaultValue(a),P=C?this.pluralResolver.getSuffix(m,a.count,a):"",T=a.ordinal&&C?this.pluralResolver.getSuffix(m,a.count,{ordinal:!1}):"",D=C&&!a.ordinal&&a.count===0,A=D&&a[`defaultValue${this.options.pluralSeparator}zero`]||a[`defaultValue${P}`]||a[`defaultValue${T}`]||a.defaultValue;let R=y;N&&!y&&k&&(R=A);const G=HM(R),z=Object.prototype.toString.apply(R);if(N&&R&&G&&g.indexOf(z)<0&&!(Mn(_)&&Array.isArray(R))){if(!a.returnObjects&&!this.options.returnObjects){this.options.returnedObjectHandler||this.logger.warn("accessing an object - but returnObjects options is not enabled!");const Z=this.options.returnedObjectHandler?this.options.returnedObjectHandler(v,R,{...a,ns:c}):`key '${l} (${this.language})' returned an object instead of string.`;return s?(f.res=Z,f.usedParams=this.getUsedParamsDetails(a),f):Z}if(o){const Z=Array.isArray(R),I=Z?[]:{},J=Z?b:v;for(const ae in R)if(Object.prototype.hasOwnProperty.call(R,ae)){const Y=`${J}${o}${ae}`;k&&!y?I[ae]=this.translate(Y,{...a,defaultValue:HM(A)?A[ae]:void 0,joinArrays:!1,ns:c}):I[ae]=this.translate(Y,{...a,joinArrays:!1,ns:c}),I[ae]===Y&&(I[ae]=R[ae])}y=I}}else if(N&&Mn(_)&&Array.isArray(y))y=y.join(_),y&&(y=this.extendTranslation(y,e,a,r));else{let Z=!1,I=!1;!this.isValidLookup(y)&&k&&(Z=!0,y=A),this.isValidLookup(y)||(I=!0,y=l);const ae=(a.missingKeyNoValueFallbackToKey||this.options.missingKeyNoValueFallbackToKey)&&I?void 0:y,Y=k&&A!==y&&this.options.updateMissing;if(I||Z||Y){if(this.logger.log(Y?"updateKey":"missingKey",m,u,l,Y?A:y),o){const V=this.resolve(l,{...a,keySeparator:!1});V&&V.res&&this.logger.warn("Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.")}let ue=[];const de=this.languageUtils.getFallbackCodes(this.options.fallbackLng,a.lng||this.language);if(this.options.saveMissingTo==="fallback"&&de&&de[0])for(let V=0;V{const pe=k&&se!==y?se:ae;this.options.missingKeyHandler?this.options.missingKeyHandler(V,u,ne,pe,Y,a):this.backendConnector?.saveMissing&&this.backendConnector.saveMissing(V,u,ne,pe,Y,a),this.emit("missingKey",V,u,ne,y)};this.options.saveMissing&&(this.options.saveMissingPlurals&&C?ue.forEach(V=>{const ne=this.pluralResolver.getSuffixes(V,a);D&&a[`defaultValue${this.options.pluralSeparator}zero`]&&ne.indexOf(`${this.options.pluralSeparator}zero`)<0&&ne.push(`${this.options.pluralSeparator}zero`),ne.forEach(se=>{he([V],l+se,a[`defaultValue${se}`]||A)})}):he(ue,l,A))}y=this.extendTranslation(y,e,a,f,r),I&&y===l&&this.options.appendNamespaceToMissingKey&&(y=`${u}${d}${l}`),(I||Z)&&this.options.parseMissingKeyHandler&&(y=this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey?`${u}${d}${l}`:l,Z?y:void 0,a))}return s?(f.res=y,f.usedParams=this.getUsedParamsDetails(a),f):y}extendTranslation(e,n,r,a,s){if(this.i18nFormat?.parse)e=this.i18nFormat.parse(e,{...this.options.interpolation.defaultVariables,...r},r.lng||this.language||a.usedLng,a.usedNS,a.usedKey,{resolved:a});else if(!r.skipInterpolation){r.interpolation&&this.interpolator.init({...r,interpolation:{...this.options.interpolation,...r.interpolation}});const c=Mn(e)&&(r?.interpolation?.skipOnVariables!==void 0?r.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables);let u;if(c){const m=e.match(this.interpolator.nestingRegexp);u=m&&m.length}let d=r.replace&&!Mn(r.replace)?r.replace:r;if(this.options.interpolation.defaultVariables&&(d={...this.options.interpolation.defaultVariables,...d}),e=this.interpolator.interpolate(e,d,r.lng||this.language||a.usedLng,r),c){const m=e.match(this.interpolator.nestingRegexp),p=m&&m.length;us?.[0]===m[0]&&!r.context?(this.logger.warn(`It seems you are nesting recursively key: ${m[0]} in key: ${n[0]}`),null):this.translate(...m,n),r)),r.interpolation&&this.interpolator.reset()}const o=r.postProcess||this.options.postProcess,l=Mn(o)?[o]:o;return e!=null&&l?.length&&r.applyPostProcessor!==!1&&(e=ZY.handle(l,e,n,this.options&&this.options.postProcessPassResolved?{i18nResolved:{...a,usedParams:this.getUsedParamsDetails(r)},...r}:r,this)),e}resolve(e,n={}){let r,a,s,o,l;return Mn(e)&&(e=[e]),e.forEach(c=>{if(this.isValidLookup(r))return;const u=this.extractFromKey(c,n),d=u.key;a=d;let m=u.namespaces;this.options.fallbackNS&&(m=m.concat(this.options.fallbackNS));const p=n.count!==void 0&&!Mn(n.count),f=p&&!n.ordinal&&n.count===0,y=n.context!==void 0&&(Mn(n.context)||typeof n.context=="number")&&n.context!=="",v=n.lngs?n.lngs:this.languageUtils.toResolveHierarchy(n.lng||this.language,n.fallbackLng);m.forEach(b=>{this.isValidLookup(r)||(l=b,!p8[`${v[0]}-${b}`]&&this.utils?.hasLoadedNamespace&&!this.utils?.hasLoadedNamespace(l)&&(p8[`${v[0]}-${b}`]=!0,this.logger.warn(`key "${a}" for languages "${v.join(", ")}" won't get resolved as namespace "${l}" was not yet loaded`,"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!")),v.forEach(g=>{if(this.isValidLookup(r))return;o=g;const _=[d];if(this.i18nFormat?.addLookupKeys)this.i18nFormat.addLookupKeys(_,d,g,b,n);else{let C;p&&(C=this.pluralResolver.getSuffix(g,n.count,n));const k=`${this.options.pluralSeparator}zero`,P=`${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;if(p&&(n.ordinal&&C.indexOf(P)===0&&_.push(d+C.replace(P,this.options.pluralSeparator)),_.push(d+C),f&&_.push(d+k)),y){const T=`${d}${this.options.contextSeparator||"_"}${n.context}`;_.push(T),p&&(n.ordinal&&C.indexOf(P)===0&&_.push(T+C.replace(P,this.options.pluralSeparator)),_.push(T+C),f&&_.push(T+k))}}let N;for(;N=_.pop();)this.isValidLookup(r)||(s=N,r=this.getResource(g,b,N,n))}))})}),{res:r,usedKey:a,exactUsedKey:s,usedLng:o,usedNS:l}}isValidLookup(e){return e!==void 0&&!(!this.options.returnNull&&e===null)&&!(!this.options.returnEmptyString&&e==="")}getResource(e,n,r,a={}){return this.i18nFormat?.getResource?this.i18nFormat.getResource(e,n,r,a):this.resourceStore.getResource(e,n,r,a)}getUsedParamsDetails(e={}){const n=["defaultValue","ordinal","context","replace","lng","lngs","fallbackLng","ns","keySeparator","nsSeparator","returnObjects","returnDetails","joinArrays","postProcess","interpolation"],r=e.replace&&!Mn(e.replace);let a=r?e.replace:e;if(r&&typeof e.count<"u"&&(a.count=e.count),this.options.interpolation.defaultVariables&&(a={...this.options.interpolation.defaultVariables,...a}),!r){a={...a};for(const s of n)delete a[s]}return a}static hasDefaultValue(e){const n="defaultValue";for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)&&n===r.substring(0,n.length)&&e[r]!==void 0)return!0;return!1}}class f8{constructor(e){this.options=e,this.supportedLngs=this.options.supportedLngs||!1,this.logger=ou.create("languageUtils")}getScriptPartFromCode(e){if(e=x0(e),!e||e.indexOf("-")<0)return null;const n=e.split("-");return n.length===2||(n.pop(),n[n.length-1].toLowerCase()==="x")?null:this.formatLanguageCode(n.join("-"))}getLanguagePartFromCode(e){if(e=x0(e),!e||e.indexOf("-")<0)return e;const n=e.split("-");return this.formatLanguageCode(n[0])}formatLanguageCode(e){if(Mn(e)&&e.indexOf("-")>-1){let n;try{n=Intl.getCanonicalLocales(e)[0]}catch{}return n&&this.options.lowerCaseLng&&(n=n.toLowerCase()),n||(this.options.lowerCaseLng?e.toLowerCase():e)}return this.options.cleanCode||this.options.lowerCaseLng?e.toLowerCase():e}isSupportedCode(e){return(this.options.load==="languageOnly"||this.options.nonExplicitSupportedLngs)&&(e=this.getLanguagePartFromCode(e)),!this.supportedLngs||!this.supportedLngs.length||this.supportedLngs.indexOf(e)>-1}getBestMatchFromCodes(e){if(!e)return null;let n;return e.forEach(r=>{if(n)return;const a=this.formatLanguageCode(r);(!this.options.supportedLngs||this.isSupportedCode(a))&&(n=a)}),!n&&this.options.supportedLngs&&e.forEach(r=>{if(n)return;const a=this.getScriptPartFromCode(r);if(this.isSupportedCode(a))return n=a;const s=this.getLanguagePartFromCode(r);if(this.isSupportedCode(s))return n=s;n=this.options.supportedLngs.find(o=>{if(o===s)return o;if(!(o.indexOf("-")<0&&s.indexOf("-")<0)&&(o.indexOf("-")>0&&s.indexOf("-")<0&&o.substring(0,o.indexOf("-"))===s||o.indexOf(s)===0&&s.length>1))return o})}),n||(n=this.getFallbackCodes(this.options.fallbackLng)[0]),n}getFallbackCodes(e,n){if(!e)return[];if(typeof e=="function"&&(e=e(n)),Mn(e)&&(e=[e]),Array.isArray(e))return e;if(!n)return e.default||[];let r=e[n];return r||(r=e[this.getScriptPartFromCode(n)]),r||(r=e[this.formatLanguageCode(n)]),r||(r=e[this.getLanguagePartFromCode(n)]),r||(r=e.default),r||[]}toResolveHierarchy(e,n){const r=this.getFallbackCodes((n===!1?[]:n)||this.options.fallbackLng||[],e),a=[],s=o=>{o&&(this.isSupportedCode(o)?a.push(o):this.logger.warn(`rejecting language code not found in supportedLngs: ${o}`))};return Mn(e)&&(e.indexOf("-")>-1||e.indexOf("_")>-1)?(this.options.load!=="languageOnly"&&s(this.formatLanguageCode(e)),this.options.load!=="languageOnly"&&this.options.load!=="currentOnly"&&s(this.getScriptPartFromCode(e)),this.options.load!=="currentOnly"&&s(this.getLanguagePartFromCode(e))):Mn(e)&&s(this.formatLanguageCode(e)),r.forEach(o=>{a.indexOf(o)<0&&s(this.formatLanguageCode(o))}),a}}const g8={zero:0,one:1,two:2,few:3,many:4,other:5},b8={select:t=>t===1?"one":"other",resolvedOptions:()=>({pluralCategories:["one","other"]})};class tue{constructor(e,n={}){this.languageUtils=e,this.options=n,this.logger=ou.create("pluralResolver"),this.pluralRulesCache={}}addRule(e,n){this.rules[e]=n}clearCache(){this.pluralRulesCache={}}getRule(e,n={}){const r=x0(e==="dev"?"en":e),a=n.ordinal?"ordinal":"cardinal",s=JSON.stringify({cleanedCode:r,type:a});if(s in this.pluralRulesCache)return this.pluralRulesCache[s];let o;try{o=new Intl.PluralRules(r,{type:a})}catch{if(!Intl)return this.logger.error("No Intl support, please use an Intl polyfill!"),b8;if(!e.match(/-|_/))return b8;const c=this.languageUtils.getLanguagePartFromCode(e);o=this.getRule(c,n)}return this.pluralRulesCache[s]=o,o}needsPlural(e,n={}){let r=this.getRule(e,n);return r||(r=this.getRule("dev",n)),r?.resolvedOptions().pluralCategories.length>1}getPluralFormsOfKey(e,n,r={}){return this.getSuffixes(e,r).map(a=>`${n}${a}`)}getSuffixes(e,n={}){let r=this.getRule(e,n);return r||(r=this.getRule("dev",n)),r?r.resolvedOptions().pluralCategories.sort((a,s)=>g8[a]-g8[s]).map(a=>`${this.options.prepend}${n.ordinal?`ordinal${this.options.prepend}`:""}${a}`):[]}getSuffix(e,n,r={}){const a=this.getRule(e,r);return a?`${this.options.prepend}${r.ordinal?`ordinal${this.options.prepend}`:""}${a.select(n)}`:(this.logger.warn(`no plural rule found for: ${e}`),this.getSuffix("dev",n,r))}}const x8=(t,e,n,r=".",a=!0)=>{let s=Gce(t,e,n);return!s&&a&&Mn(n)&&(s=a3(t,n,r),s===void 0&&(s=a3(e,n,r))),s},$M=t=>t.replace(/\$/g,"$$$$");class nue{constructor(e={}){this.logger=ou.create("interpolator"),this.options=e,this.format=e?.interpolation?.format||(n=>n),this.init(e)}init(e={}){e.interpolation||(e.interpolation={escapeValue:!0});const{escape:n,escapeValue:r,useRawValueToEscape:a,prefix:s,prefixEscaped:o,suffix:l,suffixEscaped:c,formatSeparator:u,unescapeSuffix:d,unescapePrefix:m,nestingPrefix:p,nestingPrefixEscaped:f,nestingSuffix:y,nestingSuffixEscaped:v,nestingOptionsSeparator:b,maxReplaces:g,alwaysFormat:_}=e.interpolation;this.escape=n!==void 0?n:Kce,this.escapeValue=r!==void 0?r:!0,this.useRawValueToEscape=a!==void 0?a:!1,this.prefix=s?Ig(s):o||"{{",this.suffix=l?Ig(l):c||"}}",this.formatSeparator=u||",",this.unescapePrefix=d?"":m||"-",this.unescapeSuffix=this.unescapePrefix?"":d||"",this.nestingPrefix=p?Ig(p):f||Ig("$t("),this.nestingSuffix=y?Ig(y):v||Ig(")"),this.nestingOptionsSeparator=b||",",this.maxReplaces=g||1e3,this.alwaysFormat=_!==void 0?_:!1,this.resetRegExp()}reset(){this.options&&this.init(this.options)}resetRegExp(){const e=(n,r)=>n?.source===r?(n.lastIndex=0,n):new RegExp(r,"g");this.regexp=e(this.regexp,`${this.prefix}(.+?)${this.suffix}`),this.regexpUnescape=e(this.regexpUnescape,`${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`),this.nestingRegexp=e(this.nestingRegexp,`${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`)}interpolate(e,n,r,a){let s,o,l;const c=this.options&&this.options.interpolation&&this.options.interpolation.defaultVariables||{},u=f=>{if(f.indexOf(this.formatSeparator)<0){const g=x8(n,c,f,this.options.keySeparator,this.options.ignoreJSONStructure);return this.alwaysFormat?this.format(g,void 0,r,{...a,...n,interpolationkey:f}):g}const y=f.split(this.formatSeparator),v=y.shift().trim(),b=y.join(this.formatSeparator).trim();return this.format(x8(n,c,v,this.options.keySeparator,this.options.ignoreJSONStructure),b,r,{...a,...n,interpolationkey:v})};this.resetRegExp();const d=a?.missingInterpolationHandler||this.options.missingInterpolationHandler,m=a?.interpolation?.skipOnVariables!==void 0?a.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables;return[{regex:this.regexpUnescape,safeValue:f=>$M(f)},{regex:this.regexp,safeValue:f=>this.escapeValue?$M(this.escape(f)):$M(f)}].forEach(f=>{for(l=0;s=f.regex.exec(e);){const y=s[1].trim();if(o=u(y),o===void 0)if(typeof d=="function"){const b=d(e,s,a);o=Mn(b)?b:""}else if(a&&Object.prototype.hasOwnProperty.call(a,y))o="";else if(m){o=s[0];continue}else this.logger.warn(`missed to pass in variable ${y} for interpolating ${e}`),o="";else!Mn(o)&&!this.useRawValueToEscape&&(o=c8(o));const v=f.safeValue(o);if(e=e.replace(s[0],v),m?(f.regex.lastIndex+=o.length,f.regex.lastIndex-=s[0].length):f.regex.lastIndex=0,l++,l>=this.maxReplaces)break}}),e}nest(e,n,r={}){let a,s,o;const l=(c,u)=>{const d=this.nestingOptionsSeparator;if(c.indexOf(d)<0)return c;const m=c.split(new RegExp(`${d}[ ]*{`));let p=`{${m[1]}`;c=m[0],p=this.interpolate(p,o);const f=p.match(/'/g),y=p.match(/"/g);((f?.length??0)%2===0&&!y||y.length%2!==0)&&(p=p.replace(/'/g,'"'));try{o=JSON.parse(p),u&&(o={...u,...o})}catch(v){return this.logger.warn(`failed parsing options string in nesting for key ${c}`,v),`${c}${d}${p}`}return o.defaultValue&&o.defaultValue.indexOf(this.prefix)>-1&&delete o.defaultValue,c};for(;a=this.nestingRegexp.exec(e);){let c=[];o={...r},o=o.replace&&!Mn(o.replace)?o.replace:o,o.applyPostProcessor=!1,delete o.defaultValue;const u=/{.*}/.test(a[1])?a[1].lastIndexOf("}")+1:a[1].indexOf(this.formatSeparator);if(u!==-1&&(c=a[1].slice(u).split(this.formatSeparator).map(d=>d.trim()).filter(Boolean),a[1]=a[1].slice(0,u)),s=n(l.call(this,a[1].trim(),o),o),s&&a[0]===e&&!Mn(s))return s;Mn(s)||(s=c8(s)),s||(this.logger.warn(`missed to resolve ${a[1]} for nesting ${e}`),s=""),c.length&&(s=c.reduce((d,m)=>this.format(d,m,r.lng,{...r,interpolationkey:a[1].trim()}),s.trim())),e=e.replace(a[0],s),this.regexp.lastIndex=0}return e}}const rue=t=>{let e=t.toLowerCase().trim();const n={};if(t.indexOf("(")>-1){const r=t.split("(");e=r[0].toLowerCase().trim();const a=r[1].substring(0,r[1].length-1);e==="currency"&&a.indexOf(":")<0?n.currency||(n.currency=a.trim()):e==="relativetime"&&a.indexOf(":")<0?n.range||(n.range=a.trim()):a.split(";").forEach(o=>{if(o){const[l,...c]=o.split(":"),u=c.join(":").trim().replace(/^'+|'+$/g,""),d=l.trim();n[d]||(n[d]=u),u==="false"&&(n[d]=!1),u==="true"&&(n[d]=!0),isNaN(u)||(n[d]=parseInt(u,10))}})}return{formatName:e,formatOptions:n}},y8=t=>{const e={};return(n,r,a)=>{let s=a;a&&a.interpolationkey&&a.formatParams&&a.formatParams[a.interpolationkey]&&a[a.interpolationkey]&&(s={...s,[a.interpolationkey]:void 0});const o=r+JSON.stringify(s);let l=e[o];return l||(l=t(x0(r),a),e[o]=l),l(n)}},iue=t=>(e,n,r)=>t(x0(n),r)(e);class aue{constructor(e={}){this.logger=ou.create("formatter"),this.options=e,this.init(e)}init(e,n={interpolation:{}}){this.formatSeparator=n.interpolation.formatSeparator||",";const r=n.cacheInBuiltFormats?y8:iue;this.formats={number:r((a,s)=>{const o=new Intl.NumberFormat(a,{...s});return l=>o.format(l)}),currency:r((a,s)=>{const o=new Intl.NumberFormat(a,{...s,style:"currency"});return l=>o.format(l)}),datetime:r((a,s)=>{const o=new Intl.DateTimeFormat(a,{...s});return l=>o.format(l)}),relativetime:r((a,s)=>{const o=new Intl.RelativeTimeFormat(a,{...s});return l=>o.format(l,s.range||"day")}),list:r((a,s)=>{const o=new Intl.ListFormat(a,{...s});return l=>o.format(l)})}}add(e,n){this.formats[e.toLowerCase().trim()]=n}addCached(e,n){this.formats[e.toLowerCase().trim()]=y8(n)}format(e,n,r,a={}){const s=n.split(this.formatSeparator);if(s.length>1&&s[0].indexOf("(")>1&&s[0].indexOf(")")<0&&s.find(l=>l.indexOf(")")>-1)){const l=s.findIndex(c=>c.indexOf(")")>-1);s[0]=[s[0],...s.splice(1,l)].join(this.formatSeparator)}return s.reduce((l,c)=>{const{formatName:u,formatOptions:d}=rue(c);if(this.formats[u]){let m=l;try{const p=a?.formatParams?.[a.interpolationkey]||{},f=p.locale||p.lng||a.locale||a.lng||r;m=this.formats[u](l,f,{...d,...a,...p})}catch(p){this.logger.warn(p)}return m}else this.logger.warn(`there was no format function for ${u}`);return l},e)}}const sue=(t,e)=>{t.pending[e]!==void 0&&(delete t.pending[e],t.pendingCount--)};class oue extends eT{constructor(e,n,r,a={}){super(),this.backend=e,this.store=n,this.services=r,this.languageUtils=r.languageUtils,this.options=a,this.logger=ou.create("backendConnector"),this.waitingReads=[],this.maxParallelReads=a.maxParallelReads||10,this.readingCalls=0,this.maxRetries=a.maxRetries>=0?a.maxRetries:5,this.retryTimeout=a.retryTimeout>=1?a.retryTimeout:350,this.state={},this.queue=[],this.backend?.init?.(r,a.backend,a)}queueLoad(e,n,r,a){const s={},o={},l={},c={};return e.forEach(u=>{let d=!0;n.forEach(m=>{const p=`${u}|${m}`;!r.reload&&this.store.hasResourceBundle(u,m)?this.state[p]=2:this.state[p]<0||(this.state[p]===1?o[p]===void 0&&(o[p]=!0):(this.state[p]=1,d=!1,o[p]===void 0&&(o[p]=!0),s[p]===void 0&&(s[p]=!0),c[m]===void 0&&(c[m]=!0)))}),d||(l[u]=!0)}),(Object.keys(s).length||Object.keys(o).length)&&this.queue.push({pending:o,pendingCount:Object.keys(o).length,loaded:{},errors:[],callback:a}),{toLoad:Object.keys(s),pending:Object.keys(o),toLoadLanguages:Object.keys(l),toLoadNamespaces:Object.keys(c)}}loaded(e,n,r){const a=e.split("|"),s=a[0],o=a[1];n&&this.emit("failedLoading",s,o,n),!n&&r&&this.store.addResourceBundle(s,o,r,void 0,void 0,{skipCopy:!0}),this.state[e]=n?-1:2,n&&r&&(this.state[e]=0);const l={};this.queue.forEach(c=>{Vce(c.loaded,[s],o),sue(c,e),n&&c.errors.push(n),c.pendingCount===0&&!c.done&&(Object.keys(c.loaded).forEach(u=>{l[u]||(l[u]={});const d=c.loaded[u];d.length&&d.forEach(m=>{l[u][m]===void 0&&(l[u][m]=!0)})}),c.done=!0,c.errors.length?c.callback(c.errors):c.callback())}),this.emit("loaded",l),this.queue=this.queue.filter(c=>!c.done)}read(e,n,r,a=0,s=this.retryTimeout,o){if(!e.length)return o(null,{});if(this.readingCalls>=this.maxParallelReads){this.waitingReads.push({lng:e,ns:n,fcName:r,tried:a,wait:s,callback:o});return}this.readingCalls++;const l=(u,d)=>{if(this.readingCalls--,this.waitingReads.length>0){const m=this.waitingReads.shift();this.read(m.lng,m.ns,m.fcName,m.tried,m.wait,m.callback)}if(u&&d&&a{this.read.call(this,e,n,r,a+1,s*2,o)},s);return}o(u,d)},c=this.backend[r].bind(this.backend);if(c.length===2){try{const u=c(e,n);u&&typeof u.then=="function"?u.then(d=>l(null,d)).catch(l):l(null,u)}catch(u){l(u)}return}return c(e,n,l)}prepareLoading(e,n,r={},a){if(!this.backend)return this.logger.warn("No backend was added via i18next.use. Will not load resources."),a&&a();Mn(e)&&(e=this.languageUtils.toResolveHierarchy(e)),Mn(n)&&(n=[n]);const s=this.queueLoad(e,n,r,a);if(!s.toLoad.length)return s.pending.length||a(),null;s.toLoad.forEach(o=>{this.loadOne(o)})}load(e,n,r){this.prepareLoading(e,n,{},r)}reload(e,n,r){this.prepareLoading(e,n,{reload:!0},r)}loadOne(e,n=""){const r=e.split("|"),a=r[0],s=r[1];this.read(a,s,"read",void 0,void 0,(o,l)=>{o&&this.logger.warn(`${n}loading namespace ${s} for language ${a} failed`,o),!o&&l&&this.logger.log(`${n}loaded namespace ${s} for language ${a}`,l),this.loaded(e,o,l)})}saveMissing(e,n,r,a,s,o={},l=()=>{}){if(this.services?.utils?.hasLoadedNamespace&&!this.services?.utils?.hasLoadedNamespace(n)){this.logger.warn(`did not save key "${r}" as the namespace "${n}" was not yet loaded`,"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!");return}if(!(r==null||r==="")){if(this.backend?.create){const c={...o,isUpdate:s},u=this.backend.create.bind(this.backend);if(u.length<6)try{let d;u.length===5?d=u(e,n,r,a,c):d=u(e,n,r,a),d&&typeof d.then=="function"?d.then(m=>l(null,m)).catch(l):l(null,d)}catch(d){l(d)}else u(e,n,r,a,l,c)}!e||!e[0]||this.store.addResource(e[0],n,r,a)}}}const v8=()=>({debug:!1,initAsync:!0,ns:["translation"],defaultNS:["translation"],fallbackLng:["dev"],fallbackNS:!1,supportedLngs:!1,nonExplicitSupportedLngs:!1,load:"all",preload:!1,simplifyPluralSuffix:!0,keySeparator:".",nsSeparator:":",pluralSeparator:"_",contextSeparator:"_",partialBundledLanguages:!1,saveMissing:!1,updateMissing:!1,saveMissingTo:"fallback",saveMissingPlurals:!0,missingKeyHandler:!1,missingInterpolationHandler:!1,postProcess:!1,postProcessPassResolved:!1,returnNull:!1,returnEmptyString:!0,returnObjects:!1,joinArrays:!1,returnedObjectHandler:!1,parseMissingKeyHandler:!1,appendNamespaceToMissingKey:!1,appendNamespaceToCIMode:!1,overloadTranslationOptionHandler:t=>{let e={};if(typeof t[1]=="object"&&(e=t[1]),Mn(t[1])&&(e.defaultValue=t[1]),Mn(t[2])&&(e.tDescription=t[2]),typeof t[2]=="object"||typeof t[3]=="object"){const n=t[3]||t[2];Object.keys(n).forEach(r=>{e[r]=n[r]})}return e},interpolation:{escapeValue:!0,format:t=>t,prefix:"{{",suffix:"}}",formatSeparator:",",unescapePrefix:"-",nestingPrefix:"$t(",nestingSuffix:")",nestingOptionsSeparator:",",maxReplaces:1e3,skipOnVariables:!0},cacheInBuiltFormats:!0}),w8=t=>(Mn(t.ns)&&(t.ns=[t.ns]),Mn(t.fallbackLng)&&(t.fallbackLng=[t.fallbackLng]),Mn(t.fallbackNS)&&(t.fallbackNS=[t.fallbackNS]),t.supportedLngs?.indexOf?.("cimode")<0&&(t.supportedLngs=t.supportedLngs.concat(["cimode"])),typeof t.initImmediate=="boolean"&&(t.initAsync=t.initImmediate),t),c_=()=>{},lue=t=>{Object.getOwnPropertyNames(Object.getPrototypeOf(t)).forEach(n=>{typeof t[n]=="function"&&(t[n]=t[n].bind(t))})};class Wv extends eT{constructor(e={},n){if(super(),this.options=w8(e),this.services={},this.logger=ou,this.modules={external:[]},lue(this),n&&!this.isInitialized&&!e.isClone){if(!this.options.initAsync)return this.init(e,n),this;setTimeout(()=>{this.init(e,n)},0)}}init(e={},n){this.isInitializing=!0,typeof e=="function"&&(n=e,e={}),e.defaultNS==null&&e.ns&&(Mn(e.ns)?e.defaultNS=e.ns:e.ns.indexOf("translation")<0&&(e.defaultNS=e.ns[0]));const r=v8();this.options={...r,...this.options,...w8(e)},this.options.interpolation={...r.interpolation,...this.options.interpolation},e.keySeparator!==void 0&&(this.options.userDefinedKeySeparator=e.keySeparator),e.nsSeparator!==void 0&&(this.options.userDefinedNsSeparator=e.nsSeparator);const a=u=>u?typeof u=="function"?new u:u:null;if(!this.options.isClone){this.modules.logger?ou.init(a(this.modules.logger),this.options):ou.init(null,this.options);let u;this.modules.formatter?u=this.modules.formatter:u=aue;const d=new f8(this.options);this.store=new h8(this.options.resources,this.options);const m=this.services;m.logger=ou,m.resourceStore=this.store,m.languageUtils=d,m.pluralResolver=new tue(d,{prepend:this.options.pluralSeparator,simplifyPluralSuffix:this.options.simplifyPluralSuffix}),this.options.interpolation.format&&this.options.interpolation.format!==r.interpolation.format&&this.logger.deprecate("init: you are still using the legacy format function, please use the new approach: https://www.i18next.com/translation-function/formatting"),u&&(!this.options.interpolation.format||this.options.interpolation.format===r.interpolation.format)&&(m.formatter=a(u),m.formatter.init&&m.formatter.init(m,this.options),this.options.interpolation.format=m.formatter.format.bind(m.formatter)),m.interpolator=new nue(this.options),m.utils={hasLoadedNamespace:this.hasLoadedNamespace.bind(this)},m.backendConnector=new oue(a(this.modules.backend),m.resourceStore,m,this.options),m.backendConnector.on("*",(f,...y)=>{this.emit(f,...y)}),this.modules.languageDetector&&(m.languageDetector=a(this.modules.languageDetector),m.languageDetector.init&&m.languageDetector.init(m,this.options.detection,this.options)),this.modules.i18nFormat&&(m.i18nFormat=a(this.modules.i18nFormat),m.i18nFormat.init&&m.i18nFormat.init(this)),this.translator=new jN(this.services,this.options),this.translator.on("*",(f,...y)=>{this.emit(f,...y)}),this.modules.external.forEach(f=>{f.init&&f.init(this)})}if(this.format=this.options.interpolation.format,n||(n=c_),this.options.fallbackLng&&!this.services.languageDetector&&!this.options.lng){const u=this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);u.length>0&&u[0]!=="dev"&&(this.options.lng=u[0])}!this.services.languageDetector&&!this.options.lng&&this.logger.warn("init: no languageDetector is used and no lng is defined"),["getResource","hasResourceBundle","getResourceBundle","getDataByLanguage"].forEach(u=>{this[u]=(...d)=>this.store[u](...d)}),["addResource","addResources","addResourceBundle","removeResourceBundle"].forEach(u=>{this[u]=(...d)=>(this.store[u](...d),this)});const l=Zy(),c=()=>{const u=(d,m)=>{this.isInitializing=!1,this.isInitialized&&!this.initializedStoreOnce&&this.logger.warn("init: i18next is already initialized. You should call init just once!"),this.isInitialized=!0,this.options.isClone||this.logger.log("initialized",this.options),this.emit("initialized",this.options),l.resolve(m),n(d,m)};if(this.languages&&!this.isInitialized)return u(null,this.t.bind(this));this.changeLanguage(this.options.lng,u)};return this.options.resources||!this.options.initAsync?c():setTimeout(c,0),l}loadResources(e,n=c_){let r=n;const a=Mn(e)?e:this.language;if(typeof e=="function"&&(r=e),!this.options.resources||this.options.partialBundledLanguages){if(a?.toLowerCase()==="cimode"&&(!this.options.preload||this.options.preload.length===0))return r();const s=[],o=l=>{if(!l||l==="cimode")return;this.services.languageUtils.toResolveHierarchy(l).forEach(u=>{u!=="cimode"&&s.indexOf(u)<0&&s.push(u)})};a?o(a):this.services.languageUtils.getFallbackCodes(this.options.fallbackLng).forEach(c=>o(c)),this.options.preload?.forEach?.(l=>o(l)),this.services.backendConnector.load(s,this.options.ns,l=>{!l&&!this.resolvedLanguage&&this.language&&this.setResolvedLanguage(this.language),r(l)})}else r(null)}reloadResources(e,n,r){const a=Zy();return typeof e=="function"&&(r=e,e=void 0),typeof n=="function"&&(r=n,n=void 0),e||(e=this.languages),n||(n=this.options.ns),r||(r=c_),this.services.backendConnector.reload(e,n,s=>{a.resolve(),r(s)}),a}use(e){if(!e)throw new Error("You are passing an undefined module! Please check the object you are passing to i18next.use()");if(!e.type)throw new Error("You are passing a wrong module! Please check the object you are passing to i18next.use()");return e.type==="backend"&&(this.modules.backend=e),(e.type==="logger"||e.log&&e.warn&&e.error)&&(this.modules.logger=e),e.type==="languageDetector"&&(this.modules.languageDetector=e),e.type==="i18nFormat"&&(this.modules.i18nFormat=e),e.type==="postProcessor"&&ZY.addPostProcessor(e),e.type==="formatter"&&(this.modules.formatter=e),e.type==="3rdParty"&&this.modules.external.push(e),this}setResolvedLanguage(e){if(!(!e||!this.languages)&&!(["cimode","dev"].indexOf(e)>-1)){for(let n=0;n-1)&&this.store.hasLanguageSomeTranslations(r)){this.resolvedLanguage=r;break}}!this.resolvedLanguage&&this.languages.indexOf(e)<0&&this.store.hasLanguageSomeTranslations(e)&&(this.resolvedLanguage=e,this.languages.unshift(e))}}changeLanguage(e,n){this.isLanguageChangingTo=e;const r=Zy();this.emit("languageChanging",e);const a=l=>{this.language=l,this.languages=this.services.languageUtils.toResolveHierarchy(l),this.resolvedLanguage=void 0,this.setResolvedLanguage(l)},s=(l,c)=>{c?this.isLanguageChangingTo===e&&(a(c),this.translator.changeLanguage(c),this.isLanguageChangingTo=void 0,this.emit("languageChanged",c),this.logger.log("languageChanged",c)):this.isLanguageChangingTo=void 0,r.resolve((...u)=>this.t(...u)),n&&n(l,(...u)=>this.t(...u))},o=l=>{!e&&!l&&this.services.languageDetector&&(l=[]);const c=Mn(l)?l:l&&l[0],u=this.store.hasLanguageSomeTranslations(c)?c:this.services.languageUtils.getBestMatchFromCodes(Mn(l)?[l]:l);u&&(this.language||a(u),this.translator.language||this.translator.changeLanguage(u),this.services.languageDetector?.cacheUserLanguage?.(u)),this.loadResources(u,d=>{s(d,u)})};return!e&&this.services.languageDetector&&!this.services.languageDetector.async?o(this.services.languageDetector.detect()):!e&&this.services.languageDetector&&this.services.languageDetector.async?this.services.languageDetector.detect.length===0?this.services.languageDetector.detect().then(o):this.services.languageDetector.detect(o):o(e),r}getFixedT(e,n,r){const a=(s,o,...l)=>{let c;typeof o!="object"?c=this.options.overloadTranslationOptionHandler([s,o].concat(l)):c={...o},c.lng=c.lng||a.lng,c.lngs=c.lngs||a.lngs,c.ns=c.ns||a.ns,c.keyPrefix!==""&&(c.keyPrefix=c.keyPrefix||r||a.keyPrefix);const u=this.options.keySeparator||".";let d;return c.keyPrefix&&Array.isArray(s)?d=s.map(m=>(typeof m=="function"&&(m=s3(m,{...this.options,...o})),`${c.keyPrefix}${u}${m}`)):(typeof s=="function"&&(s=s3(s,{...this.options,...o})),d=c.keyPrefix?`${c.keyPrefix}${u}${s}`:s),this.t(d,c)};return Mn(e)?a.lng=e:a.lngs=e,a.ns=n,a.keyPrefix=r,a}t(...e){return this.translator?.translate(...e)}exists(...e){return this.translator?.exists(...e)}setDefaultNamespace(e){this.options.defaultNS=e}hasLoadedNamespace(e,n={}){if(!this.isInitialized)return this.logger.warn("hasLoadedNamespace: i18next was not initialized",this.languages),!1;if(!this.languages||!this.languages.length)return this.logger.warn("hasLoadedNamespace: i18n.languages were undefined or empty",this.languages),!1;const r=n.lng||this.resolvedLanguage||this.languages[0],a=this.options?this.options.fallbackLng:!1,s=this.languages[this.languages.length-1];if(r.toLowerCase()==="cimode")return!0;const o=(l,c)=>{const u=this.services.backendConnector.state[`${l}|${c}`];return u===-1||u===0||u===2};if(n.precheck){const l=n.precheck(this,o);if(l!==void 0)return l}return!!(this.hasResourceBundle(r,e)||!this.services.backendConnector.backend||this.options.resources&&!this.options.partialBundledLanguages||o(r,e)&&(!a||o(s,e)))}loadNamespaces(e,n){const r=Zy();return this.options.ns?(Mn(e)&&(e=[e]),e.forEach(a=>{this.options.ns.indexOf(a)<0&&this.options.ns.push(a)}),this.loadResources(a=>{r.resolve(),n&&n(a)}),r):(n&&n(),Promise.resolve())}loadLanguages(e,n){const r=Zy();Mn(e)&&(e=[e]);const a=this.options.preload||[],s=e.filter(o=>a.indexOf(o)<0&&this.services.languageUtils.isSupportedCode(o));return s.length?(this.options.preload=a.concat(s),this.loadResources(o=>{r.resolve(),n&&n(o)}),r):(n&&n(),Promise.resolve())}dir(e){if(e||(e=this.resolvedLanguage||(this.languages?.length>0?this.languages[0]:this.language)),!e)return"rtl";try{const a=new Intl.Locale(e);if(a&&a.getTextInfo){const s=a.getTextInfo();if(s&&s.direction)return s.direction}}catch{}const n=["ar","shu","sqr","ssh","xaa","yhd","yud","aao","abh","abv","acm","acq","acw","acx","acy","adf","ads","aeb","aec","afb","ajp","apc","apd","arb","arq","ars","ary","arz","auz","avl","ayh","ayl","ayn","ayp","bbz","pga","he","iw","ps","pbt","pbu","pst","prp","prd","ug","ur","ydd","yds","yih","ji","yi","hbo","men","xmn","fa","jpr","peo","pes","prs","dv","sam","ckb"],r=this.services?.languageUtils||new f8(v8());return e.toLowerCase().indexOf("-latn")>1?"ltr":n.indexOf(r.getLanguagePartFromCode(e))>-1||e.toLowerCase().indexOf("-arab")>1?"rtl":"ltr"}static createInstance(e={},n){const r=new Wv(e,n);return r.createInstance=Wv.createInstance,r}cloneInstance(e={},n=c_){const r=e.forkResourceStore;r&&delete e.forkResourceStore;const a={...this.options,...e,isClone:!0},s=new Wv(a);if((e.debug!==void 0||e.prefix!==void 0)&&(s.logger=s.logger.clone(e)),["store","services","language"].forEach(l=>{s[l]=this[l]}),s.services={...this.services},s.services.utils={hasLoadedNamespace:s.hasLoadedNamespace.bind(s)},r){const l=Object.keys(this.store.data).reduce((c,u)=>(c[u]={...this.store.data[u]},c[u]=Object.keys(c[u]).reduce((d,m)=>(d[m]={...c[u][m]},d),c[u]),c),{});s.store=new h8(l,a),s.services.resourceStore=s.store}return s.translator=new jN(s.services,a),s.translator.on("*",(l,...c)=>{s.emit(l,...c)}),s.init(a,n),s.translator.options=a,s.translator.backendConnector.services.utils={hasLoadedNamespace:s.hasLoadedNamespace.bind(s)},s}toJSON(){return{options:this.options,store:this.store,language:this.language,languages:this.languages,resolvedLanguage:this.resolvedLanguage}}}const to=Wv.createInstance();to.createInstance;to.dir;to.init;to.loadResources;to.reloadResources;to.use;to.changeLanguage;to.getFixedT;to.t;to.exists;to.setDefaultNamespace;to.hasLoadedNamespace;to.loadNamespaces;to.loadLanguages;const cue=(t,e,n,r)=>{const a=[n,{code:e,...r||{}}];if(t?.services?.logger?.forward)return t.services.logger.forward(a,"warn","react-i18next::",!0);kf(a[0])&&(a[0]=`react-i18next:: ${a[0]}`),t?.services?.logger?.warn?t.services.logger.warn(...a):console?.warn&&console.warn(...a)},S8={},eX=(t,e,n,r)=>{kf(n)&&S8[n]||(kf(n)&&(S8[n]=new Date),cue(t,e,n,r))},tX=(t,e)=>()=>{if(t.isInitialized)e();else{const n=()=>{setTimeout(()=>{t.off("initialized",n)},0),e()};t.on("initialized",n)}},o3=(t,e,n)=>{t.loadNamespaces(e,tX(t,n))},_8=(t,e,n,r)=>{if(kf(n)&&(n=[n]),t.options.preload&&t.options.preload.indexOf(e)>-1)return o3(t,n,r);n.forEach(a=>{t.options.ns.indexOf(a)<0&&t.options.ns.push(a)}),t.loadLanguages(e,tX(t,r))},uue=(t,e,n={})=>!e.languages||!e.languages.length?(eX(e,"NO_LANGUAGES","i18n.languages were undefined or empty",{languages:e.languages}),!0):e.hasLoadedNamespace(t,{lng:n.lng,precheck:(r,a)=>{if(n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&r.services.backendConnector.backend&&r.isLanguageChangingTo&&!a(r.isLanguageChangingTo,t))return!1}}),kf=t=>typeof t=="string",due=t=>typeof t=="object"&&t!==null,mue=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,hue={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},pue=t=>hue[t],fue=t=>t.replace(mue,pue);let l3={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:fue,transDefaultProps:void 0};const gue=(t={})=>{l3={...l3,...t}},bue=()=>l3;let nX;const xue=t=>{nX=t},yue=()=>nX,vue={type:"3rdParty",init(t){gue(t.options.react),xue(t)}},wue=w.createContext();class Sue{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach(n=>{this.usedNamespaces[n]||(this.usedNamespaces[n]=!0)})}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}var qM={exports:{}},VM={};var N8;function _ue(){if(N8)return VM;N8=1;var t=Jf();function e(m,p){return m===p&&(m!==0||1/m===1/p)||m!==m&&p!==p}var n=typeof Object.is=="function"?Object.is:e,r=t.useState,a=t.useEffect,s=t.useLayoutEffect,o=t.useDebugValue;function l(m,p){var f=p(),y=r({inst:{value:f,getSnapshot:p}}),v=y[0].inst,b=y[1];return s(function(){v.value=f,v.getSnapshot=p,c(v)&&b({inst:v})},[m,f,p]),a(function(){return c(v)&&b({inst:v}),m(function(){c(v)&&b({inst:v})})},[m]),o(f),f}function c(m){var p=m.getSnapshot;m=m.value;try{var f=p();return!n(m,f)}catch{return!0}}function u(m,p){return p()}var d=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?u:l;return VM.useSyncExternalStore=t.useSyncExternalStore!==void 0?t.useSyncExternalStore:d,VM}var k8;function rX(){return k8||(k8=1,qM.exports=_ue()),qM.exports}var AL=rX();const Nue=(t,e)=>kf(e)?e:due(e)&&kf(e.defaultValue)?e.defaultValue:Array.isArray(t)?t[t.length-1]:t,kue={t:Nue,ready:!1},Cue=()=>()=>{},Dt=(t,e={})=>{const{i18n:n}=e,{i18n:r,defaultNS:a}=w.useContext(wue)||{},s=n||r||yue();s&&!s.reportNamespaces&&(s.reportNamespaces=new Sue),s||eX(s,"NO_I18NEXT_INSTANCE","useTranslation: You will need to pass in an i18next instance by using initReactI18next");const o=w.useMemo(()=>({...bue(),...s?.options?.react,...e}),[s,e]),{useSuspense:l,keyPrefix:c}=o,u=a||s?.options?.defaultNS,d=kf(u)?[u]:u||["translation"],m=w.useMemo(()=>d,d);s?.reportNamespaces?.addUsedNamespaces?.(m);const p=w.useRef(0),f=w.useCallback(A=>{if(!s)return Cue;const{bindI18n:R,bindI18nStore:G}=o,z=()=>{p.current+=1,A()};return R&&s.on(R,z),G&&s.store.on(G,z),()=>{R&&R.split(" ").forEach(Z=>s.off(Z,z)),G&&G.split(" ").forEach(Z=>s.store.off(Z,z))}},[s,o]),y=w.useRef(),v=w.useCallback(()=>{if(!s)return kue;const A=!!(s.isInitialized||s.initializedStoreOnce)&&m.every(J=>uue(J,s,o)),R=e.lng||s.language,G=p.current,z=y.current;if(z&&z.ready===A&&z.lng===R&&z.keyPrefix===c&&z.revision===G)return z;const I={t:s.getFixedT(R,o.nsMode==="fallback"?m:m[0],c),ready:A,lng:R,keyPrefix:c,revision:G};return y.current=I,I},[s,m,c,o,e.lng]),[b,g]=w.useState(0),{t:_,ready:N}=AL.useSyncExternalStore(f,v,v);w.useEffect(()=>{if(s&&!N&&!l){const A=()=>g(R=>R+1);e.lng?_8(s,e.lng,m,A):o3(s,m,A)}},[s,e.lng,m,N,l,b]);const C=s||{},k=w.useRef(null),P=w.useRef(),T=A=>{const R=Object.getOwnPropertyDescriptors(A);R.__original&&delete R.__original;const G=Object.create(Object.getPrototypeOf(A),R);if(!Object.prototype.hasOwnProperty.call(G,"__original"))try{Object.defineProperty(G,"__original",{value:A,writable:!1,enumerable:!1,configurable:!1})}catch{}return G},D=w.useMemo(()=>{const A=C,R=A?.language;let G=A;A&&(k.current&&k.current.__original===A?P.current!==R?(G=T(A),k.current=G,P.current=R):G=k.current:(G=T(A),k.current=G,P.current=R));const z=[_,G,N];return z.t=_,z.i18n=G,z.ready=N,z},[_,C,N,C.resolvedLanguage,C.language,C.languages]);if(s&&l&&!N)throw new Promise(A=>{const R=()=>A();e.lng?_8(s,e.lng,m,R):o3(s,m,R)});return D},{slice:Tue,forEach:Aue}=[];function Pue(t){return Aue.call(Tue.call(arguments,1),e=>{if(e)for(const n in e)t[n]===void 0&&(t[n]=e[n])}),t}function Mue(t){return typeof t!="string"?!1:[/<\s*script.*?>/i,/<\s*\/\s*script\s*>/i,/<\s*img.*?on\w+\s*=/i,/<\s*\w+\s*on\w+\s*=.*?>/i,/javascript\s*:/i,/vbscript\s*:/i,/expression\s*\(/i,/eval\s*\(/i,/alert\s*\(/i,/document\.cookie/i,/document\.write\s*\(/i,/window\.location/i,/innerHTML/i].some(n=>n.test(t))}const C8=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/,Eue=function(t,e){const r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{path:"/"},a=encodeURIComponent(e);let s=`${t}=${a}`;if(r.maxAge>0){const o=r.maxAge-0;if(Number.isNaN(o))throw new Error("maxAge should be a Number");s+=`; Max-Age=${Math.floor(o)}`}if(r.domain){if(!C8.test(r.domain))throw new TypeError("option domain is invalid");s+=`; Domain=${r.domain}`}if(r.path){if(!C8.test(r.path))throw new TypeError("option path is invalid");s+=`; Path=${r.path}`}if(r.expires){if(typeof r.expires.toUTCString!="function")throw new TypeError("option expires is invalid");s+=`; Expires=${r.expires.toUTCString()}`}if(r.httpOnly&&(s+="; HttpOnly"),r.secure&&(s+="; Secure"),r.sameSite)switch(typeof r.sameSite=="string"?r.sameSite.toLowerCase():r.sameSite){case!0:s+="; SameSite=Strict";break;case"lax":s+="; SameSite=Lax";break;case"strict":s+="; SameSite=Strict";break;case"none":s+="; SameSite=None";break;default:throw new TypeError("option sameSite is invalid")}return r.partitioned&&(s+="; Partitioned"),s},T8={create(t,e,n,r){let a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:{path:"/",sameSite:"strict"};n&&(a.expires=new Date,a.expires.setTime(a.expires.getTime()+n*60*1e3)),r&&(a.domain=r),document.cookie=Eue(t,e,a)},read(t){const e=`${t}=`,n=document.cookie.split(";");for(let r=0;r-1&&(r=window.location.hash.substring(window.location.hash.indexOf("?")));const s=r.substring(1).split("&");for(let o=0;o0&&s[o].substring(0,l)===e&&(n=s[o].substring(l+1))}}return n}},Rue={name:"hash",lookup(t){let{lookupHash:e,lookupFromHashIndex:n}=t,r;if(typeof window<"u"){const{hash:a}=window.location;if(a&&a.length>2){const s=a.substring(1);if(e){const o=s.split("&");for(let l=0;l0&&o[l].substring(0,c)===e&&(r=o[l].substring(c+1))}}if(r)return r;if(!r&&n>-1){const o=a.match(/\/([a-zA-Z-]*)/g);return Array.isArray(o)?o[typeof n=="number"?n:0]?.replace("/",""):void 0}}}return r}};let zg=null;const A8=()=>{if(zg!==null)return zg;try{if(zg=typeof window<"u"&&window.localStorage!==null,!zg)return!1;const t="i18next.translate.boo";window.localStorage.setItem(t,"foo"),window.localStorage.removeItem(t)}catch{zg=!1}return zg};var Fue={name:"localStorage",lookup(t){let{lookupLocalStorage:e}=t;if(e&&A8())return window.localStorage.getItem(e)||void 0},cacheUserLanguage(t,e){let{lookupLocalStorage:n}=e;n&&A8()&&window.localStorage.setItem(n,t)}};let Ug=null;const P8=()=>{if(Ug!==null)return Ug;try{if(Ug=typeof window<"u"&&window.sessionStorage!==null,!Ug)return!1;const t="i18next.translate.boo";window.sessionStorage.setItem(t,"foo"),window.sessionStorage.removeItem(t)}catch{Ug=!1}return Ug};var Lue={name:"sessionStorage",lookup(t){let{lookupSessionStorage:e}=t;if(e&&P8())return window.sessionStorage.getItem(e)||void 0},cacheUserLanguage(t,e){let{lookupSessionStorage:n}=e;n&&P8()&&window.sessionStorage.setItem(n,t)}},Oue={name:"navigator",lookup(t){const e=[];if(typeof navigator<"u"){const{languages:n,userLanguage:r,language:a}=navigator;if(n)for(let s=0;s0?e:void 0}},Iue={name:"htmlTag",lookup(t){let{htmlTag:e}=t,n;const r=e||(typeof document<"u"?document.documentElement:null);return r&&typeof r.getAttribute=="function"&&(n=r.getAttribute("lang")),n}},zue={name:"path",lookup(t){let{lookupFromPathIndex:e}=t;if(typeof window>"u")return;const n=window.location.pathname.match(/\/([a-zA-Z-]*)/g);return Array.isArray(n)?n[typeof e=="number"?e:0]?.replace("/",""):void 0}},Uue={name:"subdomain",lookup(t){let{lookupFromSubdomainIndex:e}=t;const n=typeof e=="number"?e+1:1,r=typeof window<"u"&&window.location?.hostname?.match(/^(\w{2,5})\.(([a-z0-9-]{1,63}\.[a-z]{2,6})|localhost)/i);if(r)return r[n]}};let iX=!1;try{document.cookie,iX=!0}catch{}const aX=["querystring","cookie","localStorage","sessionStorage","navigator","htmlTag"];iX||aX.splice(1,1);const Bue=()=>({order:aX,lookupQuerystring:"lng",lookupCookie:"i18next",lookupLocalStorage:"i18nextLng",lookupSessionStorage:"i18nextLng",caches:["localStorage"],excludeCacheFor:["cimode"],convertDetectedLanguage:t=>t});class sX{constructor(e){let n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};this.type="languageDetector",this.detectors={},this.init(e,n)}init(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{languageUtils:{}},n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};this.services=e,this.options=Pue(n,this.options||{},Bue()),typeof this.options.convertDetectedLanguage=="string"&&this.options.convertDetectedLanguage.indexOf("15897")>-1&&(this.options.convertDetectedLanguage=a=>a.replace("-","_")),this.options.lookupFromUrlIndex&&(this.options.lookupFromPathIndex=this.options.lookupFromUrlIndex),this.i18nOptions=r,this.addDetector(jue),this.addDetector(Due),this.addDetector(Fue),this.addDetector(Lue),this.addDetector(Oue),this.addDetector(Iue),this.addDetector(zue),this.addDetector(Uue),this.addDetector(Rue)}addDetector(e){return this.detectors[e.name]=e,this}detect(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.options.order,n=[];return e.forEach(r=>{if(this.detectors[r]){let a=this.detectors[r].lookup(this.options);a&&typeof a=="string"&&(a=[a]),a&&(n=n.concat(a))}}),n=n.filter(r=>r!=null&&!Mue(r)).map(r=>this.options.convertDetectedLanguage(r)),this.services&&this.services.languageUtils&&this.services.languageUtils.getBestMatchFromCodes?n:n.length>0?n[0]:null}cacheUserLanguage(e){let n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:this.options.caches;n&&(this.options.excludeCacheFor&&this.options.excludeCacheFor.indexOf(e)>-1||n.forEach(r=>{this.detectors[r]&&this.detectors[r].cacheUserLanguage(e,this.options)}))}}sX.type="languageDetector";const Hue={nav:{printers:"Printers",archives:"Archives",queue:"Queue",stats:"Statistics",profiles:"Profiles",maintenance:"Maintenance",projects:"Projects",inventory:"Filament",files:"File Manager",notifications:"Notifications",settings:"Settings",system:"System",collapseSidebar:"Collapse sidebar",expandSidebar:"Expand sidebar",update:"Update",updateAvailable:"Update available: v{{version}}",updateAvailableBanner:"Version {{version}} is available!",viewUpdate:"View update",viewOnGithub:"View on GitHub",keyboardShortcuts:"Keyboard shortcuts (?)",switchToLight:"Switch to light mode",switchToDark:"Switch to dark mode",smartSwitches:"Smart Switches",logout:"Logout"},common:{save:"Save",saving:"Saving...",cancel:"Cancel",delete:"Delete",edit:"Edit",add:"Add",close:"Close",confirm:"Confirm",loading:"Loading...",error:"Error",success:"Success",warning:"Warning",enabled:"Enabled",disabled:"Disabled",yes:"Yes",no:"No",on:"On",off:"Off",all:"All",none:"None",search:"Search",filter:"Filter",sort:"Sort",refresh:"Refresh",download:"Download",upload:"Upload",uploading:"Uploading...",uploadFailed:"Upload failed",actions:"Actions",status:"Status",name:"Name",description:"Description",date:"Date",time:"Time",hours:"hours",minutes:"minutes",seconds:"seconds",days:"days",enable:"Enable",disable:"Disable",permissions:"Permissions",noPrinters:"No printers configured",noData:"No data available",linkNotFound:"Link not found",required:"Required",optional:"Optional",dismiss:"Dismiss",apply:"Apply",reset:"Reset",export:"Export",import:"Import",clear:"Clear",selectAll:"Select All",deselectAll:"Deselect All",noChange:"— No change —",unchanged:"Unchanged",unassigned:"Unassigned",unknown:"Unknown",unknownError:"Unknown error",today:"Today",tomorrow:"Tomorrow",asap:"ASAP",overdue:"Overdue",now:"Now",collapse:"Collapse",expand:"Expand",viewArchive:"View archive",viewInFileManager:"View in File Manager",addedBy:"Added by {{username}}",prints:"prints",more:"+{{count}} more",ascending:"Ascending",descending:"Descending",printer:"Printer",remove:"Remove",type:"Type",print:"Print",rename:"Rename",move:"Move",create:"Create",duplicate:"Duplicate",left:"Left",right:"Right"},printers:{title:"Printers",addPrinter:"Add Printer",editPrinter:"Edit Printer",deletePrinter:"Delete Printer",printerName:"Printer Name",serialNumber:"Serial Number",ipAddress:"IP Address / Hostname",accessCode:"Access Code",model:"Model",nozzleCount:"Nozzle Count",autoArchive:"Auto Archive",status:{available:"Available",idle:"Idle",printing:"Printing",paused:"Paused",offline:"Offline",problem:"Problem",error:"Error",finished:"Finished",unknown:"Unknown"},temperatures:{nozzle:"Nozzle",bed:"Bed",chamber:"Chamber"},progress:"{{percent}}% complete",timeRemaining:"{{time}} remaining",deleteConfirm:'Are you sure you want to delete "{{name}}"?',maintenanceOk:"Maintenance OK",maintenanceWarning:"{{count}} warning",maintenanceWarning_plural:"{{count}} warnings",maintenanceDue:"{{count}} due",maintenanceDue_plural:"{{count}} due",sort:{name:"Name",status:"Status",model:"Model",location:"Location",ascending:"Sort ascending",descending:"Sort descending"},cardSize:{small:"Small cards",medium:"Medium cards",large:"Large cards",extraLarge:"Extra large cards"},hideOffline:"Hide offline",nextAvailable:"Next available",powerOn:"Power On",offlinePrintersWithPlugs:"Offline printers with smart plugs",noPrintersConfigured:"No printers configured yet",readyToPrint:"Ready to print",external:"External",extL:"Ext-L",extR:"Ext-R",deleteArchives:"Delete print archives",noLabel:"No label",printPreview:"Print preview",width:"Width",height:"Height",noObjectsFound:"No objects found",objectsLoadedOnPrintStart:"Objects are loaded when a print starts",willBeSkipped:"Will be skipped",name:"Name",serialCannotBeChanged:"Serial number cannot be changed",locationHelp:"Used to group printers and filter queue jobs",wifiSignal:{veryWeak:"Very weak",weak:"Weak",fair:"Fair",good:"Good",excellent:"Excellent"},maintenanceUpToDate:"All maintenance up to date - Click to view",chamberLightOn:"Turn on chamber light",chamberLightOff:"Turn off chamber light",files:"Files",browseFiles:"Browse printer files",autoOffAfterPrint:"Auto power-off after print",autoOffExecuted:"Auto-off was executed - turn printer on to reset",hmsErrors:"HMS Errors",viewHmsErrors:"View {{count}} HMS error(s)",resume:"Resume",pause:"Pause",stop:"Stop",camera:"Camera",skipObject:"Skip Object",reconnect:"Reconnect",mqttDebug:"MQTT Debug",printerInformation:"Printer Information",copyToClipboard:"Copy",copied:"Copied!",state:"State",wifiSignalLabel:"WiFi Signal",developerMode:"Developer Mode",enabled:"Enabled",disabled:"Disabled",addedOn:"Added",sdCard:"SD Card",inserted:"Inserted",notInserted:"Not inserted",totalPrintHours:"Print Hours",activeNozzle:"Active: {{nozzle}} nozzle",nozzleRack:"Nozzle Rack",nozzleDocked:"Docked",nozzleMounted:"Mounted",nozzleActive:"Active",nozzleIdle:"Idle",nozzleDiameter:"Diameter",nozzleType:"Type",nozzleStatus:"Status",nozzleFilament:"Filament",nozzleWear:"Wear",nozzleMaxTemp:"Max Temp",nozzleSerial:"Serial",nozzleHardenedSteel:"Hardened Steel",nozzleStainlessSteel:"Stainless Steel",nozzleTungstenCarbide:"Tungsten Carbide",nozzleFlow:"Flow",nozzleHighFlow:"High Flow",nozzleStandardFlow:"Standard",firmwareUpdate:"Firmware Update",firmwareInstructions:"On the printer's touchscreen, go to",firmwareNav:"Navigate to",settings:"Settings",firmware:"Firmware",discoverPrinters:"Discover Printers",searching:"Searching...",manualEntry:"Manual Entry",addFromCloud:"Add from Cloud",toast:{printerDeleted:"Printer deleted",printerAdded:"Printer added",printerUpdated:"Printer updated",failedToDelete:"Failed to delete printer",failedToAdd:"Failed to add printer",failedToUpdate:"Failed to update printer",commandSent:"Command sent",failedToSendCommand:"Failed to send command",turnedOn:"{{name}} turned on",failedToPowerOn:"Failed to power on {{name}}",scriptTriggered:"Script triggered",printStopped:"Print stopped",printPaused:"Print paused",printResumed:"Print resumed",referenceDeleted:"Reference deleted",detectionAreaSaved:"Detection area saved",failedToRunScript:"Failed to run script",failedToStopPrint:"Failed to stop print",failedToPausePrint:"Failed to pause print",failedToResumePrint:"Failed to resume print",failedToControlChamberLight:"Failed to control chamber light",failedToSetSpeed:"Failed to set print speed",failedToUpdateSetting:"Failed to update setting",failedToSkipObjects:"Failed to skip objects",failedToRereadRfid:"Failed to re-read RFID",failedToCheckPlate:"Failed to check plate",failedToUpdateLabel:"Failed to update label",failedToDeleteReference:"Failed to delete reference",failedToSaveDetectionArea:"Failed to save detection area",plateCheckEnabled:"Plate check enabled",plateCheckDisabled:"Plate check disabled",calibrationSaved:"Calibration saved!",calibrationFailed:"Calibration failed",rfidRereadInitiated:"RFID re-read initiated"},connection:{connected:"Connected",offline:"Offline"},queue:{inQueue:"{{count}} print in queue",inQueue_plural:"{{count}} prints in queue"},controls:"Controls",rfid:{reread:"Re-read RFID"},permission:{noAdd:"You do not have permission to add printers",noEdit:"You do not have permission to edit printers",noDelete:"You do not have permission to delete printers",noControl:"You do not have permission to control printers",noFiles:"You do not have permission to access printer files",noAmsRfid:"You do not have permission to re-read AMS RFID",noSmartPlugControl:"You do not have permission to control smart plugs",noCamera:"You do not have permission to view cameras"},modal:{addTitle:"Add Printer",editTitle:"Edit Printer",myPrinter:"My Printer",selectModel:"Select model...",locationGroup:"Location / Group (optional)",locationPlaceholder:"e.g., Workshop, Office, Basement",autoArchiveLabel:"Auto-archive completed prints",fromPrinterSettings:"From printer settings",modelOptional:"Model (optional)",saveChanges:"Save Changes"},skipObjects:{tooltip:"Skip objects",onlyWhilePrinting:"Skip objects (only while printing)",requiresMultiple:"Skip objects (requires 2+ objects)",title:"Skip Objects",matchIdsInfo:"Match IDs with your printer display",printerShowsIds:"The printer screen shows object IDs on the build plate",skipSelected:"Skip Selected",skipping:"Skipping...",noObjectsSelected:"No objects selected",selectObjectsToSkip:"Select objects you want to skip from the current print",skipped:"skipped",objectsSkipped:"Objects skipped",activeCount:"{{count}} active",waitForLayer:"Wait for layer 2+ to skip objects (currently layer {{layer}})",skip:"Skip",confirmTitle:"Skip Object?",confirmMessage:'Are you sure you want to skip "{{name}}"? This cannot be undone.'},confirm:{deleteTitle:"Delete Printer",deleteMessage:'Are you sure you want to delete "{{name}}"? This will remove all connection settings.',deleteArchivesNote:"All print history for this printer will be permanently deleted.",keepArchivesNote:"Print history will be kept but no longer associated with this printer.",stopTitle:"Stop Print",stopMessage:'Are you sure you want to stop the current print on "{{name}}"? This will cancel the print job.',stopButton:"Stop Print",pauseTitle:"Pause Print",pauseMessage:'Are you sure you want to pause the current print on "{{name}}"?',pauseButton:"Pause Print",resumeTitle:"Resume Print",resumeMessage:'Are you sure you want to resume the print on "{{name}}"?',resumeButton:"Resume Print",powerOnTitle:"Power On Printer",powerOnMessage:'Are you sure you want to turn ON the power for "{{name}}"?',powerOnButton:"Power On",powerOffTitle:"Power Off Printer",powerOffMessage:'Are you sure you want to turn OFF the power for "{{name}}"?',powerOffWarning:'WARNING: "{{name}}" is currently printing! Are you sure you want to turn OFF the power? This will interrupt the print and may damage the printer.',powerOffButton:"Power Off"},discovery:{title:"Discover Printers",searching:"Searching...",scanning:"Scanning...",scanProgress:"Scanning... {{scanned}}/{{total}}",foundPrinters:"Found {{count}} printer(s)",noPrintersFound:"No printers found",noPrintersFoundSubnet:"No printers found in the specified subnet.",noPrintersFoundNetwork:"No printers found on the network.",allConfigured:"All discovered printers are already configured.",alreadyAdded:"Already added",select:"Select",manualEntry:"Manual Entry",addFromCloud:"Add from Cloud",subnetToScan:"Subnet to scan",dockerNote:"Docker detected. Enter your printer's subnet in CIDR notation. Requires network_mode: host in docker-compose.yml.",scanSubnet:"Scan Subnet for Printers",discoverNetwork:"Discover Printers on Network",scanningSubnet:"Scanning subnet for Bambu printers...",scanningNetwork:"Scanning network...",serialRequired:"Serial required",unknown:"Unknown",failedToStart:"Failed to start discovery"},drying:{start:"Start Drying",stop:"Stop Drying",temperature:"Temperature",duration:"Duration",hours:"hours",timeRemaining:"{{time}} left",active:"Drying",notSupported:"Drying not supported",powerRequired:"Connect AMS power adapter to enable drying",startingDrying:"Starting drying...",stoppingDrying:"Stopping drying...",rotateTray:"Rotate spool during drying"},filaments:"Filaments",openCameraOverlay:"Open camera overlay",openCameraWindow:"Open camera in new window",firmwareUpdateAvailable:"Firmware update available: {{current}} → {{latest}}",firmwareUpToDate:"Firmware {{version}} — Up to date",firmwareUpdateButton:"Update",plateDetection:{noPermission:"You do not have permission to update printers",enabledClick:"Plate check enabled - Click to disable",disabledClick:"Plate check disabled - Click to enable",manageCalibration:"Manage plate detection calibration",calibrationRequired:"Calibration Required",calibrationInstructions:"Please ensure the build plate is completely empty, then click Calibrate.",calibrationDescription:"Calibration captures a reference image of the empty plate. Future checks will compare against this reference to detect objects.",calibrationTip:"Tip: You can store up to 5 calibrations for different plates. The system automatically uses the best match when checking.",plateEmpty:"Plate appears empty",objectsDetected:"Objects detected on plate",confidence:"Confidence",difference:"Difference",analysisPreview:"Analysis preview:",analysisLegend:"Green box = detection area, Red overlay = differences from calibration",savedReferences:"Saved References ({{count}}/{{max}})",deleteReference:"Delete reference",labelPlaceholder:"Label...",clickToEdit:"{{label}} - Click to edit",clickToAddLabel:"Click to add label"},speed:{title:"Print Speed",silent:"Silent (50%)",standard:"Standard (100%)",sport:"Sport (124%)",ludicrous:"Ludicrous (166%)"},fans:{partCooling:"Part Cooling Fan",auxiliary:"Auxiliary Fan",chamber:"Chamber Fan"},clickToViewHmsErrors:"Click to view HMS errors",estimatedCompletion:"Estimated completion time",slotOptions:"Slot options",amsPopup:{friendlyName:"AMS Name",friendlyNamePlaceholder:"e.g. AMS Friendly Name",serialNumber:"Serial Number",firmwareVersion:"Firmware",save:"Save",clear:"Clear",noEditPermission:"You do not have permission to rename AMS units"},firmwareModal:{title:"Firmware Update",titleUpToDate:"Firmware Info",currentVersion:"Current:",latestVersion:"Latest:",releaseNotes:"Release Notes",checkingPrereqs:"Checking prerequisites...",sdCardReady:"SD card ready. Click below to upload firmware.",uploadedSuccess:"Firmware uploaded to SD card!",applyInstructions:"To apply the update on your printer:",step1:"On the printer's touchscreen, go to Settings",step2:"Navigate to Firmware",step3:"Select Update from SD card",step4:"The update will take 10-20 minutes",done:"Done",starting:"Starting...",uploadFirmware:"Upload Firmware",uploadFailed:"Failed to start upload: {{error}}",uploadedToast:"Firmware uploaded! Trigger update from printer screen."},accessCodePlaceholder:"Leave empty to keep current",roi:{title:"Detection Area (ROI)",xStart:"X Start",yStart:"Y Start",width:"Width",height:"Height",instruction:"Adjust the detection area to focus on the build plate. The green box in the preview shows the current area."},developerModeWarning:"Developer LAN mode is not enabled on: {{names}}. Some features may not work.",howToEnable:"How to enable",incompatibleFile:"This file was sliced for {{slicedFor}}, but this printer is a {{printerModel}}",dropNotPrintable:"Only .gcode and .gcode.3mf files can be printed",dropToPrint:"Drop to print",cannotPrint:"Printer busy"},archives:{title:"Print Archives",searchPlaceholder:"Search archives...",filterByPrinter:"Filter by printer",filterByStatus:"Filter by status",sortBy:"Sort by",sortNewest:"Newest first",sortOldest:"Oldest first",sortName:"Name",sortDuration:"Duration",sortLargest:"Largest first",sortSmallest:"Smallest first",sortSize:"Size",noArchives:"No archives found",noArchivesSearch:"No archives match your search",originalPrintNotVisible:"Original print not visible - try clearing filters",noArchivesYet:"No archives yet",loadingArchives:"Loading archives...",releaseToUpload:"Release to upload",showAll:"Show all",showFavoritesOnly:"Show favorites only",gridView:"Grid view",listView:"List view",calendarView:"Calendar view",logView:"Print Log",manageTags:"Manage Tags",showFailedPrints:"Show failed prints",hideFailedPrints:"Hide failed prints",hideDuplicates:"Hide Duplicates",viewOriginalPrint:"Click to view original print (#{{id}})",printTime:"Print Time",filamentUsed:"Filament Used",cost:"Cost",reprint:"Reprint",preview:"Preview",deleteArchive:"Delete Archive",deleteConfirm:"Are you sure you want to delete this archive?",favorite:"Favorite",unfavorite:"Remove from favorites",viewDetails:"View Details",status:{completed:"Completed",failed:"Failed",stopped:"Stopped"},toast:{source3mfAttached:"Source 3MF attached: {{filename}}",failedUploadSource3mf:"Failed to upload source 3MF",source3mfRemoved:"Source 3MF removed",failedRemoveSource3mf:"Failed to remove source 3MF",f3dAttached:"F3D attached: {{filename}}",failedUploadF3d:"Failed to upload F3D",f3dRemoved:"F3D removed",failedRemoveF3d:"Failed to remove F3D",timelapseAttached:"Timelapse attached: {{filename}}",timelapseAlreadyAttached:"Timelapse already attached",noMatchingTimelapse:"No matching timelapse found",failedScanTimelapse:"Failed to scan for timelapse",failedAttachTimelapse:"Failed to attach timelapse",timelapseRemoved:"Timelapse removed",failedRemoveTimelapse:"Failed to remove timelapse",timelapseUploaded:"Timelapse uploaded: {{filename}}",failedUploadTimelapse:"Failed to upload timelapse",archiveDeleted:"Archive deleted",failedDeleteArchive:"Failed to delete archive",addedToFavorites:"Added to favorites",removedFromFavorites:"Removed from favorites",projectUpdated:"Project updated",failedUpdateProject:"Failed to update project",linkCopied:"Link copied to clipboard",failedCopyLink:"Failed to copy link",photoDeleted:"Photo deleted",failedDeletePhoto:"Failed to delete photo",failedDeleteArchives:"Failed to delete archives",failedUpdateFavorites:"Failed to update favorites",exportDownloaded:"Export downloaded",exportFailed:"Export failed"},menu:{print:"Print",schedule:"Schedule",openInBambuStudio:"Open in Slicer",slice:"Slice",externalLink:"External Link",viewOnMakerWorld:"View on MakerWorld",preview3d:"3D Preview",viewTimelapse:"View Timelapse",scanForTimelapse:"Scan for Timelapse",uploadTimelapse:"Upload Timelapse",removeTimelapse:"Remove Timelapse",downloadSource3mf:"Download Source 3MF",uploadSource3mf:"Upload Source 3MF",replaceSource3mf:"Replace Source 3MF",removeSource3mf:"Remove Source 3MF",uploadF3d:"Upload F3D",replaceF3d:"Replace F3D",downloadF3d:"Download F3D",removeF3d:"Remove F3D",download:"Download",copyDownloadLink:"Copy Download Link",qrCode:"QR Code",viewPhotos:"View Photos",viewPhotosCount:"View Photos ({{count}})",projectPage:"Project Page",addToFavorites:"Add to Favorites",removeFromFavorites:"Remove from Favorites",edit:"Edit",goToProject:"Go to Project: {{name}}",addToProject:"Add to Project",removeFromProject:"Remove from Project",loading:"Loading...",noProjectsAvailable:"No projects available",select:"Select",deselect:"Deselect",delete:"Delete"},permission:{noReprint:"You do not have permission to reprint this archive",noAddToQueue:"You do not have permission to add to queue",noUpdateArchives:"You do not have permission to update archives",noUploadFiles:"You do not have permission to upload files",noDownload:"You do not have permission to download archives",noCopyLink:"You do not have permission to copy download links",noDelete:"You do not have permission to delete this archive",noCreate:"You do not have permission to create archives"},card:{previousPlate:"Previous plate",nextPlate:"Next plate",plateNumber:"Plate {{index}}",moreOptions:"Right-click for more options",addToFavorites:"Add to favorites",removeFromFavorites:"Remove from favorites",cancelled:"cancelled",failed:"failed",duplicate:"duplicate",duplicateTitle:"This model has been printed before",openSource3mf:"Open source 3MF in Bambu Studio (right-click for more options)",downloadF3d:"Download Fusion 360 design file",viewTimelapse:"View timelapse",viewPhoto:"View 1 photo",viewPhotos:"View {{count}} photos",openFolder:"Open folder: {{name}}",slicedFile:"Sliced file - ready to print",sourceFile:"Source file only - no AMS mapping available",gcode:"GCODE",source:"SOURCE",project:"Project: {{name}}",estimated:"Estimated: {{time}}",actual:"Actual: {{time}}",accuracy:"Accuracy: {{percent}}%",filament:"{{weight}}g",layer:"{{count}} layer",layers:"{{count}} layers",object:"{{count}} object",objects:"{{count}} objects",slicedFor:"Sliced for {{model}}",uploadedBy:"Uploaded By",noPermissionReprint:"You do not have permission to reprint",noFileForReprint:"No 3MF file available — the file could not be downloaded from the printer when the print was recorded",noPermissionEdit:"You do not have permission to edit archives",noPermissionDelete:"You do not have permission to delete archives",reprint:"Reprint",schedulePrint:"Schedule Print",schedule:"Schedule",openInBambuStudio:"Open in Slicer",openInBambuStudioToSlice:"Open in Slicer to slice",slice:"Slice",externalLink:"External Link",makerWorld:"MakerWorld: {{designer}}",viewProject:"View project",noExternalLink:"No external link",preview3d:"3D Preview",download:"Download",edit:"Edit",delete:"Delete"},modal:{deleteArchive:"Delete Archive",deleteConfirm:'Are you sure you want to delete "{{name}}"? This action cannot be undone.',deleteButton:"Delete",removeSource3mf:"Remove Source 3MF",removeSource3mfConfirm:'Are you sure you want to remove the source 3MF file from "{{name}}"? This will delete the original slicer project file.',removeButton:"Remove",removeF3d:"Remove F3D",removeF3dConfirm:'Are you sure you want to remove the Fusion 360 design file from "{{name}}"?',removeTimelapse:"Remove Timelapse",removeTimelapseConfirm:'Are you sure you want to remove the timelapse video from "{{name}}"?',timelapse:"{{name}} - Timelapse",selectTimelapse:"Select Timelapse",selectTimelapseDesc:"No auto-match found. Select the timelapse for this print:",deleteArchives:"Delete Archives",deleteArchivesConfirm:"Are you sure you want to delete {{count}} archive(s)? This action cannot be undone.",deleteCount:"Delete {{count}}"},page:{title:"Archives",printsCount:"{{filtered}} of {{total}} prints",dropFilesHere:"Drop .3mf files here",releaseToUpload:"Release to upload",only3mfSupported:"Only .3mf files are supported",close:"Close",selected:"{{count}} selected",selectAll:"Select All",tags:"Tags",project:"Project",favorite:"Favorite",delete:"Delete",toggledFavorites:"Toggled favorites for {{count}} archive(s)",failedUpdateFavorites:"Failed to update favorites",archivesDeleted:"{{count}} archive(s) deleted",failedDeleteArchives:"Failed to delete archives",photoDeleted:"Photo deleted",failedDeletePhoto:"Failed to delete photo"},list:{name:"Name",printer:"Printer",date:"Date",size:"Size",actions:"Actions",hasTimelapse:"Has timelapse"},log:{date:"Date",printName:"Print Name",printer:"Printer",user:"User",status:"Status",duration:"Duration",filament:"Filament",allPrinters:"All Printers",allUsers:"All Users",allStatuses:"All Statuses",cancelled:"Cancelled",skipped:"Skipped",dateFrom:"From",dateTo:"To",noEntries:"No print log entries found",showing:"Showing {{count}} of {{total}} entries",rowsPerPage:"Rows",page:"Page",prev:"Prev",next:"Next",clearLog:"Clear Log",clearLogTitle:"Clear Print Log",clearLogConfirm:"All print log entries will be permanently deleted. Archives and queue items are not affected. This action cannot be undone. Are you sure?",clearLogButton:"Clear All",cleared:"{{count}} log entries cleared",clearFailed:"Failed to clear print log"}},queue:{title:"Print Queue",subtitle:"Schedule and manage your print jobs",addToQueue:"Add to Queue",print:"Print",reprint:"Re-print",schedulePrint:"Schedule Print",editQueueItem:"Edit Queue Item",printToPrinters:"Print to {{count}} Printers",queueToPrinters:"Queue to {{count}} Printers",queueSelectedPlates:"Queue {{count}} Plates",selectAllPlates:"Select All {{count}} Plates",deselectAll:"Deselect All",printQueued:"Print queued",itemsQueued:"{{count}} items queued",sending:"Sending...",sendingProgress:"Sending {{current}}/{{total}}...",adding:"Adding...",addingProgress:"Adding {{current}}/{{total}}...",savingProgress:"Saving {{current}}/{{total}}...",clearQueue:"Clear Queue",clearHistory:"Clear History",emptyQueue:"Queue is empty",position:"Position",scheduledTime:"Scheduled Time",moveUp:"Move Up",moveDown:"Move Down",startNow:"Start Now",printingInProgress:"Printing in progress...",viewArchive:"View archive",viewInFileManager:"View in File Manager",itemCount:"{{count}} item",itemCount_plural:"{{count}} items",dragToReorder:"Drag to reorder (ASAP only)",reorderHint:"Position only affects ASAP items. Scheduled items run at their set time.",addedBy:"Added by {{name}}",nextInQueue:"Next in queue",clearPlate:"Clear Plate & Start Next",clearPlateSuccess:"Plate cleared — ready for next print",plateReady:"Plate cleared — ready for next print",plateNumber:"Plate {{index}}",sections:{currentlyPrinting:"Currently Printing",queued:"Queued",history:"History"},status:{pending:"Pending",waiting:"Waiting",printing:"Printing",paused:"Paused",completed:"Completed",failed:"Failed",skipped:"Skipped",cancelled:"Cancelled"},summary:{printing:"Printing",queued:"Queued",totalTime:"Total Queue Time",totalWeight:"Total Queue Weight",history:"History"},filter:{allPrinters:"All Printers",unassigned:"Unassigned",allStatus:"All Status",allLocations:"All Locations",any:"Any"},sort:{byPosition:"Sort by Position",byName:"Sort by Name",byPrinter:"Sort by Printer",bySchedule:"Sort by Schedule",byDate:"Sort by Date",ascendingOldest:"Ascending (oldest first)",descendingNewest:"Descending (newest first)"},badges:{staged:"Staged",requiresPrevious:"Requires previous success",autoPowerOff:"Auto power off"},empty:{title:"No prints scheduled",description:'Schedule a print from the Archives page using the "Schedule" option in the context menu, or drag and drop files to get started.'},time:{asap:"ASAP",overdue:"Overdue",now:"Now",lessThanMinute:"In less than a minute",inMinutes:"In {{count}} min",inHours:"In {{count}} hours"},actions:{stopPrint:"Stop Print",startPrint:"Start Print",requeue:"Re-queue"},bulkEdit:{title:"Edit {{count}} Item",title_plural:"Edit {{count}} Items",description:"Only changed settings will be applied to selected items.",printer:"Printer",noChange:"— No change —",queueOptions:"Queue Options",staged:"Staged (manual start)",autoPowerOff:"Auto power off after print",requirePrevious:"Require previous success",printOptions:"Print Options",bedLevelling:"Bed levelling",flowCalibration:"Flow calibration",vibrationCalibration:"Vibration calibration",layerInspection:"First layer inspection",timelapse:"Timelapse",useAms:"Use AMS",applyChanges:"Apply Changes",selectAll:"Select All",deselectAll:"Deselect All",selected:"{{count}} selected",editSelected:"Edit Selected",cancelSelected:"Cancel Selected"},confirm:{cancelTitle:"Cancel Scheduled Print",cancelMessage:'Are you sure you want to cancel "{{name}}"?',stopTitle:"Stop Print",stopMessage:'Are you sure you want to stop the current print "{{name}}"? This will cancel the print job on the printer.',removeTitle:"Remove from History",removeMessage:'Are you sure you want to remove "{{name}}" from the queue history?',clearHistoryTitle:"Clear History",clearHistoryMessage:"Are you sure you want to remove all {{count}} item(s) from the history?",cancelButton:"Cancel Print",stopButton:"Stop Print",thisPrint:"this print",thisItem:"this item"},toast:{cancelled:"Queue item cancelled",cancelFailed:"Failed to cancel item",removed:"Queue item removed",removeFailed:"Failed to remove item",stopped:"Print stopped",stopFailed:"Failed to stop print",released:"Print released to queue",startFailed:"Failed to start print",reorderFailed:"Failed to reorder queue",historyCleared:"Cleared {{count}} history item(s)",clearHistoryFailed:"Failed to clear history",updateFailed:"Failed to update items",bulkCancelled:"Cancelled {{count}} item(s)",bulkCancelFailed:"Failed to cancel items"},permissions:{noStopPrint:"You do not have permission to stop prints",noStartPrint:"You do not have permission to start prints",noEdit:"You do not have permission to edit this queue item",noCancel:"You do not have permission to cancel this queue item",noRequeue:"You do not have permission to re-queue items",noRemove:"You do not have permission to remove this queue item",noClearHistory:"You do not have permission to clear all history",noEditItems:"You do not have permission to edit queue items",noCancelItems:"You do not have permission to cancel queue items"}},backgroundDispatch:{unknownFile:"Unknown file",unknownPrinter:"Unknown printer",startingPrints:"Starting prints",progressSummary:"{{complete}}/{{total}} complete • Dispatched: {{dispatched}} • Processing: {{processing}}",expandDetails:"Expand dispatch details",collapseDetails:"Collapse dispatch details",dismissToast:"Dismiss dispatch toast",cancelDispatchJob:"Cancel dispatch job",cancel:"Cancel",cancelling:"Cancelling…",status:{dispatched:"Dispatched",processing:"Processing",completed:"Completed",failed:"Failed",cancelled:"Cancelled"},toast:{cancellingUpload:"Cancelling upload...",cancelled:"Dispatch cancelled",cancelFailed:"Failed to cancel dispatch",completeWithFailures:"Background dispatch complete: {{completed}} succeeded, {{failed}} failed",completeSuccess:"Background dispatch complete: {{completed}} succeeded",printStartedRemaining:"{{completed}} print(s) started, {{remaining}} more sending..."}},stats:{title:"Dashboard",subtitle:"Drag widgets to rearrange. Click the eye icon to hide.",overview:"Overview",totalPrints:"Total Prints",successRate:"Success Rate",totalPrintTime:"Total Print Time",printTime:"Print Time",totalFilament:"Total Filament Used",filamentUsed:"Filament Used",filamentCost:"Filament Cost",totalCost:"Total Cost",energyUsed:"Energy Used",energyCost:"Energy Cost",averagePrintTime:"Average Print Time",printsPerDay:"Prints per Day",byPrinter:"By Printer",printsByPrinter:"Prints by Printer",byMaterial:"By Material",byMonth:"By Month",last7Days:"Last 7 Days",last30Days:"Last 30 Days",last90Days:"Last 90 Days",allTime:"All Time",quickStats:"Quick Stats",printActivity:"Print Activity",filamentTypes:"Filament Types",filamentTrends:"Filament Trends",failureAnalysis:"Failure Analysis",timeAccuracy:"Time Accuracy",successful:"Successful:",failed:"Failed:",perfectEstimate:"100% = perfect estimate",noTimeAccuracyData:"No time accuracy data yet",noFilamentData:"No filament data available",noPrinterData:"No printer data available",noPrintData:"No print data available",noPrintDataLast30Days:"No print data in the last 30 days",failureReasons:"Failure Reasons",topFailureReasons:"Top Failure Reasons",failedPrintsCount:"{{failed}} / {{total}} prints failed",lastWeekRate:"Last week: {{rate}}%",resetLayout:"Reset Layout",recalculateCosts:"Recalculate Costs",recalculateCostsHint:"Recalculate all archive costs using current filament prices",exportStats:"Export Stats",exportAsCsv:"Export as CSV",exportAsExcel:"Export as Excel",hiddenCount:"{{count}} Hidden",exportDownloaded:"Export downloaded",exportFailed:"Export failed",layoutReset:"Layout reset",recalculatedCosts:"Recalculated costs for {{count}} archives",recalculateFailed:"Failed to recalculate costs",loadingStats:"Loading statistics...",noPermissionResetLayout:"You do not have permission to reset layout",noPermissionRecalculate:"You do not have permission to recalculate costs",noPrintDataInRange:"No print data in selected range",periodFilament:"Period Filament",periodCost:"Period Cost",avgPerPrint:"Avg per Print",usageOverTime:"Usage Over Time",filamentByWeight:"Weight",printDuration:"Print Duration",printerUtilization:"Printer Utilization",filamentSuccess:"Success by Material",printHabits:"Print Habits",printTimeOfDay:"Print Time of Day",colorDistribution:"Color Distribution",noColorData:"No color data available",records:"Records",longestPrint:"Longest Print",heaviestPrint:"Heaviest Print",mostExpensivePrint:"Most Expensive",busiestDay:"Busiest Day",successStreak:"Success Streak",streakPrint:"consecutive print",streakPrints:"{{count}} consecutive prints",printerStats:"Printer Stats",hours:"hours",avgPrints:"Avg. prints",noArchiveData:"No print data available",filamentByTime:"Time",avgWeight:"Avg. weight",avgTime:"Avg. time",filamentByPrints:"Prints",timeframe:{today:"Today","this-week":"This Week","this-month":"This Month","last-7":"Last 7 Days","last-30":"Last 30 Days","last-90":"Last 90 Days","this-year":"This Year","all-time":"All Time",custom:"Custom Range",from:"From",to:"To"}},maintenance:{title:"Maintenance",overview:"Overview",allOk:"All maintenance up to date",dueCount:"{{count}} item due",dueCount_plural:"{{count}} items due",warningCount:"{{count}} warning",warningCount_plural:"{{count}} warnings",totalPrintTime:"Total Print Time",nextMaintenance:"Next Maintenance",nothingDue:"Nothing due",tasks:"Tasks",lastPerformed:"Last performed",interval:"Interval",hoursRemaining:"{{hours}}h remaining",hoursOverdue:"{{hours}}h overdue",markDone:"Mark as Done",performMaintenance:"Perform Maintenance",history:"History",noHistory:"No maintenance history",editPrintHours:"Edit Print Hours",currentHours:"Current Hours",statusTab:"Status",settingsTab:"Settings",overdueCount:"{{count}} overdue",dueSoonCount:"{{count}} due soon",dueSoon:"Due soon",allGood:"All good",overdueBy:"Overdue by {{duration}}",dueIn:"Due in {{duration}}",timeLeft:"{{duration}} left",day:"1 day",days:"{{count}} days",week:"1 week",weeks:"{{count}} weeks",month:"1 month",months:"{{count}} months",year:"1 year",maintenanceTypes:"Maintenance Types",maintenanceTypesDescription:"System types and your custom maintenance tasks",addCustomType:"Add Custom Type",restoreDefaults:"Restore Default Tasks",intervalType:"Interval Type",intervalValue:"Interval ({{type}})",icon:"Icon",documentationLink:"Documentation Link (optional)",assignToPrinters:"Assign to Printers",selectAtLeastOnePrinter:"Select at least one printer",addType:"Add Type",custom:"Custom",printHours:"Print Hours",calendarDays:"Calendar Days",exampleName:"e.g., Replace HEPA Filter",viewDocumentation:"View documentation",timeBasedInterval:"Time-based interval",intervalOverrides:"Interval Overrides",intervalOverridesDescription:"Customize intervals for specific printers",assignedToPrinters:"Assigned to printers:",noPrintersAssigned:"No printers assigned",addPrinterShort:"Add:",printersAssignedClick:"{{count}} printer(s) assigned - click to manage",removeFromPrinter:"Remove from this printer",types:{lubricateCarbonRods:"Lubricate Carbon Rods",lubricateRails:"Lubricate Linear Rails",cleanNozzle:"Clean Nozzle/Hotend",checkBelts:"Check Belt Tension",cleanBuildPlate:"Clean Build Plate",checkExtruder:"Check Extruder Gears",checkCooling:"Check Cooling Fans",generalInspection:"General Inspection",cleanCarbonRods:"Clean Carbon Rods",lubricateSteelRods:"Lubricate Steel Rods",cleanSteelRods:"Clean Steel Rods",cleanLinearRails:"Clean Linear Rails",checkPtfeTube:"Check PTFE Tube",replaceHepaFilter:"Replace HEPA Filter",replaceCarbonFilter:"Replace Carbon Filter",lubricateLeftNozzleRail:"Lubricate Left Nozzle Rail"},maintenanceComplete:"Maintenance marked as complete",typeUpdated:"Maintenance type updated",typeDeleted:"Maintenance type deleted",defaultsRestored:"Restored {{count}} default task(s)",printHoursUpdated:"Print hours updated",printerAssigned:"Printer assigned",printerRemoved:"Printer removed",deleteTypeConfirm:'Delete "{{name}}"?',deleteSystemTypeTitle:"Delete default maintenance task?",deleteSystemTypeMessage:'Are you sure you want to delete the default maintenance task "{{name}}"?',noPermissionUpdate:"You do not have permission to update maintenance items",noPermissionPerform:"You do not have permission to perform maintenance",noPermissionEditTypes:"You do not have permission to edit maintenance types",noPermissionDeleteTypes:"You do not have permission to delete maintenance types",noPermissionEditHours:"You do not have permission to edit print hours",noPermissionRemovePrinter:"You do not have permission to remove printer assignments",noPermissionAssignPrinter:"You do not have permission to assign printers",noPermissionEditIntervals:"You do not have permission to edit intervals",configureSettings:"Configure maintenance types and intervals"},settings:{title:"Settings",general:"General",tabs:{general:"General",smartPlugs:"Smart Plugs",notifications:"Notifications",filament:"Filament",network:"Network",apiKeys:"API Keys",virtualPrinter:"Virtual Printer",users:"Authentication",backup:"Backup",emailAuth:"Email Authentication"},email:{smtpSettings:"SMTP Configuration",smtpHost:"SMTP Server",smtpPort:"SMTP Port",security:"Security",authentication:"Authentication",username:"Username",password:"Password",fromEmail:"From Email",fromName:"From Name",testConnection:"Test SMTP Connection",testRecipient:"Test Recipient Email",sendTest:"Send Test Email",sending:"Sending...",save:"Save Settings",saving:"Saving...",advancedAuth:"Advanced Authentication",advancedAuthEnabled:"Advanced Authentication is enabled",advancedAuthEnabledDesc:"Email-based user management features are active. New users will receive auto-generated passwords via email, and users can reset their passwords through the forgot password feature.",advancedAuthDisabled:"Advanced Authentication is disabled",advancedAuthDisabledDesc:"Enable advanced authentication to activate email-based features for user management.",enable:"Enable",disable:"Disable",feature1:"Passwords are auto-generated and emailed to new users",feature2:"Users can login with username or email",feature3:"Forgot password feature is available",feature4:"Admins can reset user passwords via email",errors:{requiredFields:"Please fill in all required fields",usernameRequired:"Username is required when authentication is enabled",enterTestEmail:"Please enter a test email address",smtpServerAndEmail:"Please fill in SMTP Server and From Email before testing",usernamePasswordRequired:"Username and Password are required when authentication is enabled",configureSmtpFirst:"Please configure and test SMTP settings first",enableAuthFirst:"Please enable authentication first to use email-based features."},success:{settingsSaved:"SMTP settings saved successfully"},securityOptions:{starttls:"STARTTLS (Port 587)",ssl:"SSL/TLS (Port 465)",none:"None (Port 25)"},authOptions:{enabled:"Enabled",disabled:"Disabled"}},appearance:"Appearance",notifications:"Notifications",smartPlugs:"Smart Plugs",spoolman:"Spoolman",updates:"Updates",language:"Language",languageDescription:"Select your preferred language",theme:"Theme",themeLight:"Light",themeDark:"Dark",themeSystem:"System",defaultView:"Default View",defaultViewDescription:"Page to show when opening the app",checkForUpdates:"Check for Updates",autoUpdate:"Auto Update",currentVersion:"Current Version",latestVersion:"Latest Version",upToDate:"You are up to date",updateAvailable:"Update available",notificationLanguage:"Notification Language",notificationLanguageDescription:"Language for push notifications",bedCooledThreshold:"Bed Cooled Threshold",bedCooledThresholdDescription:"Temperature below which the bed is considered cooled after a print",userNotificationsEnabled:"User Notifications",userNotificationsEnabledDescription:"Enable the user notifications menu and email notifications for print job events. Requires Advanced Authentication.",userNotificationsDisabledHint:"Enable Advanced Authentication to use user notifications.",notificationProviders:"Notification Providers",addProvider:"Add Provider",editProvider:"Edit Provider",providerType:"Provider Type",testNotification:"Test Notification",testSuccess:"Test notification sent successfully",testFailed:"Failed to send test notification",quietHours:"Quiet Hours",quietHoursDescription:"Do not disturb during these hours",quietHoursStart:"Start",quietHoursEnd:"End",events:{title:"Notification Events",printStart:"Print Started",printComplete:"Print Completed",printFailed:"Print Failed",printStopped:"Print Stopped",printProgress:"Progress Milestones",printProgressDescription:"Notify at 25%, 50%, 75%",printerOffline:"Printer Offline",printerError:"Printer Error",filamentLow:"Low Filament",maintenanceDue:"Maintenance Due",maintenanceDueDescription:"Notify when maintenance is needed"},smartPlug:{title:"Smart Plugs",add:"Add Smart Plug",edit:"Edit Smart Plug",name:"Name",ipAddress:"IP Address",linkedPrinter:"Linked Printer",autoOn:"Auto Power On",autoOnDescription:"Turn on when print starts",autoOff:"Auto Power Off",autoOffDescription:"Turn off after print completes",offDelay:"Off Delay",offDelayMinutes:"Minutes after print",offDelayTemp:"When nozzle below temperature",currentState:"Current State",turnOn:"Turn On",turnOff:"Turn Off"},filamentTracking:"Filament Tracking",filamentTrackingDesc:"Choose how to track your filament spools. You can use the built-in inventory or connect an external Spoolman server.",filamentChecks:"Filament checks",disableFilamentWarnings:"Disable filament warnings",disableFilamentWarningsDesc:"Don't show warnings about insufficient filament when printing or queueing",trackingModeBuiltIn:"Built-in Inventory",trackingModeBuiltInDesc:"RFID auto-matching and usage tracking included",trackingModeSpoolmanDesc:"External filament management server",builtInFeatureRfid:"Automatically detects Bambu Lab RFID spools in AMS",builtInFeatureUsage:"Tracks filament consumption per print",builtInFeatureCatalog:"Manage spools, colors, and K-factor profiles",builtInFeatureThirdParty:"Third-party spools can be assigned to inventory spools",amsSyncButton:"Sync Weights from AMS",amsSyncTitle:"Sync Spool Weights from AMS",amsSyncMessage:"This will overwrite all inventory spool weights with the current AMS remain% values from connected printers. Use this to recover from corrupted weight data. Printers must be online.",amsSyncing:"Syncing...",amsSyncSuccess:"{{synced}} spool(s) synced, {{skipped}} skipped",amsSyncError:"Failed to sync weights from AMS",spoolmanUrl:"Spoolman URL",spoolmanUrlHint:"URL of your Spoolman server (e.g., http://localhost:7912)",spoolmanConnected:"Connected",spoolmanDisconnected:"Disconnected",status:"Status",connect:"Connect",disconnect:"Disconnect",howSyncWorks:"How Sync Works",syncInfoRfidOnly:"Only official Bambu Lab spools with RFID are synced",syncInfoAutoCreate:"New spools are auto-created in Spoolman on first sync",syncInfoThirdPartySkipped:"Non-Bambu Lab spools (third-party, refilled) are skipped",linkingExistingSpools:"Linking Existing Spools",linkingExistingSpoolsDesc:'To link existing Spoolman spools to your AMS, hover over an AMS slot and click "Link to Spoolman".',syncMode:"Sync Mode",syncModeAuto:"Automatic",syncModeManual:"Manual Only",syncModeAutoDesc:"AMS data syncs automatically when changes are detected",syncModeManualDesc:"Only sync when manually triggered",syncAmsData:"Sync AMS Data",syncAmsDataDesc:"Manually sync printer AMS data to Spoolman",allPrinters:"All Printers",noDefaultPrinter:"No default (ask each time)",sidebarOrder:"Sidebar order",saveThumbnails:"Save thumbnails",captureFinishPhoto:"Capture finish photo",noPrintersConfigured:"No printers configured",archiveMode:{always:"Always create archive entry",never:"Never create archive entry",ask:"Ask each time"},checkForUpdatesLabel:"Check for updates",checkPrinterFirmware:"Check printer firmware",includeBetaUpdates:"Include beta versions",includeBetaUpdatesDesc:"Notify about beta and prerelease versions when checking for updates",enableRetry:"Enable retry",homeAssistantDescription:"Control smart plugs via Home Assistant",environmentManagedLabel:"(Environment Managed)",autoEnabledViaEnv:"Automatically enabled via environment variables",urlFromEnvReadOnly:"Value set by HA_URL environment variable (read-only)",tokenFromEnvReadOnly:"Value set by HA_TOKEN environment variable (read-only)",mqttConnectedTo:"Connected to",prometheusDescription:"Expose printer data in Prometheus format",noSmartPlugsTitle:"No smart plugs configured",noSmartPlugsDescription:"Add a Tasmota-based smart plug to track energy usage and automate power control.",noProvidersTitle:"No providers configured",noProvidersDescription:"Add a provider to receive alerts.",noTemplatesAvailable:"No templates available. Restart the backend to seed default templates.",apiPermissionView:"View printer status and queue",apiPermissionEdit:"Add and remove items from print queue",apiKeysEmptyTitle:"No API keys",apiKeysEmptyDescription:"Create an API key to integrate with external services.",noUsersFound:"No users found",noGroupsFound:"No groups found",noGroupsAvailable:"No groups available",passwordsDoNotMatch:"Passwords do not match",systemGroupWarning:"System group names cannot be changed",authDisabledTitle:"Authentication is Disabled",authDisabledFeature1:"Require login to access the system",authDisabledFeature2:"Create multiple users with group-based permissions",authDisabledFeature3:"Control access with 50+ granular permissions",userHasCreated:"This user has created:",userItemsQuestion:"What would you like to do with these items?",deleteUserConfirm:"Are you sure you want to delete this user?",actionCannotBeUndone:"This action cannot be undone.",addFirstSmartPlug:"Add Your First Smart Plug",providers:"Providers",log:"Log",testAll:"Test All",testResults:"Test Results",testPassedCount:"{{count}} passed",testFailedCount:"{{count}} failed",messageTemplates:"Message Templates",messageTemplatesDescription:"Customize notification messages for each event.",apiKeys:"API Keys",apiKeysDescription:"Create API keys for external integrations and webhooks.",createKey:"Create Key",apiKeyCreated:"API Key Created Successfully",apiKeyCopyWarning:"Copy this key now - it won't be shown again!",useInApiBrowser:"Use in API Browser",createNewApiKey:"Create New API Key",keyName:"Key Name",keyNamePlaceholder:"e.g., Home Assistant, OctoPrint",readStatus:"Read Status",readStatusDescription:"View printer status and queue",manageQueue:"Manage Queue",manageQueueDescription:"Add and remove items from print queue",controlPrinter:"Control Printer",controlPrinterDescription:"Pause, resume, and stop prints",unnamedKey:"Unnamed Key",lastUsed:"Last used",read:"Read",control:"Control",createFirstKey:"Create Your First Key",webhookEndpoints:"Webhook Endpoints",webhookApiKeyHint:"Use your API key in the X-API-Key header.",webhook:{getAllStatus:"Get all printer status",getSpecificStatus:"Get specific printer status",addToQueue:"Add to print queue",pausePrint:"Pause print",resumePrint:"Resume print",stopPrint:"Stop print"},apiBrowser:"API Browser",apiBrowserDescription:"Explore and test all available API endpoints.",apiKeyForTesting:"API Key for Testing",apiKeyPlaceholder:"Paste your API key here to test authenticated endpoints...",apiKeyHint:"This key will be sent as X-API-Key header with requests.",deleteApiKeyTitle:"Delete API Key",deleteApiKeyMessage:"Are you sure you want to delete this API key? Any integrations using this key will stop working.",deleteKey:"Delete Key",amsDisplayThresholds:"AMS Display Thresholds",amsThresholdsDescription:"Configure color thresholds for AMS humidity and temperature indicators.",humidity:"Humidity",goodGreen:"Good (green)",fairOrange:"Fair (orange)",aboveFairBad:"Above fair threshold shows as red (bad)",fairAlsoDryingThreshold:"This threshold is also used to trigger auto-drying when enabled",temperature:"Temperature",goodBlue:"Good (blue)",aboveFairHot:"Above fair threshold shows as red (hot)",historyRetention:"History Retention",keepSensorHistory:"Keep sensor history for",historyRetentionDescription:"Older humidity and temperature data will be automatically deleted",queueDrying:"Queue Auto-Drying",queueDryingDescription:"Automatically dry AMS filament when printer is idle between queued prints. Uses humidity threshold above to trigger drying.",queueDryingEnabled:"Enable auto-drying",queueDryingEnabledDescription:"Start AMS drying automatically when printer is idle and humidity is above threshold",queueDryingBlock:"Wait for drying to complete",queueDryingBlockDescription:"Block the print queue until drying finishes. When off, prints take priority over drying.",ambientDryingEnabled:"Ambient drying",ambientDryingEnabledDescription:"Automatically dry filament on idle printers when humidity exceeds threshold, even without queued prints.",dryingPresets:"Drying Presets",dryingPresetsDescription:"Temperature and duration per filament type. AMS 2 Pro uses lower temps, AMS-HT supports higher temps.",dryingFilament:"Filament",printModal:"Print Modal",expandCustomMapping:"Expand custom mapping by default",expandCustomMappingDescription:"When printing to multiple printers, show per-printer AMS mapping expanded",authentication:"Authentication",authEnabledDescription:"Your instance is secured with user authentication",authDisabledDescription:"Enable to require login and manage user access",authDisabledMessage:"Enable authentication to create user accounts, manage permissions, and secure your Bambuddy instance.",enableAuthentication:"Enable Authentication",currentUser:"Current User",changePassword:"Change Password",admin:"Admin",users:"Users",addUser:"Add User",groups:"Groups",addGroup:"Add Group",system:"System",noDescription:"No description",userCount:"{{count}} users",permissionCount:"{{count}} permissions",createUser:"Create User",username:"Username",enterUsername:"Enter username",password:"Password",enterPassword:"Enter password (min 6 characters)",confirmPassword:"Confirm Password",confirmPasswordPlaceholder:"Confirm password",viewReleaseOnGitHub:"View release on GitHub",turnAllPlugsOn:"Turn all plugs on",turnAllPlugsOff:"Turn all plugs off",clearNotificationLogs:"Clear Notification Logs",clearLogsMessage:"This will permanently delete all notification logs older than 30 days. This action cannot be undone.",clearLogs:"Clear Logs",resetUiPreferences:"Reset UI Preferences",resetUiPreferencesMessage:"This will reset all UI preferences to defaults: sidebar order, theme, dashboard layout, view modes, and sorting preferences. Your printers, archives, and server settings will NOT be affected. The page will reload after clearing.",resetPreferences:"Reset Preferences",deleteGroupTitle:"Delete Group",deleteGroupMessage:"Are you sure you want to delete this group? Users in this group will lose these permissions.",deleteGroup:"Delete Group",disableAuthenticationTitle:"Disable Authentication",disableAuthenticationMessage:"Are you sure you want to disable authentication? This will make your Bambuddy instance accessible without login. All users will remain in the database but authentication will be disabled.",disableAuthentication:"Disable Authentication",configureBambuddy:"Configure Bambuddy",systemDefault:"System Default",archiveSettings:"Archive Settings",newWindow:"New Window",embeddedOverlay:"Embedded Overlay",preferredSlicer:"Preferred Slicer",preferredSlicerDescription:"Choose which slicer application to open files with",externalCameras:"External Cameras",costTracking:"Cost Tracking",printsOnly:"Prints Only",totalConsumption:"Total Consumption",dataManagement:"Data Management",storageUsage:"Storage Usage",storageUsageDescription:"Breakdown of data usage by category",storageUsageTotal:"Total",storageUsageErrors:"Errors",storageUsageOtherBreakdown:"Other (includes static assets, scripts, and configuration files)",storageUsageSystem:"System",storageUsageData:"Data",storageUsageUnavailable:"Storage usage information unavailable",clearNotificationLogsDescription:"Delete notification logs older than 30 days",resetUiPreferencesDescription:"Reset sidebar order, theme, view modes, and layout preferences. Printers, archives, and settings are not affected.",enableHomeAssistant:"Enable Home Assistant",enableMqtt:"Enable MQTT",useTls:"Use TLS",enableMetricsEndpoint:"Enable Metrics Endpoint",availableMetrics:"Available Metrics",editUser:"Edit User",deleteUserTitle:"Delete User",groupName:"Group Name",leaveEmptyForAnonymous:"Leave empty for anonymous",leaveEmptyForNoAuth:"Leave empty for no authentication",enterNewPassword:"Enter new password",confirmNewPassword:"Confirm new password",enterGroupName:"Enter group name",enterDescriptionOptional:"Enter description (optional)",enterCurrentPassword:"Enter current password",enterNewPasswordMin6:"Enter new password (min 6 characters)",toast:{keyCopied:"Key copied to clipboard",copyFailed:"Failed to copy key",keyAddedToBrowser:"Key added to API Browser",clearLogsFailed:"Failed to clear logs",uiPreferencesReset:"UI preferences reset. Refreshing...",authDisabled:"Authentication disabled successfully",authDisableFailed:"Failed to disable authentication",apiKeyCreated:"API key created",apiKeyDeleted:"API key deleted",userCreated:"User created successfully",userUpdated:"User updated successfully",userDeleted:"User deleted successfully",groupCreated:"Group created successfully",groupUpdated:"Group updated successfully",groupDeleted:"Group deleted successfully",fillRequiredFields:"Please fill in all required fields",passwordsDoNotMatch:"Passwords do not match",passwordTooShort:"Password must be at least 6 characters",enterGroupName:"Please enter a group name",settingsSaved:"Settings saved",cameraSettingsSaved:"Camera settings saved",enterCameraUrl:"Please enter a camera URL",passwordChanged:"Password changed successfully",connectionFailed:"Connection failed",testFailed:"Test failed",cameraConnected:"Camera connected{{resolution}}"},testConnection:"Test Connection",catalog:{spoolCatalog:"Spool Catalog",spoolCatalogDescription:"Empty spool weights by brand/type. Used for automatic weight lookup when adding spools.",searchCatalog:"Search catalog...",addNewEntry:"Add New Entry",namePlaceholder:"Name (e.g., Bambu Lab - Plastic)",weight:"Weight",type:"Type",default:"Default",custom:"Custom",noMatch:"No entries match your search",empty:"No entries in catalog",deleteEntry:"Delete Entry",deleteConfirm:'Are you sure you want to delete "{{name}}"?',resetCatalog:"Reset Catalog",resetConfirm:"Reset catalog to defaults? This will remove all custom entries.",loadFailed:"Failed to load spool catalog",nameWeightRequired:"Name and weight are required",entryAdded:"Entry added",addFailed:"Failed to add entry",entryUpdated:"Entry updated",updateFailed:"Failed to update entry",entryDeleted:"Entry deleted",deleteFailed:"Failed to delete entry",resetSuccess:"Catalog reset to defaults",resetFailed:"Failed to reset catalog",exported:"Exported {{count}} entries",imported:"Imported {{added}} entries ({{skipped}} skipped)",importFailed:"Failed to import: invalid JSON format",exportTooltip:"Export catalog to JSON",importTooltip:"Import catalog from JSON",resetTooltip:"Reset to defaults",selectedCount:"{{count}} selected",deleteSelected:"Delete Selected",bulkDeleteConfirm:"Are you sure you want to delete {{count}} entries?",bulkDeleted:"Deleted {{count}} entries",bulkDeleteFailed:"Failed to delete entries"},colorCatalog:{title:"Color Catalog",description:"Filament colors by manufacturer/material. Used for automatic color lookup when adding spools.",searchColors:"Search colors...",allManufacturers:"All manufacturers",addNewColor:"Add New Color",manufacturer:"Manufacturer",colorName:"Color Name",hex:"Hex",materialOptional:"Material (optional)",showing:"Showing {{filtered}} of {{total}} colors",noMatch:"No colors match your search",empty:"No colors in catalog",deleteColor:"Delete Color",deleteConfirm:'Are you sure you want to delete "{{name}}"?',resetCatalog:"Reset Color Catalog",resetConfirm:"Reset catalog to defaults? This will remove all custom colors.",sync:"Sync",starting:"Starting...",syncTooltip:"Sync from FilamentColors.xyz (2000+ colors, may take a minute)",loadFailed:"Failed to load color catalog",fieldsRequired:"Manufacturer, color name, and hex color are required",colorAdded:"Color added",addFailed:"Failed to add color",colorUpdated:"Color updated",updateFailed:"Failed to update color",colorDeleted:"Color deleted",deleteFailed:"Failed to delete color",resetSuccess:"Color catalog reset to defaults",resetFailed:"Failed to reset catalog",syncUpToDate:"Already up to date ({{count}} colors checked)",syncComplete:"Added {{added}} new colors ({{skipped}} already existed)",syncError:"Sync error",syncFailed:"Failed to sync from FilamentColors.xyz",exported:"Exported {{count}} colors",imported:"Imported {{added}} colors ({{skipped}} skipped)",importFailed:"Failed to import: invalid JSON format",selectedCount:"{{count}} selected",deleteSelected:"Delete Selected",bulkDeleteConfirm:"Are you sure you want to delete {{count}} colors?",bulkDeleted:"Deleted {{count}} colors",bulkDeleteFailed:"Failed to delete colors"},dateFormat:"Date Format",dateFormatUs:"US (MM/DD/YYYY)",dateFormatEu:"EU (DD/MM/YYYY)",dateFormatIso:"ISO (YYYY-MM-DD)",timeFormat:"Time Format",timeFormat12:"12-hour (3:30 PM)",timeFormat24:"24-hour (15:30)",defaultPrinter:"Default Printer",defaultPrinterDescription:"Pre-select this printer for uploads, reprints, and other operations.",slicerBambuStudio:"Bambu Studio",slicerOrcaSlicer:"OrcaSlicer",sidebarOrderDescription:"Drag items in the sidebar to reorder. Reset to default order here.",setDefault:"Set Default",sidebarOrderSetDefaultHint:"Set default applies the current menu order to users who haven't customized theirs.",sidebarDefaultSet:"Default menu order has been set.",sidebarDefaultCleared:"Default menu order cleared.",sidebarDefaultFailed:"Failed to set default menu order.",reset:"Reset",darkMode:"Dark Mode",lightMode:"Light Mode",active:"(active)",background:"Background",accent:"Accent",style:"Style",bgNeutral:"Neutral",bgWarm:"Warm",bgCool:"Cool",bgOled:"OLED Black",bgSlate:"Slate Blue",bgForest:"Forest Green",accentGreen:"Green",accentTeal:"Teal",accentBlue:"Blue",accentOrange:"Orange",accentPurple:"Purple",accentRed:"Red",styleClassic:"Classic",styleGlow:"Glow",styleVibrant:"Vibrant",themeToggleHint:"Toggle between dark and light mode using the sun/moon icon in the sidebar.",autoArchivePrints:"Auto-archive prints",autoArchiveDescription:"Automatically save 3MF files when prints complete",saveThumbnailsDescription:"Extract and save preview images from 3MF files",captureFinishPhotoDescription:"Take a photo from printer camera when print completes",ffmpegNotInstalled:"ffmpeg not installed",ffmpegRequired:"Camera capture requires ffmpeg. Install it via brew install ffmpeg (macOS) or apt install ffmpeg (Linux).",camera:"Camera",cameraViewMode:"Camera View Mode",cameraOverlayDescription:"Camera opens in a resizable overlay on the main screen",cameraWindowDescription:"Camera opens in a separate browser window",externalCamerasDescription:"Configure external cameras to replace the built-in printer camera. Supports MJPEG streams, RTSP, HTTP snapshots, and USB cameras (V4L2). When enabled, the external camera is used for live view and finish photos.",cameraPlaceholderUsb:"Device path (/dev/video0)",cameraPlaceholderUrl:"Camera URL (rtsp://... or http://...)",cameraTypeMjpeg:"MJPEG Stream",cameraTypeRtsp:"RTSP Stream",cameraTypeSnapshot:"HTTP Snapshot",cameraTypeUsb:"USB Camera (V4L2)",cameraRotation:"Rotation",test:"Test",connected:"Connected",disconnected:"Disconnected",currency:"Currency",defaultFilamentCost:"Default filament cost (per kg)",electricityCost:"Electricity cost per kWh",energyDisplayMode:"Energy display mode",energyModePrintDescription:"Dashboard shows sum of energy used during prints",energyModeTotalDescription:"Dashboard shows lifetime energy from smart plugs",fileManager:"File Manager",createArchiveEntry:"Create Archive Entry When Printing",createArchiveEntryDescription:"When printing from File Manager, optionally create an archive entry",lowDiskSpaceWarning:"Low Disk Space Warning",lowDiskSpaceDescription:"Show warning when free disk space falls below this threshold",printerFirmware:"Printer Firmware",checkFirmwareDescription:"Check for printer firmware updates from Bambu Lab",bambuddySoftware:"Bambuddy Software",autoCheckDescription:"Automatically check for new versions on startup",checkNow:"Check now",updateAvailableVersion:"Update available: v{{version}}",releaseNotes:"Release Notes",updateViaDocker:"Update via Docker Compose:",installUpdate:"Install Update",latestVersionRunning:"You're running the latest version",failedToCheckUpdates:"Failed to check for updates: {{error}}",backupRestore:"Backup & Restore",backupRestoreDescription:"Export/import settings and configure GitHub backup",goToBackup:"Go to Backup",externalUrl:"External URL",externalUrlDescription:"The external URL where Bambuddy is accessible. Used for notification images and external integrations.",bambuddyUrl:"Bambuddy URL",externalUrlHint:"Include protocol and port (e.g., http://192.168.1.100:8000)",ftpRetry:"FTP Retry",ftpRetryDescription:"Retry FTP operations when printer WiFi is unreliable. Applies to 3MF downloads, print uploads, timelapse downloads, and firmware updates.",autoRetryDescription:"Automatically retry failed FTP operations",retryAttempts:"Retry attempts",retryDelay:"Retry delay",connectionTimeout:"Connection timeout",time_one:"{{count}} time",time_other:"{{count}} times",second_one:"{{count}} second",second_other:"{{count}} seconds",nSeconds:"{{count}} seconds",increaseForWeakWifi:"Increase for printers with weak WiFi",homeAssistant:"Home Assistant",homeAssistantFullDescription:"Connect to Home Assistant to control smart plugs via HA's REST API. Supports switch, light, input_boolean, and script entities.",homeAssistantUrl:"Home Assistant URL",longLivedAccessToken:"Long-Lived Access Token",haTokenHint:"Create a token in HA: Profile → Long-Lived Access Tokens → Create Token",connectionSuccessful:"Connection Successful",connectionFailed:"Connection Failed",haConnectionSuccess:"Successfully connected to Home Assistant.",haConnectionFailed:"Failed to connect to Home Assistant.",mqttPublishing:"MQTT Publishing",mqttDescription:"Publish BamBuddy events to an external MQTT broker for integration with Node-RED, Home Assistant, and other automation systems.",mqttEnableDescription:"Publish events to external MQTT broker",brokerHostname:"Broker hostname",port:"Port",usernameOptional:"Username (optional)",passwordOptional:"Password (optional)",topicPrefix:"Topic prefix",topicPrefixHint:"Topics will be: {{prefix}}/printers//status, etc.",prometheusMetrics:"Prometheus Metrics",prometheusEndpointDescription:"Expose printer metrics at /api/v1/metrics for Prometheus/Grafana monitoring.",bearerTokenOptional:"Bearer Token (optional)",bearerTokenHint:"If set, requests must include Authorization: Bearer ",metricsConnectionStatus:"Connection status",metricsPrinterState:"Printer state (idle/printing/etc)",metricsPrintProgress:"Print progress 0-100%",metricsBedTemp:"Bed temperature",metricsNozzleTemp:"Nozzle temperature",metricsPrintsTotal:"Total prints by result",metricsMore:"...and more (layers, fans, queue, filament usage)",smartPlugsDescription:"Connect smart plugs (Tasmota or Home Assistant) to automate power control and track energy usage for your printers.",allOn:"All On",allOff:"All Off",addSmartPlug:"Add Smart Plug",energySummary:"Energy Summary",currentPower:"Current Power",plugsOnline:"{{reachable}}/{{total}} plugs online",today:"Today",yesterday:"Yesterday",total:"Total",enablePlugsForSummary:"Enable plugs to see energy summary",addNotificationProvider:"Add",systemBadge:"(System)",creating:"Creating...",changing:"Changing...",deleteUserAndItems:"Delete user AND their items",deleteUserKeepItems:"Delete user, keep items (become ownerless)",ok:"OK"},notification:{printStarted:{title:"Print Started",body:"{{printer}}: {{filename}} has started printing"},printCompleted:{title:"Print Completed",body:"{{printer}}: {{filename}} completed successfully"},printFailed:{title:"Print Failed",body:"{{printer}}: {{filename}} has failed"},printStopped:{title:"Print Stopped",body:"{{printer}}: {{filename}} was stopped"},printProgress:{title:"Print Progress",body:"{{printer}}: {{filename}} is {{percent}}% complete"},printerOffline:{title:"Printer Offline",body:"{{printer}} is offline"},printerError:{title:"Printer Error",body:"{{printer}}: {{error}}"},filamentLow:{title:"Low Filament",body:"{{printer}}: Filament is running low"},maintenanceDue:{title:"Maintenance Due",body:"{{printer}}: {{items}} need attention"}},errors:{generic:"Something went wrong",networkError:"Network error. Please check your connection.",notFound:"Not found",unauthorized:"Unauthorized",serverError:"Server error",validationError:"Please check your input",printerConnectionFailed:"Failed to connect to printer",saveFailed:"Failed to save changes",deleteFailed:"Failed to delete",loadFailed:"Failed to load data"},hmsErrors:{title:"Errors - {{name}}",noErrors:"No errors",viewOnWiki:"View on Bambu Lab Wiki",clearInstructions:"Clear errors on the printer to dismiss them here.",clearErrors:"Clear Errors",clearSuccess:"HMS errors cleared",clearFailed:"Failed to clear HMS errors"},mqttDebug:{title:"MQTT Debug Log",searchPlaceholder:"Search topic or payload...",noMessages:"No messages logged yet",startLoggingHint:'Click "Start Logging" to begin capturing MQTT messages',noMessagesMatch:"No messages match your filter",adjustFilterHint:"Try adjusting your search or filter criteria",incoming:"Incoming",outgoing:"Outgoing",loggingStopped:"Logging stopped",loggingActive:"Logging active - messages will auto-refresh",startLogging:"Start Logging",stopLogging:"Stop Logging",clearLog:"Clear Log",topic:"Topic",timestamp:"Timestamp",direction:"Direction",all:"All"},printerFiles:{title:"File Manager",storageUsed:"Used:",storageFree:"Free:",filterPlaceholder:"Filter files...",deleteButton:"Delete",deleteFiles:"Delete {{count}} Files",deleteFileConfirm:'Delete "{{name}}"? This cannot be undone.',deleteFilesConfirm:"Delete {{count}} selected files? This cannot be undone.",noFiles:"No files on printer",loadingFiles:"Loading files...",failedToLoad:"Failed to load files",toast:{filesDeleted:"Deleted {{count}} file(s)",deleteFailed:"Delete failed: {{error}}"}},confirm:{delete:"Are you sure you want to delete this?",unsavedChanges:"You have unsaved changes. Are you sure you want to leave?",clearQueue:"Are you sure you want to clear the queue?"},login:{title:"Bambuddy Login",subtitle:"Sign in to your account",username:"Username",usernamePlaceholder:"Enter your username",usernameOrEmail:"Username or Email",usernameOrEmailPlaceholder:"Username or @ Email",password:"Password",passwordPlaceholder:"Enter your password",signIn:"Sign in",signingIn:"Logging in...",forgotPassword:"Forgot your password?",loginSuccess:"Logged in successfully",loginFailed:"Login failed",enterCredentials:"Please enter username and password",forgotPasswordTitle:"Forgot Password",forgotPasswordMessage:"If you've forgotten your password, please contact your system administrator to reset it.",forgotPasswordEmailMessage:"Enter your email address and we'll send you a new password.",emailAddress:"Email Address",emailPlaceholder:"your.email@example.com",cancel:"Cancel",sending:"Sending...",sendResetEmail:"Send Reset Email",howToReset:"How to reset your password:",resetStep1:"Contact your Bambuddy administrator",resetStep2:"Ask them to reset your password in User Management",resetStep3:"They can set a new temporary password for you",resetStep4:"Log in with the new password and change it in Settings",gotIt:"Got it"},setup:{title:"Bambuddy Setup",subtitle:"Configure authentication for your Bambuddy instance",enableAuth:"Enable Authentication",adminAccount:"Admin Account",adminAccountDesc:"If admin users already exist, authentication will be enabled using the existing admin accounts. Leave the fields below empty to use existing admins, or enter new credentials to create a new admin user.",adminUsername:"Admin Username",adminPassword:"Admin Password",optionalIfAdminExists:"(optional if admin users exist)",adminUsernamePlaceholder:"Enter admin username (optional)",adminPasswordPlaceholder:"Enter admin password (optional)",confirmPassword:"Confirm Password",confirmPasswordPlaceholder:"Confirm admin password",settingUp:"Setting up...",completeSetup:"Complete Setup",toast:{authEnabledAdminCreated:"Authentication enabled and admin user created",authEnabledExistingAdmins:"Authentication enabled using existing admin users",setupCompleted:"Setup completed",enterBothCredentials:"Please enter both admin username and password, or leave both empty to use existing admin users",passwordsDoNotMatch:"Passwords do not match",passwordTooShort:"Password must be at least 6 characters"}},changePassword:{title:"Change Password",currentPassword:"Current Password",currentPasswordPlaceholder:"Enter current password",newPassword:"New Password",newPasswordPlaceholder:"Enter new password (min 6 characters)",confirmPassword:"Confirm New Password",confirmPasswordPlaceholder:"Confirm new password",passwordsDoNotMatch:"Passwords do not match",passwordTooShort:"Password must be at least 6 characters",changing:"Changing...",success:"Password changed successfully",failed:"Failed to change password"},plateAlert:{title:"Print Paused!",message:"Objects detected on build plate. The print has been automatically paused. Please clear the plate and resume the print.",understand:"I Understand"},camera:{title:"Camera View",invalidPrinterId:"Invalid printer ID",live:"Live",snapshot:"Snapshot",restartStream:"Restart stream",refreshSnapshot:"Refresh snapshot",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen",connectingToCamera:"Connecting to camera...",capturingSnapshot:"Capturing snapshot...",connectionLost:"Connection lost",connectionFailed:"Camera connection failed",reconnecting:"Reconnecting in {{countdown}}s... (attempt {{attempt}}/{{max}})",reconnectNow:"Reconnect now",cameraUnavailable:"Camera unavailable",cameraUnavailableDesc:"Make sure the printer is powered on and connected.",noCamera:"No camera available",retry:"Retry",cameraStream:"Camera stream",zoomOut:"Zoom out",zoomIn:"Zoom in",resetZoom:"Reset zoom",recording:"Recording",startRecording:"Start Recording",stopRecording:"Stop Recording",chamberLight:"Toggle chamber light"},groups:{title:"Group Management",subtitle:"Manage permission groups for access control",backToSettings:"Back to Settings",createGroup:"Create Group",noPermission:"You do not have permission to access this page.",system:"System",noDescription:"No description",usersCount:"{{count}} users",permissionsCount:"{{count}} permissions",edit:"Edit",delete:"Delete",toast:{created:"Group created successfully",updated:"Group updated successfully",deleted:"Group deleted successfully",enterGroupName:"Please enter a group name"},modal:{editGroup:"Edit Group",createGroup:"Create Group",cancel:"Cancel",saving:"Saving...",creating:"Creating...",saveChanges:"Save Changes"},form:{groupName:"Group Name",groupNamePlaceholder:"Enter group name",systemGroupWarning:"System group names cannot be changed",description:"Description",descriptionPlaceholder:"Enter description (optional)",permissions:"Permissions ({{count}} selected)"},deleteModal:{title:"Delete Group",message:"Are you sure you want to delete this group? Users in this group will lose these permissions.",confirm:"Delete Group"},editor:{title:"Edit Group",createTitle:"Create Group",search:"Search permissions...",selectAll:"Select All",clearAll:"Clear All",permissionsSelected:"{{count}} selected",noResults:"No permissions match your search"}},users:{title:"User Management",subtitle:"Manage users and their access to your Bambuddy instance",backToSettings:"Back to Settings",createUser:"Create User",noPermission:"You do not have permission to access this page.",admin:"Admin",noGroups:"No groups",active:"Active",inactive:"Inactive",edit:"Edit",delete:"Delete",system:"System",noGroupsAvailable:"No groups available",table:{username:"Username",groups:"Groups",status:"Status",actions:"Actions"},toast:{created:"User created successfully",updated:"User updated successfully",deleted:"User deleted successfully",fillRequired:"Please fill in all required fields",passwordsDoNotMatch:"Passwords do not match",passwordTooShort:"Password must be at least 6 characters"},modal:{createUser:"Create User",editUser:"Edit User",cancel:"Cancel",creating:"Creating...",saving:"Saving...",saveChanges:"Save Changes",advancedAuthSubtitle:"with Advanced Authentication"},form:{username:"Username",usernamePlaceholder:"Enter username",email:"Email",emailPlaceholder:"user@example.com",password:"Password",passwordPlaceholder:"Enter password",confirmPassword:"Confirm Password",confirmPasswordPlaceholder:"Confirm password",newPasswordPlaceholder:"Enter new password",confirmNewPasswordPlaceholder:"Confirm new password",leaveBlankToKeep:"leave blank to keep current",groups:"Groups",optional:"optional",autoGeneratedPassword:"A secure password will be automatically generated and emailed to the user.",passwordManagedByAdvancedAuth:'Password is managed by Advanced Authentication. Use "Reset Password" to send a new password to the user via email.',resetPassword:"Reset Password",resettingPassword:"Resetting Password..."},deleteModal:{title:"Delete User",message:"Are you sure you want to delete this user? This action cannot be undone.",confirm:"Delete User"}},streamOverlay:{title:"Stream Overlay",invalidPrinterId:"Invalid printer ID",cameraStream:"Camera stream",progress:"Progress",eta:"ETA",printerIdle:"Printer is idle",printerOffline:"Printer offline",status:{printing:"Printing",paused:"Paused",finished:"Finished",failed:"Failed",idle:"Idle",unknown:"Unknown"}},profiles:{title:"Profiles",subtitle:"Manage your slicer presets and pressure advance calibrations",tabs:{cloud:"Cloud Profiles",local:"Local Profiles",kprofiles:"K-Profiles"},localProfiles:{title:"Local Profiles",subtitle:"Import and manage slicer presets from OrcaSlicer",import:"Import Profiles",importDesc:"Drop .bbscfg, .bbsflmt, .orca_filament, .zip, or .json files here",importing:"Importing...",search:"Search local presets...",noPresets:"No local presets yet",badge:"Local",edit:"Edit",delete:"Delete",cancel:"Cancel",deleteConfirmTitle:"Delete Preset",deleteConfirm:"Are you sure you want to delete this preset? This cannot be undone.",source:"Source",inheritsFrom:"Inherits",filamentType:"Type",vendor:"Vendor",compatiblePrinters:"Printers",nozzleTemp:"Nozzle Temp",cost:"Cost",density:"Density",pressureAdvance:"Pressure Advance",filament:"Filament",process:"Process",printer:"Printer",toast:{importSuccess:"{{count}} preset(s) imported",importSkipped:"{{count}} preset(s) skipped (duplicates)",importError:"{{count}} error(s) during import",deleted:"Preset deleted",updated:"Preset updated"}},connectedAs:"Connected as",logout:"Logout",noLogoutPermission:"You do not have permission to logout",failedToLoad:"Failed to load profiles",retry:"Retry",time:{justNow:"Just now",minsAgo:"{{count}}m ago",hoursAgo:"{{count}}h ago",daysAgo:"{{count}}d ago"},toast:{loggedOut:"Logged out"},login:{title:"Connect to Bambu Cloud",subtitle:"Sync your slicer presets across devices",email:"Email",password:"Password",region:"Region",regionGlobal:"Global",regionChina:"China",verificationCode:"Verification Code",totpCode:"Authenticator Code",checkEmail:"Check your email ({{email}}) for a 6-digit code",enterTotpHint:"Enter the 6-digit code from your authenticator app",accessToken:"Access Token",accessTokenHint:"Paste your Bambu Lab access token (from Bambu Studio)",back:"Back",loginButton:"Login",verifyButton:"Verify",setTokenButton:"Set Token",useToken:"Use access token instead",useEmail:"Login with email instead",toast:{loggedIn:"Logged in successfully",codeSent:"Verification code sent to your email",enterTotp:"Enter code from your authenticator app",tokenSet:"Token set successfully"}},presets:{myPreset:"My preset (editable)",duplicate:"Duplicate",editable:"Editable",failedToLoadDetails:"Failed to load preset details",deleteConfirm:"Delete this preset?",deleteWarning:'This will permanently delete "{{name}}" from Bambu Cloud. This cannot be undone.',noDuplicatePermission:"You do not have permission to duplicate presets",noEditPermission:"You do not have permission to edit presets",noDeletePermission:"You do not have permission to delete presets",types:{filament:"Filament preset",printer:"Printer preset",process:"Process preset"},toast:{deleted:"Preset deleted",created:"Preset created",updated:"Preset updated",duplicated:"Preset duplicated",fieldAdded:'Field "{{key}}" added',exported:"Preset exported"},baseLabel:"Base: {{name}}",currentLabel:"Current: {{name}}",newPreset:"New Preset",editPreset:"Edit Preset",duplicatePreset:"Duplicate Preset",createNewPreset:"Create New Preset",customizeSettings:"Customize settings for your new preset",compareWithBase:"Compare with base preset",compare:"Compare",basePreset:"Base Preset",selectBasePreset:"Select base preset...",presetName:"Preset Name",myCustomPreset:"My custom preset",inheritsFrom:"Inherits from",dropJsonToImport:"Drop JSON to import",tabs:{common:"Common",allFields:"All Fields"},availableFields:"Available Fields",searchFieldsPlaceholder:"Search fields...",noMatchingFields:"No matching fields",allFieldsAdded:"All fields added",addCustomField:"Add custom field",yourOverrides:"Your Overrides",noOverridesYet:"No overrides yet",clickFieldsToAdd:"Click fields on the left to add them",saveAsTemplate:"Save as template",jsonTip:"Tip: Drag & drop a .json file anywhere on this modal to import settings"},cloudView:{searchPlaceholder:"Search presets...",templates:"Templates",refresh:"Refresh",newPreset:"New Preset",clearFilters:"Clear filters",compareMode:"Compare Mode",selectAnotherPreset:"Select another {{type}} preset",clickTwoPresets:"Click two presets of the same type to compare",selectFirst:"1. Select first",selectSecond:"2. Select second",compareNow:"Compare Now",lastSynced:"Last synced:",showingCount:"Showing {{showing}} of {{total}} presets",noPresetsFound:"No presets found",columns:{filament:"Filament",process:"Process",printer:"Printer"},noFilamentPresets:"No filament presets",noProcessPresets:"No process presets",noPrinterPresets:"No printer presets",filters:{type:"Type",owner:"Owner",printer:"Printer",nozzle:"Nozzle",filament:"Filament",layer:"Layer",all:"All",myPresets:"My Presets",builtIn:"Built-in",process:"Process"},noTemplatesPermission:"You do not have permission to manage templates",noRefreshPermission:"You do not have permission to refresh profiles",noCreatePermission:"You do not have permission to create presets"},templates:{title:"Quick Templates",noTemplates:"No templates yet",createFirst:"Create templates from the preset editor",typeFilter:"Type:",deleteTitle:"Delete Template",deleteWarning:"This action cannot be undone",deleteConfirm:'Are you sure you want to delete "{{name}}"?',namePlaceholder:"Template name",descriptionPlaceholder:"Description",settingsJson:"Settings (JSON)",fieldsCount:"{{count}} fields",shownInModals:"Shown in modals",hiddenInModals:"Hidden in modals",apply:"Apply",toast:{deleted:"Template deleted",updated:"Template updated",created:"Template created",applied:"Template applied"}}},support:{debugLoggingActive:"Debug logging is active",manageLogs:"Manage",collectItem7:"Printer connectivity and firmware versions",collectItem8:"Integration status (Spoolman, MQTT, HA)",collectItem9:"Network interfaces (subnets only)",collectItem10:"Python package versions",collectItem11:"Database health checks",collectItem12:"Docker environment details"},fileManager:{title:"File Manager",subtitle:"Organize and manage your print files",uploadFiles:"Upload Files",newFolder:"New Folder",folderName:"Folder Name",folderNamePlaceholder:"e.g., Functional Parts",renameFile:"Rename File",renameFolder:"Rename Folder",moveFiles:"Move {{count}} File(s)",rootNoFolder:"Root (No Folder)",current:"current",linkFolder:"Link Folder",linkFolderDescription:'Link "{{name}}" to a project or archive for quick access.',project:"Project",archive:"Archive",noProjectsFound:"No projects found",noArchivesFound:"No archives found",unlink:"Unlink",link:"Link",dragDropFiles:"Drag & drop files here",dropFilesHere:"Drop files here",orClickToBrowse:"or click to browse",allFileTypesSupported:"All file types supported. ZIP files will be extracted.",zipFilesDetected:"ZIP files detected",zipExtractOptions:"ZIP files will be extracted. Choose how to handle folder structure:",preserveZipStructure:"Preserve folder structure from ZIP",createFolderFromZip:"Create folder from ZIP filename",stlThumbnailGeneration:"STL thumbnail generation",zipMayContainStl:"ZIP files may contain STL files. Thumbnails can be generated during extraction.",thumbnailsCanBeGenerated:"Thumbnails can be generated for STL files. Large models may take longer to process.",generateThumbnailsForStl:"Generate thumbnails for STL files",threemfDetected:"3MF files detected",threemfExtractionInfo:"Printer model, material, color, and print settings will be automatically extracted from 3MF files.",willBeExtracted:"Will be extracted",filesExtracted:"{{count}} files extracted",uploadComplete:"Upload complete: {{succeeded}} succeeded",uploadFailed:"Upload failed",zipFilesFailed:"{{count}} files failed",uploading:"Uploading...",changeLink:"Change Link...",linkTo:"Link to...",linkToProjectOrArchive:"Link to project or archive",addToQueue:"Add to Queue",schedulePrint:"Schedule",generateThumbnail:"Generate Thumbnail",generateThumbnails:"Generate Thumbnails",generateThumbnailsForMissing:"Generate thumbnails for STL files missing them",gridView:"Grid view",listView:"List view",lowDiskSpaceWarning:"Low disk space warning",lowDiskSpaceDetails:"Only {{free}} free of {{total}} total. Threshold is set to {{threshold}} GB in settings.",files:"Files",folders:"Folders",size:"Size",free:"Free",allFiles:"All Files",wrap:"Wrap",enableTextWrapping:"Enable text wrapping",disableTextWrapping:"Disable text wrapping",dragToResizeTooltip:"Drag to resize, double-click to reset",searchFiles:"Search files...",allTypes:"All types",prints:"Prints",ascending:"Ascending",descending:"Descending",resultsCount:"{{showing}} of {{total}} files",selectAll:"Select All",deselectAll:"Deselect All",selected:"{{count}} selected",adding:"Adding...",loadingFiles:"Loading files...",folderIsEmpty:"Folder is empty",noFilesYet:"No files yet",folderEmptyDescription:"Upload files or move files into this folder to get started.",noFilesDescription:"Upload files to start organizing your print-related files.",noMatchingFiles:"No matching files",noMatchingFilesDescription:"No files match your current search or filter criteria.",clearFilters:"Clear filters",printedCount:"Printed {{count}}x",uploadedBy:"Uploaded By",deleteFolder:"Delete Folder",deleteFile:"Delete File",deleteFilesCount:"Delete {{count}} Files",deleteFolderConfirm:"Are you sure you want to delete this folder? All files inside will also be deleted.",deleteFileConfirm:"Are you sure you want to delete this file?",deleteFilesConfirm:"Are you sure you want to delete {{count}} selected files? This action cannot be undone.",deleting:"Deleting...",noPermissionRenameFolder:"You do not have permission to rename folders",noPermissionLinkFolder:"You do not have permission to link folders",noPermissionDeleteFolder:"You do not have permission to delete folders",noPermissionPrint:"You do not have permission to print",noPermissionAddToQueue:"You do not have permission to add to queue",noPermissionDownload:"You do not have permission to download files",noPermissionRenameFile:"You do not have permission to rename this file",noPermissionGenerateThumbnail:"You do not have permission to generate thumbnails",noPermissionDeleteFile:"You do not have permission to delete this file",noPermissionCreateFolder:"You do not have permission to create folders",noPermissionUpload:"You do not have permission to upload files",noPermissionMoveFiles:"You do not have permission to move files",noPermissionDeleteFiles:"You do not have permission to delete files",toast:{folderCreated:"Folder created",folderDeleted:"Folder deleted",fileDeleted:"File deleted",filesDeleted:"Deleted {{count}} files",filesMoved:"Files moved",folderLinked:"Folder linked",folderUnlinked:"Folder unlinked",addedToQueue:"Added {{count}} file(s) to queue",addedToQueuePartial:"Added {{added}} file(s), {{failed}} failed",failedToAddToQueue:"Failed to add files: {{error}}",fileRenamed:"File renamed",folderRenamed:"Folder renamed",thumbnailsGenerated:"Generated {{count}} thumbnail(s)",thumbnailsGeneratedPartial:"Generated {{succeeded}} thumbnail(s), {{failed}} failed",noStlMissingThumbnails:"No STL files missing thumbnails",failedToGenerateThumbnails:"Failed to generate thumbnails: {{error}}",thumbnailGenerated:"Thumbnail generated",failedToGenerateThumbnail:"Failed to generate thumbnail: {{error}}"}},projects:{title:"Projects",subtitle:"Organize and track your 3D printing projects",newProject:"New Project",editProject:"Edit Project",deleteProject:"Delete Project",projectName:"Project Name",description:"Description",noProjects:"No projects yet",noProjectsFiltered:"No {{status}} projects",noProjectsFilteredHelp:"You don't have any {{status}} projects. Projects will appear here when their status changes.",createFirst:"Create your first project to start organizing related prints, tracking progress, and managing your builds.",createFirstButton:"Create Your First Project",create:"Create",files:"Files",prints:"Prints",plates:"plates",parts:"parts",lastModified:"Last Modified",deleteConfirm:"Are you sure you want to delete this project? Archives and queue items will be unlinked but not deleted.",addFiles:"Add Files",removeFile:"Remove File",viewDetails:"View Details",namePlaceholder:"e.g., Voron 2.4 Build",descriptionPlaceholder:"Optional description...",color:"Color",targetPlates:"Target Plates",targetPlatesPlaceholder:"e.g., 25",targetPlatesHelp:"Number of print jobs",targetParts:"Target Parts",targetPartsPlaceholder:"e.g., 150",targetPartsHelp:"Total objects needed",tagsLabel:"Tags (comma-separated)",tagsPlaceholder:"e.g., voron, functional, gift",dueDate:"Due Date",priority:"Priority",priorityLow:"Low",priorityNormal:"Normal",priorityHigh:"High",priorityUrgent:"Urgent",statusActive:"Active",statusCompleted:"Completed",statusArchived:"Archived",done:"Done",completed:"completed",failed:"failed",inQueue:"in queue",noPrintsYet:"No prints yet",printJobs:"Print jobs (plates)",partsPrinted:"Parts printed",failedParts:"Failed parts",import:"Import",export:"Export",importProject:"Import project",exportAll:"Export all projects",loading:"Loading projects...",noEditPermission:"You do not have permission to edit projects",noDeletePermission:"You do not have permission to delete projects",noCreatePermission:"You do not have permission to create projects",noImportPermission:"You do not have permission to import projects",noExportPermission:"You do not have permission to export projects",toast:{created:"Project created",updated:"Project updated",deleted:"Project deleted",imported:"Project imported",multipleImported:"{{count}} projects imported",importFailed:"Import failed",exported:"Projects exported (metadata only)"}},projectDetail:{notFound:"Project not found",backToProjects:"Back to Projects",export:"Export",exportProject:"Export project",noExportPermission:"You do not have permission to export projects",noEditPermission:"You do not have permission to edit projects",partOf:"Part of:",priorityLabel:"Priority:",noPrints:"No prints in this project yet",status:{active:"Active",completed:"Completed",archived:"Archived"},priority:{low:"Low",normal:"Normal",high:"High",urgent:"Urgent"},dueDate:{overdue:"Overdue",today:"Due today",daysLeft:"{{count}} days left"},progress:{platesProgress:"Plates Progress",partsProgress:"Parts Progress",printJobs:"print jobs",parts:"parts",percentComplete:"{{percent}}% complete",remaining:"{{count}} remaining"},stats:{printJobs:"Print Jobs",total:"total",failed:"{{count}} failed",partsPrinted:"{{count}} parts printed",printTime:"Print Time",filamentUsed:"Filament Used"},cost:{title:"Cost Tracking",filamentCost:"Filament Cost",energy:"Energy",totalCost:"Total Cost",total:"Total",includesBom:"incl. BOM",budget:"Budget",remaining:"Remaining"},subProjects:{title:"Sub-projects ({{count}})"},notes:{title:"Notes",noEditPermission:"You do not have permission to edit notes",placeholder:"Add notes about this project...",empty:"No notes yet. Click Edit to add notes."},files:{title:"Files",linkFolders:"Link folders from the File Manager",forQuickAccess:"to this project for quick access.",fileCount:"{{count}} file(s)",empty:"No folders linked. Go to File Manager and link a folder to this project."},bom:{title:"Bill of Materials",acquired:"{{completed}}/{{total}} acquired",showAll:"Show all",hideDone:"Hide done",addPart:"Add Part",noAddPermission:"You do not have permission to add parts",partNamePlaceholder:"Part name (e.g., M3x8 screws)",partName:"Part name",qty:"Qty",price:"Price ({{currency}})",sourcingUrlPlaceholder:"Sourcing URL (optional)",remarksPlaceholder:"Remarks (optional)",deletePart:"Delete Part",deleteConfirm:'Are you sure you want to delete "{{name}}"?',noUpdatePermission:"You do not have permission to update parts",noEditPermission:"You do not have permission to edit parts",noDeletePermission:"You do not have permission to delete parts",totalCost:"Total cost:",empty:"No parts in the bill of materials. Add hardware, electronics, or other components to track what needs to be sourced."},timeline:{title:"Activity Timeline",empty:"No activity yet."},template:{saveAsTemplate:"Save as Template",noCreatePermission:"You do not have permission to create templates"},queue:{title:"Queue",viewAll:"View all",printing:"{{count}} printing",queued:"{{count}} queued"},prints:{title:"Prints ({{count}})"},toast:{projectUpdated:"Project updated",partAdded:"Part added",partRemoved:"Part removed",exportFailed:"Export failed",projectExported:"Project exported",templateCreated:"Template created"}},system:{title:"System Information",version:"Version",uptime:"Uptime",cpuUsage:"CPU Usage",memoryUsage:"Memory Usage",diskUsage:"Disk Usage",networkInfo:"Network Info",logs:"Logs",debugMode:"Debug Mode",enableDebug:"Enable Debug Logging",disableDebug:"Disable Debug Logging",downloadLogs:"Download Logs",clearLogs:"Clear Logs",dockerInfo:"Docker Info",containerName:"Container Name",imageName:"Image Name",platform:"Platform",architecture:"Architecture"},library:{title:"Filament Library",addFilament:"Add Filament",editFilament:"Edit Filament",deleteFilament:"Delete Filament",vendor:"Vendor",material:"Material",color:"Color",kFactor:"K Factor",temperature:"Temperature",noFilaments:"No filaments in library",deleteConfirm:"Are you sure you want to delete this filament?",importFromPrinter:"Import from Printer",exportToFile:"Export to File"},spoolman:{title:"Spoolman Integration",enabled:"Spoolman Enabled",url:"Spoolman URL",connected:"Connected",disconnected:"Not Connected",testConnection:"Test Connection",sync:"Sync",syncing:"Syncing...",lastSync:"Last Sync",linkToSpoolman:"Link to Spoolman",openInSpoolman:"Open in Spoolman",unlinkSpool:"Unlink Spool",unlinkConfirmTitle:"Unlink Spool?",unlinkConfirmMessage:"This will disconnect the spool from Spoolman. The spool data in Spoolman will remain unchanged.",selectSpool:"Select Spool",noUnlinkedSpools:"No unlinked spools available",linkSuccess:"Spool linked to Spoolman successfully",linkFailed:"Failed to link spool",unlinkSuccess:"Spool unlinked from Spoolman successfully",unlinkFailed:"Failed to unlink spool",spoolId:"Spool ID",fillSourceLabel:"(Spoolman)",weight:"Weight",remaining:"Remaining",disableWeightSync:"Disable AMS Estimated Weight Sync",disableWeightSyncDesc:"Don't update remaining capacity from AMS estimates. Use this if you prefer Spoolman's usage tracking over AMS percentage-based estimates. New spools will still use the AMS estimate as their initial weight.",reportPartialUsage:"Report Partial Usage for Failed Prints",reportPartialUsageDesc:"When a print fails or is cancelled, report the estimated filament used up to that point based on layer progress."},inventory:{title:"Spool Inventory",addSpool:"Add Spool",editSpool:"Edit Spool",material:"Material",selectMaterial:"Select material...",subtype:"Subtype",brand:"Brand",searchBrand:"Search brand...",useCustomBrand:'Use "{{brand}}"',useCustomMaterial:"Use custom material: {{material}}",colorName:"Color Name",colorNamePlaceholder:"Jade White, Fire Red...",color:"Color",hexColor:"Hex Color",pickColor:"Pick custom color",labelWeight:"Label Weight",coreWeight:"Empty Spool Weight",searchSpoolWeight:"Search spool weight...",weightUsed:"Used",currentWeight:"Remaining Weight",measuredWeight:"Measured Weight",spoolName:"Spool",costPerKg:"Cost per kg",measuredWeightError:"Measured weight must be between {{min}}g and {{max}}g.",slicerFilament:"Slicer Filament",slicerFilamentName:"Slicer Preset Name",slicerPreset:"Slicer Preset",searchPresets:"Search filament presets...",selectedPreset:"Selected",noPresetsFound:"No presets found",tempOverrides:"Temperature Overrides",note:"Note",notePlaceholder:"Any additional notes about this spool...",archive:"Archive",restore:"Restore",noSpools:"No spools yet. Add your first spool to get started.",noManualSpools:"No manually added spools available. Add a spool to your inventory first.",kProfiles:"K-Profiles",addKProfile:"Add K-Profile",assignSpool:"Assign Spool",unassignSpool:"Unassign",assignSuccess:"Spool assigned and AMS slot configured",assignFailed:"Failed to assign spool",selectSpool:"Select a spool to assign to this slot",assigned:"Assigned",assigning:"Assigning...",searchSpools:"Search spools...",allMaterials:"All Materials",filterByBrand:"Filter by brand...",showArchived:"Show archived",quickAdd:"Quick Add (Stock)",quantity:"Quantity",stock:"Stock",configured:"Configured",spoolsCreated:"{{count}} spools created",spoolCreated:"Spool created",spoolUpdated:"Spool updated",spoolDeleted:"Spool deleted",spoolArchived:"Spool archived",spoolRestored:"Spool restored",deleteConfirm:"Are you sure you want to delete this spool? This cannot be undone.",archiveConfirm:"Are you sure you want to archive this spool?",advancedSettings:"Advanced Settings",filamentInfoTab:"Filament Info",paProfileTab:"PA Profile",filamentInfo:"Filament",additional:"Additional",loadingPresets:"Loading cloud presets...",cloudConnected:"Cloud connected",cloudNotConnected:"Cloud not connected (using defaults)",recentColors:"Recent",searchColors:"Search colors...",searchResults:"Search results",allColors:"All colors",commonColors:"Common colors",showLess:"Show less",showAll:"Show all",noColorsFound:"No colors match your search",noResults:"No matches found",selectMaterialFirst:"Please select a material first in the Filament Info tab.",noPrintersConfigured:"No printers configured. Add printers to use PA profiles.",matchingFilter:"Matching",anyBrand:"Any brand",anyVariant:"Any variant",autoSelect:"Auto-select",matches:"matches",match:"match",noMatches:"No matches",connected:"Connected",offline:"Offline",printerOffline:"Printer is offline. Connect to view calibration profiles.",noKProfilesMatch:"No K-profiles match the selected filament.",leftNozzle:"Left Nozzle",rightNozzle:"Right Nozzle",profilesSelected:"calibration profile(s) selected",totalInventory:"Total Inventory",totalConsumed:"Total Consumed",byMaterial:"By Material",inPrinter:"In Printer",lowStock:"Low Stock",sinceTracking:"Since tracking started",loadedInAms:"Loaded in AMS/Ext",remaining:"Remaining",weightCheck:"Weight Check",lastWeighed:"Last weighed",neverWeighed:"Never weighed",search:"Search spools...",showing:"Showing",to:"to",of:"of",show:"Show",spools:"spools",spool:"spool",page:"Page",noSpoolsMatch:"No results found",noSpoolsMatchDesc:"Try adjusting your search or filters to find what you're looking for.",active:"Active",archived:"Archived",all:"All",used:"Used",new:"New",clearFilters:"Clear filters",table:"Table",cards:"Cards",net:"Net",groupSimilar:"Group",groupedSpools:"{{count}} identical spools",groupedRows:"rows",columns:"Columns",configureColumns:"Configure Columns",configureColumnsDesc:"Drag to reorder columns or use arrows. Toggle visibility with the eye icon.",visible:"visible",reset:"Reset",cancel:"Cancel",applyChanges:"Apply Changes",moveUp:"Move up",moveDown:"Move down",hideColumn:"Hide column",showColumn:"Show column",linkToSpool:"Link to Spool",tagLinked:"Tag linked to spool",tagLinkFailed:"Failed to link tag",tagAlreadyLinked:"Tag already linked to another spool",unknownTag:"Unknown RFID tag detected",usageHistory:"Usage History",noUsageHistory:"No usage recorded yet",printName:"Print Name",weightConsumed:"Weight Consumed",clearHistory:"Clear",historyCleared:"Usage history cleared",fillSourceLabel:"(Inv)",lowStockThresholdError:"Threshold must be between 0.1 and 99.9",assignMismatchTitle:"Material mismatch",assignMismatchMessage:'The selected spool material "{{spoolMaterial}}" does not match the tray material "{{trayMaterial}}" for {{location}}. Assign anyway?',assignMismatchConfirm:"Assign Anyway",assignPartialMismatchMessage:'The spool material "{{spoolMaterial}}" is similar to but not exactly matching "{{trayMaterial}}" in {{location}}. Do you want to proceed?',assignProfileMismatchMessage:'The spool profile "{{spoolProfile}}" does not match the tray profile "{{trayProfile}}" in {{location}}. Do you want to proceed?'},timelapse:{title:"Timelapse",create:"Create Timelapse",download:"Download",delete:"Delete",preview:"Preview",frameRate:"Frame Rate",quality:"Quality",processing:"Processing...",noTimelapses:"No timelapses available"},ams:{title:"AMS",slot:"Slot",empty:"Empty",emptySlot:"Empty slot",unknown:"Unknown",humidity:"Humidity",temperature:"Temperature",filamentType:"Filament Type",filamentColor:"Color",remaining:"Remaining",history:"AMS History",noHistory:"No history available",configureSlot:"Configure Slot",externalSpool:"External Spool",profile:"Profile",kFactor:"K Factor",fill:"Fill",configure:"Configure",used:"used",remainingUnit:"remaining"},printModal:{title:"Start Print",selectPrinter:"Select Printer",selectPlate:"Select Plate",filamentMapping:"Filament Mapping",totalCost:"Total cost:",slotRemainingShort:" - {{grams}}g left",printSettings:"Print Settings",bedLeveling:"Bed Leveling",flowCalibration:"Flow Calibration",vibrationCalibration:"Vibration Calibration",layerInspection:"First Layer Inspection",timelapse:"Timelapse",startPrint:"Start Print",addToQueue:"Add to Queue",cancel:"Cancel",noPrintersAvailable:"No printers available",printerBusy:"Printer is busy",printerOffline:"Printer is offline",sameTypeDifferentColor:"Same type, different color",filamentTypeNotLoaded:"Filament type not loaded",openCalendar:"Open calendar",leftNozzle:"L",rightNozzle:"R",leftNozzleTooltip:"Left nozzle",rightNozzleTooltip:"Right nozzle",filamentOverride:"Filament Override",filamentOverrideHint:"Optionally override filaments for model-based assignment. The scheduler will match against your selected filaments instead of the original 3MF values.",originalFilament:"Original",overrideWith:"Override with",resetToOriginal:"Reset to original",insufficientFilamentTitle:"Not enough filament",insufficientFilamentMessage:"Some assigned spools have less filament remaining than this print needs:",insufficientFilamentLine:"{{printer}} - {{slot}}: needs {{required}}g, remaining {{remaining}}g",printAnyway:"Print anyway",forceColorMatch:"Force color match"},backup:{title:"Backup & Restore",createBackup:"Create Backup",restoreBackup:"Restore Backup",restoreDescription:"Replace all data from a backup file",downloadBackup:"Download Backup",uploadBackup:"Upload Backup",lastBackup:"Last Backup",autoBackup:"Auto Backup",backupNow:"Backup Now",restoreWarning:"Warning: Restoring a backup will overwrite all current data.",includeArchives:"Include Archives",includeSettings:"Include Settings",includeProfiles:"Include Profiles",backupSuccess:"Backup created successfully",restoreSuccess:"Backup restored successfully",backupFailed:"Backup failed",restoreFailed:"Restore failed",restoreNote:"Virtual Printer will be stopped during restore",githubBackup:"GitHub Backup",enabled:"Enabled",cloudLoginRequired:"Bambu Cloud login required. Sign in under Profiles → Cloud Profiles to enable GitHub backup.",cloudLoginRequiredShort:"Cloud login required",githubDescription:"Automatically sync your profiles to a private GitHub repository for backup and version history.",repositoryUrl:"Repository URL",personalAccessToken:"Personal Access Token",tokenSaved:"(saved)",enterNewToken:"Enter new token to update",tokenHint:"Fine-grained token with Contents read/write permission",branch:"Branch",manualOnly:"Manual only",hourly:"Hourly",daily:"Daily",weekly:"Weekly",includeInBackup:"Include in backup",kProfiles:"K-Profiles",kProfilesDescription:"Pressure advance calibration from connected printers",noPrintersConnected:"No printers connected",printersConnected:"{{connected}}/{{total}} connected",cloudProfiles:"Cloud Profiles",cloudProfilesDescription:"Filament, printer, and process presets from Bambu Cloud",appSettings:"App Settings",appSettingsDescription:"Bambuddy configuration (complete database)",lastBackupAt:"Last backup:",noBackupsYet:"No backups yet",next:"Next:",startingBackup:"Starting backup...",test:"Test",enableBackup:"Enable Backup",testConnection:"Test Connection",enterRepoUrl:"Enter repository URL",enterRepoAndToken:"Enter repository URL and access token",repoRequired:"Repository URL is required",tokenRequired:"Access token is required",githubBackupEnabled:"GitHub backup enabled",tokenUpdated:"Token updated",settingsSaved:"Settings saved",failedToSave:"Failed to save: {{message}}",backupCompleteFiles:"Backup complete - {{count}} files updated",backupSkippedNoChanges:"Backup skipped - no changes",backupFailed2:"Backup failed: {{message}}",clearedLogs:"Cleared {{count}} logs",failedToClearLogs:"Failed to clear logs: {{message}}",history:"History",clear:"Clear",date:"Date",status:"Status",commit:"Commit",localBackup:"Local Backup",localBackupDescription:"Create a complete backup of your Bambuddy data including the database, archives, uploads, and all files.",downloadBackupLabel:"Download Backup",completeBackupZip:"Complete backup: database + all files (ZIP)",download:"Download",preparingBackup:"Preparing backup...",creatingArchive:"Creating backup archive... This may take a while for large archives.",downloadingFile:"Downloading backup file...",backupDownloaded:"Backup downloaded successfully",failedToCreateBackup:"Failed to create backup: {{message}}",restore:"Restore",restoreReplacesAll:"Restore replaces all data.",restoreReplacesAllDetail:"Your current database and files will be completely replaced. A restart is required after restore.",restoreConfirmTitle:"Restore Backup",restoreConfirmMessage:'Are you sure you want to restore from "{{filename}}"? This will completely replace your current database and all files. The application will need to be restarted after restore.',restoreConfirmButton:"Restore Backup",uploadingFile:"Uploading backup file...",backupRestoredRestart:"Backup restored. Please restart Bambuddy.",failedToRestore:"Failed to restore backup. Please check the file format.",reloadNow:"Reload Now",creatingBackup:"Creating Backup",restoringBackup:"Restoring Backup",preparing:"Preparing...",processing:"Processing...",doNotClosePage:"Please do not close this page or navigate away. This operation may take several minutes for large backups.",restoring:"Restoring...",restoreComplete:"Restore Complete",restoreFailed2:"Restore Failed",importSettings:"Import settings from a backup file",pleaseWaitRestoring:"Please wait while your data is being restored",selectBackupFile:"Click to select backup file (.json or .zip)",duplicateHandling:"How duplicate handling works:",matchPrinters:"Printers",matchPrintersBy:"matched by serial number",matchSmartPlugs:"Smart Plugs",matchSmartPlugsBy:"matched by IP address",matchNotificationProviders:"Notification Providers",matchNotificationProvidersBy:"matched by name",matchFilaments:"Filaments",matchFilamentsBy:"matched by name + type + brand",matchArchives:"Archives",matchArchivesBy:"matched by content hash (always skipped)",matchPendingUploads:"Pending Uploads",matchPendingUploadsBy:"matched by filename",matchSettingsTemplates:"Settings & Templates",matchSettingsTemplatesBy:"always overwritten",replaceExisting:"Replace existing data",keepExisting:"Keep existing data",overwriteDescription:"Overwrite items that already exist with backup data",keepDescription:"Only restore items that don't already exist",overwriteCaution:"Caution:",overwriteWarning:"Overwriting will replace your current configurations with data from the backup. Printer access codes are never overwritten for security.",cancel:"Cancel",processingBackup:"Processing backup file...",itemsRestored:"Items Restored",itemsSkipped:"Items Skipped",restored:"Restored",skippedAlreadyExist:"Skipped (already exist)",filesCategory:"Files (3MF, thumbnails, etc.)",andMore:"...and {{count}} more",newApiKeysGenerated:"New API Keys Generated",keysShownOnce:"These keys are only shown once. Copy them now!",copy:"Copy",noDataFound:"No data was found to restore in the backup file.",close:"Close",categories:{settings:"Settings",notification_providers:"Notification Providers",notification_templates:"Notification Templates",smart_plugs:"Smart Plugs",printers:"Printers",filaments:"Filaments",maintenance_types:"Maintenance Types",archives:"Archives",projects:"Projects",pending_uploads:"Pending Uploads",external_links:"External Links",api_keys:"API Keys"}},tags:{title:"Tags",addTag:"Add Tag",editTag:"Edit Tag",deleteTag:"Delete Tag",tagName:"Tag Name",tagColor:"Tag Color",noTags:"No tags",deleteConfirm:"Are you sure you want to delete this tag?",manageTags:"Manage Tags"},uploadModal:{title:"Upload 3MF Files",dragDrop:"Drag & drop .3mf files here",or:"or",browseFiles:"Browse Files",extractionInfo:"The printer model will be automatically extracted from the 3MF file metadata.",uploaded:"uploaded",failed:"failed",uploading:"Uploading...",upload:"Upload",uploadFailed:"Upload failed"},editArchive:{title:"Edit Archive",name:"Name",namePlaceholder:"Print name",printer:"Printer",noPrinter:"No printer",project:"Project",noProject:"No project",itemsPrinted:"Items Printed",itemsPrintedHelp:"Number of items produced in this print job",notes:"Notes",notesPlaceholder:"Add notes about this print...",externalLink:"External Link",externalLinkPlaceholder:"https://printables.com/model/...",externalLinkHelp:"Link to Printables, Thingiverse, or other source",tags:"Tags",tagsPlaceholder:"Add tags...",addMoreTags:"Add more tags...",matchingTags:'Matching "{{query}}"',existingTags:"Existing tags",clickToAdd:"(click to add)",status:"Status",failureReason:"Failure Reason",selectReason:"Select reason...",photos:"Photos of Printed Result",photosHelp:"Click + to add photos of your printed result",printResult:"Print result",saving:"Saving...",failureReasons:{adhesionFailure:"Adhesion failure",spaghettiDetached:"Spaghetti / Detached",layerShift:"Layer shift",cloggedNozzle:"Clogged nozzle",filamentRunout:"Filament runout",warping:"Warping",stringing:"Stringing",underExtrusion:"Under-extrusion",powerFailure:"Power failure",userCancelled:"User cancelled",other:"Other"},statuses:{completed:"Completed",failed:"Failed",aborted:"Cancelled",printing:"Printing"}},kProfiles:{title:"K-Profiles",noPrintersConfigured:"No Printers Configured",addPrinterInSettings:"Add a printer in Settings to manage K-profiles",noActivePrinters:"No Active Printers",enablePrinterConnection:"Enable a printer connection to view its K-profiles",loadingProfiles:"Loading K-Profiles...",printerOffline:"Printer Offline",printerOfflineDesc:"The selected printer is not connected. Power it on to view K-profiles.",noMatchingProfiles:"No Matching Profiles",noMatchingProfilesDesc:"No profiles match your search criteria",noKProfiles:"No K-Profiles",noKProfilesDesc:"No pressure advance profiles found for {{diameter}}mm nozzle",createFirstProfile:"Create First Profile",printer:"Printer",nozzle:"Nozzle",refresh:"Refresh",addProfile:"Add Profile",export:"Export",import:"Import",select:"Select",selectAll:"Select All",delete:"Delete",searchPlaceholder:"Search by name or filament...",allExtruders:"All Extruders",leftOnly:"Left Only",rightOnly:"Right Only",allFlow:"All Flow",hfOnly:"HF Only",sOnly:"S Only",sortName:"Sort: Name",sortKValue:"Sort: K-Value",sortFilament:"Sort: Filament",leftExtruder:"Left Extruder",rightExtruder:"Right Extruder",modal:{addTitle:"Add K-Profile",editTitle:"Edit K-Profile",profileName:"Profile Name",profileNamePlaceholder:"My PLA Profile",kValue:"K-Value",kValuePlaceholder:"0.020",kValueHelp:"Typical range: 0.01 - 0.06 for PLA, 0.02 - 0.10 for PETG",filament:"Filament",selectFilament:"Select filament...",noFilamentsHelp:"No filaments found. Create a K-profile in Bambu Studio first.",flowType:"Flow Type",highFlow:"High Flow",standard:"Standard",nozzleSize:"Nozzle Size",extruder:"Extruder",extruders:"Extruders",left:"Left",right:"Right",notes:"Notes (stored locally)",notesPlaceholder:"Add notes about this profile...",notesHelp:"Notes are saved in Bambuddy, not on the printer",syncing:"Syncing with printer...",savingExtruder:"Saving to extruder {{current}}/{{total}}...",pleaseWait:"Please wait"},deleteConfirm:{title:"Delete Profile",cannotUndo:"This cannot be undone",message:'Are you sure you want to delete "{{name}}" from the printer?'},bulkDelete:{title:"Delete Profiles",cannotUndo:"This cannot be undone",message:"Are you sure you want to delete {{count}} selected profiles from the printer?"},toast:{profileSaved:"K-profile saved",profilesSaved:"K-profile saved to {{count}} extruders",selectAtLeastOneExtruder:"Please select at least one extruder",profileDeleted:"K-profile deleted",profilesDeleted:"Deleted {{count}} profiles",exportedProfiles:"Exported {{count}} profiles",importedProfiles:"Imported {{count}} of {{total}} profiles",noProfilesToExport:"No profiles to export",invalidFileFormat:"Invalid file format",failedToParseImport:"Failed to parse import file",failedToSaveBatch:"Failed to save K-profiles",noteSaved:"Note saved",failedToSaveNote:"Failed to save note"},permission:{noRead:"You do not have permission to refresh profiles",noCreate:"You do not have permission to add profiles",noUpdate:"You do not have permission to update K-profiles",noDelete:"You do not have permission to delete K-profiles",noExport:"You do not have permission to export profiles",noImport:"You do not have permission to import profiles"}},virtualPrinter:{title:"Virtual Printer",running:"Running",stopped:"Stopped",description:{default:"Enable a virtual printer that appears in Bambu Studio and OrcaSlicer. Files sent to this printer will be archived directly without printing.",proxy:"Enable a proxy that relays slicer traffic to a real printer, allowing remote printing over any network."},enable:{title:"Enable Virtual Printer",visibleInSlicer:'Visible as "Bambuddy" in slicer discovery',proxyingTo:"Proxying to {{name}}",notActive:"Not active"},model:{title:"Printer Model",description:"Select which printer model to emulate.",restartWarning:"Changing the model will restart the virtual printer"},accessCode:{title:"Access Code",isSet:"Access code is set",notSet:"No access code set - required to enable",placeholder:"Enter 8-char code",placeholderChange:"Enter new code to change",hint:"Must be exactly 8 characters. Used by slicers to authenticate.",charCount:"({{count}}/8)"},targetPrinter:{title:"Target Printer",configured:"Proxy target configured",notConfigured:"No target printer selected - required for proxy mode",placeholder:"Select a printer...",hint:"Select the printer to proxy slicer traffic to. The printer must be in LAN mode.",noPrinters:"No printers configured. Add a printer first to use proxy mode."},remoteInterface:{title:"Network Interface Override",configured:"Interface override active",optional:"Optional - use if auto-detected IP is wrong (e.g. multiple NICs, Docker, VPN)",placeholder:"Auto-detect (default)...",hint:"Override the IP address advertised via SSDP and used in the TLS certificate. Useful when Bambuddy has multiple network interfaces."},mode:{title:"Mode",archive:"Archive",archiveDesc:"Archive files immediately",review:"Review",reviewDesc:"Review before archiving",queue:"Queue",queueDesc:"Archive and add to queue",proxy:"Proxy",proxyDesc:"Relay to real printer"},autoDispatch:{title:"Auto-dispatch",description:"Automatically start prints when added to queue. When off, prints wait for manual dispatch."},setupRequired:{title:"Setup Required",description:"The virtual printer feature requires additional system configuration before it will work. This includes port forwarding, firewall rules, and platform-specific settings.",readGuide:"Read the setup guide before enabling"},howItWorks:{title:"How it works",step1:"On the same LAN, virtual printers appear in your slicer (Bambu Studio / OrcaSlicer) automatically via discovery. From other networks, add them manually by IP address and access code.",step2:'In Archive, Review, and Queue modes, use the "Send" button in your slicer to upload 3MF files to Bambuddy. The slicer will show "Print success" — the file is stored, not printed.',step3:"In Proxy mode, the virtual printer relays all traffic to a real printer — prints start immediately as if connected directly."},status:{title:"Status Details",printerName:"Printer Name",model:"Model",serialNumber:"Serial Number",mode:"Mode",pendingFiles:"Pending Files",targetPrinter:"Target Printer",ftpPort:"FTP Port",mqttPort:"MQTT Port",ftpConnections:"FTP Connections",mqttConnections:"MQTT Connections"},toast:{updated:"Virtual printer settings updated",failedToUpdate:"Failed to update settings",accessCodeRequired:"Please set an access code first",targetPrinterRequired:"Please select a target printer first",bindIpRequired:"Please set a bind IP first",accessCodeEmpty:"Access code cannot be empty",accessCodeLength:"Access code must be exactly 8 characters",created:"Virtual printer created",failedToCreate:"Failed to create virtual printer",deleted:"Virtual printer deleted",failedToDelete:"Failed to delete virtual printer"},list:{title:"Virtual Printers",add:"Add",addFirst:"Add Virtual Printer",empty:"No virtual printers configured. Add one to get started."},bindIp:{title:"Bind Interface",placeholder:"Select interface...",hint:"Network interface for this virtual printer to bind to. Must be unique per printer."},proxy:{accessCodeHint:"In proxy mode, use your target printer's access code in the slicer. The connection is forwarded transparently to the real printer."},addDialog:{title:"Add Virtual Printer",name:"Name",hint:"You can configure access code, target printer, and other settings after creating.",create:"Create"},deleteConfirm:{title:"Delete Virtual Printer",message:'Are you sure you want to delete "{{name}}"? This will stop all services for this printer.'}},modelViewer:{openInSlicer:"Open in Slicer",tabs:{model:"3D Model",gcode:"G-code Preview"},notAvailable:"not available",notSliced:"not sliced",plates:"Plates",allPlates:"All Plates",plateNumber:"Plate {{number}}",plateCount:"{{count}} plate",plateCount_other:"{{count}} plates",objectCount:"{{count}} object",objectCount_other:"{{count}} objects",filamentCount:"{{count}} filament",filamentCount_other:"{{count}} filaments",eta:"ETA {{minutes}} min",noPreview:"No preview available for this file",pagination:{pageOf:"Page {{current}} of {{total}}",prev:"Prev",next:"Next"},errors:{failedToLoad:"Failed to load file",noMeshes:"No meshes found in 3MF file",unsupportedFormat:"Unsupported file format"}},maintenanceDescriptions:{lubricateCarbonRods:"Apply lubricant to carbon rods for smooth motion",lubricateRails:"Apply lubricant to linear rails for smooth motion",cleanNozzle:"Clean hotend and nozzle to prevent clogs",checkBelts:"Verify belt tension for accurate prints",cleanBuildPlate:"Clean build plate for better adhesion",checkExtruder:"Inspect extruder gears for wear",checkCooling:"Ensure cooling fans are working properly",generalInspection:"General printer inspection",cleanCarbonRods:"Clean carbon rods to reduce friction",lubricateSteelRods:"Apply lubricant to steel rods for smooth motion",cleanSteelRods:"Clean steel rods to reduce friction",cleanLinearRails:"Wipe linear rails to remove dust and debris",checkPtfeTube:"Inspect PTFE tube for wear or damage",replaceHepaFilter:"Replace HEPA filter for air quality",replaceCarbonFilter:"Replace activated carbon filter",lubricateLeftNozzleRail:"Lubricate left nozzle rail (H2 series)"},smartPlugs:{offline:"Offline",admin:"Admin",openPlugAdminPage:"Open plug admin page",deleteSmartPlug:"Delete Smart Plug",turnOnSmartPlug:"Turn On Smart Plug",turnOffSmartPlug:"Turn Off Smart Plug",turnOn:"Turn On",turnOff:"Turn Off",addSmartPlug:{scanningNetwork:"Scanning network...",chooseEntity:"Choose an entity...",connectionFailed:"Connection failed",searchEntities:"Search entities...",searchPowerSensors:"Search power sensors...",searchEnergySensors:"Search energy sensors...",placeholders:{plugName:"Living Room Plug",mqttStateOnValue:"ON, true, 1",mqttSameAsPower:"Same as power topic, or different"}},linkedTo:"Linked to:",monitorOnly:"Monitor Only",alerts:"Alerts",scheduleOn:"On {{time}}",scheduleOff:"Off {{time}}",on:"On",off:"Off",power:"Power",kwhToday:"kWh Today",settings:"Settings",automationSettings:"Automation Settings",showInSwitchbar:"Show in Switchbar",quickAccessSidebar:"Quick access from sidebar",enabled:"Enabled",enableAutomation:"Enable automation for this plug",autoOn:"Auto On",autoOnDescription:"Turn on when print starts",autoOff:"Auto Off",autoOffDescription:"Turn off when print completes (one-shot)",turnOffDelayMode:"Turn Off Delay Mode",time:"Time",temp:"Temp",delayMinutes:"Delay (minutes)",tempThreshold:"Temperature threshold (°C)",tempThresholdDescription:"Turns off when nozzle cools below this temperature",edit:"Edit",deleteConfirm:'Are you sure you want to delete "{{name}}"? This cannot be undone.',turnOnConfirm:'Are you sure you want to turn on "{{name}}"?',turnOffConfirm:'Are you sure you want to turn off "{{name}}"? This will cut power to the connected device.',failedToTurn:'Failed to turn {{action}} "{{name}}"',unknown:"Unknown",addTitle:"Add Smart Plug",editTitle:"Edit Smart Plug",stopScanning:"Stop Scanning",discoverTasmota:"Discover Tasmota Devices",foundDevices:"Found {{count}} device(s) - click to select:",noDevicesFound:"No Tasmota devices found on your network",haNotConfigured:"Home Assistant is not configured. Set it up in",haSettingsPath:"Settings → Network → Home Assistant",selectEntity:"Select Entity *",ipAddress:"IP Address *",nameLabel:"Name *",username:"Username",password:"Password",authHint:"Leave empty if your Tasmota device doesn't require authentication",linkToPrinter:"Link to Printer",noPrinter:"No printer (manual control only)",linkingDescription:"Linking enables automatic on/off when prints start/complete",powerAlerts:"Power Alerts",alertAbove:"Alert if above (W)",alertBelow:"Alert if below (W)",alertDescription:"Get notified when power consumption crosses these thresholds. Leave empty to disable that direction.",dailySchedule:"Daily Schedule",turnOnAt:"Turn On at",turnOffAt:"Turn Off at",scheduleDescription:"Automatically turn the plug on/off at these times daily. Leave empty to skip that action.",showOnPrinterCard:"Show on Printer Card",displayOnPrinterCard:"Display button on printer card",connectedResult:"Connected!",deviceLabel:"Device: {{name}} - ",stateLabel:"State: {{state}}",test:"Test",delete:"Delete",save:"Save",add:"Add",cancel:"Cancel",failedToStartScan:"Failed to start scan",nameRequired:"Name is required",entityRequired:"Entity is required for Home Assistant plugs",mqttTopicRequired:"At least one MQTT topic must be configured for power, energy, or state monitoring",loadingEntities:"Loading entities...",loading:"Loading...",failedToLoadEntities:"Failed to load entities: {{error}}",noEntitiesMatching:'No entities found matching "{{search}}"',noEntitiesAvailable:"No entities available",searchingEntities:"Searching all entities ({{count}} found)",showingEntities:"Showing switch, light, input_boolean ({{count}} available)",energyMonitoringOptional:"Energy Monitoring (Optional)",energyMonitoringHint:"Search and select sensors that provide power/energy data.",powerSensorW:"Power Sensor (W)",energyTodayKwh:"Energy Today (kWh)",totalEnergyKwh:"Total Energy (kWh)",noMatchingSensors:"No matching sensors",none:"None",mqttNotConfigured:"MQTT broker not configured. Set broker address in",mqttSettingsPath:"Settings → Network → MQTT Publishing",mqttNotConfiguredSuffix:"(you don't need to enable publishing, just fill in the broker details).",mqttMonitorOnlyDescription:"MQTT plugs receive power/energy data via MQTT subscription. On/off control is not available - use your MQTT broker or home automation system.",powerMonitoring:"Power Monitoring",energyMonitoring:"Energy Monitoring",stateMonitoring:"State Monitoring",optional:"optional",topic:"Topic",jsonPath:"JSON Path",multiplier:"Multiplier",onValue:"ON Value",mqttPowerHint:`JSON path extracts value from JSON payload (e.g., "power_l1"). Leave empty if topic publishes raw numeric values. Use multiplier 0.001 for mW→W, 1000 for kW→W.`,mqttEnergyHint:`JSON path extracts value from JSON payload. Leave empty for raw values. Use multiplier 0.001 for Wh→kWh, 1000 for MWh→kWh.`,mqttStateHint:`JSON path extracts value from JSON payload. Leave empty for raw values. @@ -29,7 +29,7 @@ Valor ON: a string exata que significa "ON". Deixe vazio para detecção automá 乘数:Wh→kWh使用0.001,MWh→kWh使用1000。`,mqttStateHint:`JSON路径从JSON负载中提取值。原始值请留空。 ON值:表示"ON"的确切字符串。留空以自动检测(ON、true、1)。`,noSwitchesInSwitchbar:"开关栏中没有开关",enableSwitchbarHint:'在设置 > 智能插座中启用"在开关栏显示"'},notifications:{providerTypes:{callmebot:"CallMeBot/WhatsApp",ntfy:"ntfy",pushover:"Pushover",telegram:"Telegram",email:"电子邮件",discord:"Discord",webhook:"Webhook",homeassistant:"Home Assistant"},providerDescriptions:{email:"SMTP 电子邮件通知",telegram:"通过 Telegram 机器人发送通知",discord:"通过 Webhook 发送到 Discord 频道",ntfy:"免费、可自托管的推送通知",pushover:"简单、可靠的推送通知",callmebot:"通过 CallMeBot 免费发送 WhatsApp 通知",webhook:"通用 HTTP POST 到任意 URL",homeassistant:"Home Assistant 仪表板中的持久通知"},lastSuccess:"上次:{{date}}",error:"错误",printer:"打印机:",allPrinters:"所有打印机",sendTestNotification:"发送测试通知",eventSettings:"事件设置",enabled:"已启用",sendFromProvider:"从此提供商发送通知",printEvents:"打印事件",printerStatus:"打印机状态",amsAlarms:"AMS 警报",amsHtAlarms:"AMS-HT 警报",printQueue:"打印队列",start:"开始",plateCheck:"热床检测",complete:"完成",failed:"失败",stopped:"已停止",progress:"进度",offline:"离线",lowFilament:"耗材不足",maintenance:"维护",amsHumidity:"AMS 湿度",amsTemp:"AMS 温度",amsHtHumidity:"AMS-HT 湿度",amsHtTemp:"AMS-HT 温度",bedCooled:"热床已冷却",firstLayer:"首层完成",quiet:"免打扰",digest:"摘要 {{time}}",printStarted:"打印已开始",plateNotEmpty:"热床非空",plateNotEmptyDescription:"打印前检测到物体",printCompleted:"打印已完成",bedCooledLabel:"热床已冷却",bedCooledDescription:"打印后热床温度降至阈值以下",firstLayerCompleteLabel:"首层打印完成",firstLayerCompleteDescription:"首层完成时发送带照片的通知",printFailed:"打印失败",printStopped:"打印已停止",progressMilestones:"进度里程碑",progressMilestonesDescription:"在 25%、50%、75% 时通知",printerOffline:"打印机离线",printerError:"打印机错误",lowFilamentLabel:"耗材不足",maintenanceDue:"需要维护",maintenanceDueDescription:"需要维护时通知",amsHumidityHigh:"AMS 湿度过高",amsHumidityHighDescription:"普通 AMS 湿度超过阈值",amsTemperatureHigh:"AMS 温度过高",amsTemperatureHighDescription:"普通 AMS 温度超过阈值",amsHtHumidityHigh:"AMS-HT 湿度过高",amsHtHumidityHighDescription:"AMS-HT 湿度超过阈值",amsHtTemperatureHigh:"AMS-HT 温度过高",amsHtTemperatureHighDescription:"AMS-HT 温度超过阈值",jobAdded:"任务已添加",jobAddedDescription:"任务已添加到队列",jobAssigned:"任务已分配",jobAssignedDescription:"基于模型的任务已分配给打印机",jobStarted:"任务已开始",jobStartedDescription:"队列任务已开始打印",jobWaiting:"任务等待中",jobWaitingDescription:"任务正在等待耗材或打印机",jobSkipped:"任务已跳过",jobSkippedDescription:"任务已跳过(上一个失败)",jobFailed:"任务失败",jobFailedDescription:"任务启动失败",queueComplete:"队列已完成",queueCompleteDescription:"所有队列任务已完成",quietHours:"免打扰时段",noNotificationsDuring:"在此时段内不发送通知",editProviderToChangeQuietHours:"编辑提供商以更改免打扰时段",dailyDigest:"每日摘要",batchNotifications:"将通知汇总为每日摘要",sendAt:"发送于 {{time}}",editProviderToChangeDigestTime:"编辑提供商以更改摘要时间",edit:"编辑",deleteProvider:"删除通知提供商",deleteConfirm:'确定要删除"{{name}}"吗?此操作无法撤销。',delete:"删除",addTitle:"添加通知提供商",editTitle:"编辑通知提供商",nameLabel:"名称 *",namePlaceholder:"我的通知",providerTypeLabel:"提供商类型 *",configuration:"配置",testConfiguration:"测试配置",printerFilter:"打印机筛选",onlyFromPrinter:"仅发送来自此打印机的事件通知",quietHoursDnd:"免打扰时段",quietStart:"开始",quietEnd:"结束",dailyDigestLabel:"每日摘要",sendDigestAt:"发送摘要于",digestCollected:"事件将被收集并在此时间作为单条摘要发送",notificationEvents:"通知事件",progressPercent:"(25%、50%、75%)",bedCooledAfterPrint:"(打印完成后)",cancel:"取消",save:"保存",add:"添加",nameRequired:"名称为必填项",fieldRequired:"{{field}}为必填项",phoneNumber:"电话号码",apiKey:"API 密钥",serverUrl:"服务器 URL",topic:"主题",authToken:"认证令牌",userKey:"用户密钥",appToken:"应用令牌",priority:"优先级",botToken:"机器人令牌",chatId:"聊天 ID",smtpServer:"SMTP 服务器",smtpPort:"SMTP 端口",security:"安全",authentication:"认证",username:"用户名",password:"密码",fromEmail:"发件人邮箱",toEmail:"收件人邮箱",webhookUrl:"Webhook URL",payloadFormat:"负载格式",authorization:"授权",titleFieldName:"标题字段名",messageFieldName:"消息字段名",editTemplate:"编辑模板:{{name}}",titleLabel:"标题",bodyLabel:"正文",titlePlaceholder:"通知标题...",bodyPlaceholder:"通知正文...",availableVariables:"可用变量",clickToInsert:"点击插入到正文光标位置",livePreview:"实时预览",hide:"隐藏",show:"显示",loadingPreview:"加载预览中...",enterTemplateContent:"输入模板内容以查看预览",titlePreview:"标题:",bodyPreview:"正文:",resetToDefault:"恢复默认",titleRequired:"标题为必填项",bodyRequired:"正文为必填项",notificationLog:"通知日志",showFailedOnly:"仅显示失败",last24Hours:"最近 24 小时",last7Days:"最近 7 天",last30Days:"最近 30 天",last90Days:"最近 90 天",justNow:"刚刚",noFailedNotifications:"没有失败的通知",noNotificationsLogged:"没有通知记录",unknownProvider:"未知提供商",logTitle:"标题",logMessage:"消息",logError:"错误",logProvider:"提供商:{{type}}",logTime:"时间:{{time}}",refresh:"刷新",clearOld:"清除旧记录",statsSummary:"最近 {{days}} 天:",statsNotifications:"条通知",statsSent:"{{count}} 条已发送",statsFailed:"{{count}} 条失败",eventTypes:{print_start:"打印已开始",print_complete:"打印完成",print_failed:"打印失败",print_stopped:"打印已停止",print_progress:"进度",printer_offline:"打印机离线",printer_error:"打印机错误",filament_low:"耗材不足",maintenance_due:"需要维护",test:"测试"},userEmail:{title:"通知",emailNotifications:"邮件通知",emailNotificationsDesc:"接收您自己打印任务的邮件通知。邮件将通过高级身份验证中配置的 SMTP 设置发送。",sendingTo:"通知将发送至",noEmailWarning:"您的账户没有邮件地址。请联系管理员添加。",printJobNotifications:"打印任务通知",printJobNotificationsDesc:"选择哪些事件会触发您提交的打印任务的邮件通知。",printJobStarts:"打印任务开始",printJobStartsDesc:"当您的打印任务开始时收到通知。",printJobFinishes:"打印任务完成",printJobFinishesDesc:"当您的打印任务成功完成时收到通知。",printErrors:"打印错误",printErrorsDesc:"当您的打印任务失败或遇到错误时收到通知。",printJobStops:"打印任务停止",printJobStopsDesc:"当您的打印任务被取消或停止时收到通知。",saveSuccess:"通知偏好设置已保存。",saveError:"保存通知偏好设置失败。"}},richTextEditor:{bold:"粗体",italic:"斜体",underline:"下划线",bulletList:"无序列表",numberedList:"有序列表",alignLeft:"左对齐",alignCenter:"居中对齐",alignRight:"右对齐",addLink:"添加链接",removeLink:"移除链接"},externalLinks:{noLinksConfigured:"未配置外部链接",deleteLink:"删除链接",removeCustomIcon:"移除自定义图标",openInNewTab:"在新标签页中打开",placeholders:{linkName:"我的链接"}},keyboardShortcuts:{title:"键盘快捷键",navigation:"导航",archivesSection:"归档",kProfilesSection:"K 值配置",generalSection:"通用",shortcuts:{goToPrinters:"前往打印机",goToArchives:"前往归档",goToQueue:"前往队列",goToStats:"前往统计",goToProfiles:"前往云端配置",goToSettings:"前往设置",focusSearch:"聚焦搜索",openUploadModal:"打开上传对话框",clearSelection:"清除选择 / 取消焦点",contextMenu:"卡片右键菜单",refreshProfiles:"刷新配置",newProfile:"新建配置",exitSelectionMode:"退出选择模式",showHelp:"显示此帮助"},footer:"按 Esc 或点击外部关闭"},notificationLog:{title:"通知日志",events:{printStarted:"打印开始",printComplete:"打印完成",printFailed:"打印失败",printStopped:"打印停止",progress:"进度",printerOffline:"打印机离线",printerError:"打印机错误",lowFilament:"耗材不足",maintenanceDue:"维护到期",test:"测试"},timeAgo:{justNow:"刚刚",minutesAgo:"{{minutes}} 分钟前",hoursAgo:"{{hours}} 小时前"}},restoreBackup:{title:"恢复备份",restoring:"恢复中...",restoreComplete:"恢复完成",restoreFailed:"恢复失败",importSettings:"从备份文件导入设置",pleaseWait:"请稍候,正在恢复您的数据",clickToSelect:"点击选择备份文件(.json 或 .zip)",howDuplicateHandling:"重复处理方式:",categories:{printers:"打印机",smartPlugs:"智能插座",notificationProviders:"通知提供商",filaments:"耗材",archives:"归档",pendingUploads:"待处理上传",settingsTemplates:"设置和模板"},matchingInfo:{printers:"按序列号匹配",smartPlugs:"按 IP 地址匹配",notificationProviders:"按名称匹配",filaments:"按名称 + 类型 + 品牌匹配",archives:"按内容哈希匹配",pendingUploads:"按文件名匹配",settingsTemplates:"始终覆盖"},replaceExisting:"替换现有数据",keepExisting:"保留现有数据",replaceDescription:"用备份数据覆盖已存在的项目",keepDescription:"仅恢复不存在的项目",caution:"注意:",cautionText:"覆盖将用备份数据替换您当前的配置。出于安全考虑,打印机访问码永远不会被覆盖。",itemsRestored:"已恢复项目",itemsSkipped:"已跳过项目",restored:"已恢复",skipped:"已跳过(已存在)",filesLabel:"文件(3MF、缩略图等)",newApiKeysGenerated:"已生成新 API 密钥",newApiKeysWarning:"这些密钥仅显示一次。请立即复制!",processingBackup:"处理备份文件中...",noDataFound:"备份文件中未找到可恢复的数据。",failedToRestore:"恢复备份失败。请检查文件格式。"},backupExport:{title:"导出备份",selectData:"选择要包含的数据",selectAll:"全选",selectNone:"全不选",categoryDescriptions:{settings:"语言、主题、更新偏好",notifications:"ntfy、Pushover、Discord 等",templates:"自定义消息模板",smartPlugs:"Tasmota 插座配置",externalLinks:"侧边栏外部服务链接",printers:"打印机信息(不含访问码)",plateDetection:"空打印板参考图像",filaments:"耗材类型和成本",maintenance:"自定义维护计划",archives:"所有打印数据 + 文件(3MF、缩略图、照片)",projects:"项目、材料清单和附件",pendingUploads:"虚拟打印机待审核的上传",apiKeys:"Webhook API 密钥(导入时生成新密钥)"},requiresPrinters:"需要选择打印机",zipFileWarning:"将创建 ZIP 文件。",zipFileDescription:"包括所有 3MF 文件、缩略图、延时摄影和照片。这可能需要一些时间并生成较大的文件。",includeAccessCodes:"包含访问码",includeAccessCodesDescription:"用于转移到另一台机器",includeAccessCodesWarning:"访问码将以明文形式包含。请妥善保管此备份文件!",categoriesSelected:"已选择 {{selectedCount}} 个类别"},pendingUploads:{placeholders:{notes:"添加关于此打印的备注..."},discardUpload:"丢弃上传",archiveAllUploads:"归档所有上传",discardAllUploads:"丢弃所有上传",archive:"归档",timeAgo:{justNow:"刚刚",minutesAgo:"{{minutes}} 分钟前",hoursAgo:"{{hours}} 小时前",daysAgo:"{{days}} 天前"}},apiBrowser:{placeholders:{requestBody:"JSON 请求体...",searchEndpoints:"搜索端点..."}},configureAmsSlot:{title:"配置 AMS 槽位",slotConfigured:"槽位已配置!",configuringSlot:"正在配置槽位:",slotLabel:"{{ams}} 槽位 {{slot}}",searchPresets:"搜索预设...",colorPlaceholder:"颜色名称或十六进制(例如:棕色、FF8800)",clearCustomColor:"清除自定义颜色",noCloudPresets:"无云端预设。登录拓竹云以同步。",noPresetsAvailable:"无可用预设。登录拓竹云或导入本地配置。",noMatchingPresets:"未找到匹配的预设。",custom:"自定义",builtin:"内置",settingsSentToPrinter:"设置已发送到打印机",filamentProfile:"耗材配置",kProfileLabel:"K 值配置(压力推进)",filteringFor:"筛选:{{material}}",noKProfile:"无 K 值配置(使用默认值 0.020)",noMatchingKProfiles:"未找到匹配的 K 值配置。将使用默认 K=0.020。",selectFilamentFirst:"请先选择耗材配置",kFromCalibration:"K={{value}}(来自打印机校准)",customColorLabel:"自定义颜色(可选)",presetColors:"{{name}} 颜色:",showLessColors:"显示更少颜色",showMoreColors:"显示更多颜色",clear:"清除",hexLabel:"十六进制:#{{hex}}",resetting:"重置中...",resetSlot:"重置槽位",cancel:"取消",configuring:"配置中...",configureSlot:"配置槽位"},githubBackup:{title:"GitHub 备份",history:"历史",downloadBackup:"下载备份",restoreBackup:"恢复备份",noBackupsYet:"暂无备份"},emailSettings:{placeholders:{fromName:"BamBuddy"}},tagManagement:{searchTags:"搜索标签...",renameTag:"重命名标签",deleteTag:"删除标签"},notificationTemplates:{placeholders:{title:"通知标题...",body:"通知正文..."}},batchTag:{placeholders:{newTag:"输入新标签..."}},photoGallery:{deletePhoto:"删除照片"},filamentHoverCard:{copySpoolUuid:"复制耗材 UUID"},kProfilesView:{hasNote:"有备注",copyProfile:"复制配置"},layout:{openMenu:"打开菜单",noPermissionSystemInfo:"您没有查看系统信息的权限"},dashboard:{dragToReorder:"拖动以重新排列",hideWidget:"隐藏小部件"},notificationProviderCard:{deleteNotificationProvider:"删除通知提供商"},fileManagerModal:{closeFileManager:"关闭文件管理器",sortFiles:"排序文件",goToParentFolder:"返回上级文件夹",threeView:"3D 视图"},embeddedCameraViewer:{refreshStream:"刷新流",close:"关闭",zoomOut:"缩小",resetZoom:"重置缩放",zoomIn:"放大",dragToResize:"拖动调整大小"},timelapseViewer:{skipBack5s:"后退 5 秒",skipForward5s:"前进 5 秒"},notificationProviders:{descriptions:{email:"SMTP 邮件通知",telegram:"通过 Telegram 机器人通知",discord:"通过 Webhook 发送到 Discord 频道",ntfy:"免费、可自托管的推送通知",pushover:"简单、可靠的推送通知",callmebot:"通过 CallMeBot 的免费 WhatsApp 通知",webhook:"通用 HTTP POST 到任意 URL"}},logViewer:{searchPlaceholder:"搜索消息或日志名称...",noLogEntries:"未找到日志条目"},switchbarPopover:{noSwitchesInSwitchbar:"切换栏中没有开关"},projectPageModal:{placeholders:{title:"标题",designer:"设计师",license:"许可证",description:"输入描述...",profileTitle:"配置标题",profileDescription:"配置描述..."}},spoolmanSettings:{},time:{unknown:"-",waiting:"等待中",justNow:"刚刚",now:"现在",minsAgo:"{{count}} 分钟前",inMins:"{{count}} 分钟后",hoursAgo:"{{count}} 小时前",inHours:"{{count}} 小时后",daysAgo:"{{count}} 天前",inDays:"{{count}} 天后"},spoolbuddy:{nav:{dashboard:"仪表板",ams:"AMS",inventory:"库存",writeTag:"写入",settings:"设置"},status:{nfcReady:"NFC 就绪",nfcOff:"NFC 关闭",offline:"离线",online:"在线",noPrinters:"无打印机",deviceOffline:"设备离线",waitingConnection:"等待设备连接...",systemReady:"系统就绪",status:"状态"},dashboard:{readyToScan:"准备扫描",idleMessage:"将耗材放在秤上以识别",nfcHint:"NFC 标签将自动读取",device:"设备",syncWeight:"同步重量",weightSynced:"已同步!",unknownTag:"未知标签",newTag:"检测到新标签",onScale:"在秤上",linkSpool:"链接到耗材",linkTagTitle:"将标签链接到耗材",linkTag:"链接标签",selectSpool:"选择要链接此标签的耗材:",noUntagged:"未找到没有标签的耗材",tagDetected:"检测到标签",noTag:"无标签",tagId:"标签",grossWeight:"毛重",spoolSize:"耗材盘尺寸",close:"关闭",currentSpool:"当前耗材"},modal:{spoolDetected:"检测到耗材",assignToAms:"分配到 AMS",syncWeight:"同步重量",weightSynced:"已同步!",syncing:"同步中...",newTagDetected:"检测到新标签",addToInventory:"添加到库存",assignToAmsTitle:"分配到 AMS",selectSlot:"选择槽位",assign:"分配",assigning:"分配中...",assignSuccess:"已分配!",assignError:"分配耗材失败。请重试。",noPrinterSelected:"选择打印机...",noAmsDetected:"此打印机未检测到 AMS",slot:"槽位"},weight:{noReading:"无读数",stable:"稳定",measuring:"测量中...",tare:"去皮",calibrate:"校准"},spool:{remaining:"剩余",material:"材料",brand:"品牌",color:"颜色",coreWeight:"空盘",labelWeight:"标签",scaleWeight:"秤重",netWeight:"净重",lastUsed:"上次使用"},ams:{noData:"未检测到 AMS",connectAms:"连接 AMS 以查看耗材槽位",noPrinter:"未选择打印机",selectPrinter:"从顶部栏选择打印机",printerDisconnected:"打印机已断开",humidity:"湿度",level:"余量",active:"活跃",slot:"槽位",empty:"空"},inventory:{search:"搜索耗材...",empty:"库存中没有耗材",noResults:"没有匹配的耗材",spools:"个耗材",addSpool:"添加耗材"},settings:{tabDevice:"设备",tabDisplay:"显示",tabScale:"秤",tabUpdates:"更新",nfcReader:"NFC 读卡器",type:"类型",connection:"连接",notConnected:"不适用",deviceInfo:"设备信息",hostname:"主机",uptime:"运行时间",brightness:"亮度",saved:"已保存",noBacklight:"未检测到 DSI 背光。亮度控制需要 DSI 显示屏。",screenBlank:"屏幕熄灭超时",screenBlankDesc:"不活动后屏幕关闭。触摸唤醒。",displayNote:"亮度作为软件滤镜应用。",scaleCalibration:"秤校准",currentWeight:"当前重量",tareOffset:"去皮",calFactor:"系数",knownWeight:"已知重量",calStep1:"移除秤上所有物品并按设置零点。",calStep2:"将已知重量放在秤上。",setZero:"设置零点",calibrateNow:"校准",calibrated:"已校准",tareSet:"去皮命令已发送。等待设备响应...",tareFailed:"发送去皮命令失败",zeroSet:"零点已设置。将已知重量放在秤上。",calibrationDone:"校准完成!",calibrationFailed:"校准失败",lastCalibrated:"上次校准",stable:"稳定",settling:"稳定中...",firmware:"固件",scale:"秤",noDevice:"未找到 SpoolBuddy 设备",daemonVersion:"守护进程版本",currentVersion:"当前",versionPending:"等待守护进程...",checking:"检查中...",checkUpdates:"检查更新",updateAvailable:"有可用更新",updateInstructions:"通过 SSH 更新:运行 SpoolBuddy 安装脚本进行升级。",upToDate:"已是最新",includeBeta:"包含测试版本"},writeTag:{tabExisting:"现有耗材",tabNew:"新耗材",tabReplace:"替换标签",searchPlaceholder:"按材料、颜色、品牌搜索...",noUntaggedSpools:"没有无标签的耗材",noTaggedSpools:"没有有标签的耗材",selectSpool:"选择一个耗材,然后将空白 NTAG 放在读卡器上",placeTag:"将 NTAG 放在读卡器上",tagReady:"检测到标签 — 准备写入",writeTag:"写入标签",replaceTag:"替换标签",writing:"写入标签中...",waiting:"等待 SpoolBuddy...",writeSuccess:"标签写入成功!",writeFailed:"写入失败",queueFailed:"排队写入命令失败",tryAgain:"重试",cancel:"取消",replaceWarning:"旧标签将被取消链接。新标签将替换它。",deviceOffline:"SpoolBuddy 离线",material:"材料",colorName:"颜色名称",color:"颜色",brand:"品牌",weight:"重量 (g)",createSpool:"创建耗材",creating:"创建中...",spoolCreated:"耗材已创建!准备写入。",createFailed:"创建耗材失败"}},bugReport:{title:"报告错误",description:"描述",descriptionPlaceholder:"出了什么问题?请描述问题...",email:"邮箱(可选)",emailPlaceholder:"your@email.com",emailPrivacy:"如果提供,您的邮箱将包含在GitHub Issue的折叠部分中,以便维护者后续跟进。",screenshot:"截图",uploadOrPaste:"上传、粘贴或拖拽图片",dataCollectedSummary:"报告中包含哪些数据?",dataIncluded:"包含:",dataIncludedList:"应用版本、操作系统、架构、Python版本、数据库统计(仅计数)、打印机型号、喷嘴数量、固件版本、连接状态、集成状态(Spoolman、MQTT、HA)、非敏感设置、网络接口数量、Docker详情、依赖版本。",dataNeverIncluded:"绝不包含:",dataNeverIncludedList:"打印机名称、序列号、访问代码、密码、IP地址、邮箱地址、API密钥、令牌、Webhook URL、主机名或用户名。",submit:"提交",startLogging:"开始调试日志",stepEnableLogging:"调试日志已启用",stepReproduce:"请现在重现问题",stepStopLogging:"停止并提交报告",stopAndSubmit:"停止并提交",maxDuration:"{{minutes}}分钟后自动停止",stoppingLogs:"正在收集日志并提交...",submitting:"正在提交错误报告...",submitSuccess:"错误报告提交成功!",submitFailed:"提交错误报告失败",thankYou:"谢谢!",submitted:"您的错误报告已提交。",viewIssue:"查看Issue",unexpectedError:"发生了意外错误"}},Yue={en:{translation:Hue},de:{translation:$ue},fr:{translation:que},ja:{translation:Vue},it:{translation:Gue},"pt-BR":{translation:Wue},"zh-CN":{translation:Kue}};to.use(sX).use(vue).init({resources:Yue,fallbackLng:"en",supportedLngs:["en","de","fr","ja","it","pt-BR","zh-CN"],detection:{order:["localStorage","navigator","htmlTag"],lookupLocalStorage:"bambutrack_language",caches:["localStorage"]},interpolation:{escapeValue:!1},react:{useSuspense:!1}});const M8=[{code:"en",name:"English",nativeName:"English"},{code:"de",name:"German",nativeName:"Deutsch"},{code:"fr",name:"French",nativeName:"Français"},{code:"ja",name:"Japanese",nativeName:"日本語"},{code:"it",name:"Italian",nativeName:"Italiano"},{code:"pt-BR",name:"Portuguese (Brazil)",nativeName:"Português (Brasil)"},{code:"zh-CN",name:"Chinese (Simplified)",nativeName:"简体中文"}];var E8="popstate";function Xue(t={}){function e(r,a){let{pathname:s,search:o,hash:l}=r.location;return c3("",{pathname:s,search:o,hash:l},a.state&&a.state.usr||null,a.state&&a.state.key||"default")}function n(r,a){return typeof a=="string"?a:y0(a)}return Zue(e,n,null,t)}function pi(t,e){if(t===!1||t===null||typeof t>"u")throw new Error(e)}function ql(t,e){if(!t){typeof console<"u"&&console.warn(e);try{throw new Error(e)}catch{}}}function Que(){return Math.random().toString(36).substring(2,10)}function j8(t,e){return{usr:t.state,key:t.key,idx:e}}function c3(t,e,n=null,r){return{pathname:typeof t=="string"?t:t.pathname,search:"",hash:"",...typeof e=="string"?Bx(e):e,state:n,key:e&&e.key||r||Que()}}function y0({pathname:t="/",search:e="",hash:n=""}){return e&&e!=="?"&&(t+=e.charAt(0)==="?"?e:"?"+e),n&&n!=="#"&&(t+=n.charAt(0)==="#"?n:"#"+n),t}function Bx(t){let e={};if(t){let n=t.indexOf("#");n>=0&&(e.hash=t.substring(n),t=t.substring(0,n));let r=t.indexOf("?");r>=0&&(e.search=t.substring(r),t=t.substring(0,r)),t&&(e.pathname=t)}return e}function Zue(t,e,n,r={}){let{window:a=document.defaultView,v5Compat:s=!1}=r,o=a.history,l="POP",c=null,u=d();u==null&&(u=0,o.replaceState({...o.state,idx:u},""));function d(){return(o.state||{idx:null}).idx}function m(){l="POP";let b=d(),g=b==null?null:b-u;u=b,c&&c({action:l,location:v.location,delta:g})}function p(b,g){l="PUSH";let _=c3(v.location,b,g);u=d()+1;let N=j8(_,u),C=v.createHref(_);try{o.pushState(N,"",C)}catch(k){if(k instanceof DOMException&&k.name==="DataCloneError")throw k;a.location.assign(C)}s&&c&&c({action:l,location:v.location,delta:1})}function f(b,g){l="REPLACE";let _=c3(v.location,b,g);u=d();let N=j8(_,u),C=v.createHref(_);o.replaceState(N,"",C),s&&c&&c({action:l,location:v.location,delta:0})}function y(b){return Jue(b)}let v={get action(){return l},get location(){return t(a,o)},listen(b){if(c)throw new Error("A history only accepts one active listener");return a.addEventListener(E8,m),c=b,()=>{a.removeEventListener(E8,m),c=null}},createHref(b){return e(a,b)},createURL:y,encodeLocation(b){let g=y(b);return{pathname:g.pathname,search:g.search,hash:g.hash}},push:p,replace:f,go(b){return o.go(b)}};return v}function Jue(t,e=!1){let n="http://localhost";typeof window<"u"&&(n=window.location.origin!=="null"?window.location.origin:window.location.href),pi(n,"No window.location.(origin|href) available to create URL");let r=typeof t=="string"?t:y0(t);return r=r.replace(/ $/,"%20"),!e&&r.startsWith("//")&&(r=n+r),new URL(r,n)}function oX(t,e,n="/"){return ede(t,e,n,!1)}function ede(t,e,n,r){let a=typeof e=="string"?Bx(e):e,s=zd(a.pathname||"/",n);if(s==null)return null;let o=lX(t);tde(o);let l=null;for(let c=0;l==null&&c{let d={relativePath:u===void 0?o.path||"":u,caseSensitive:o.caseSensitive===!0,childrenIndex:l,route:o};if(d.relativePath.startsWith("/")){if(!d.relativePath.startsWith(r)&&c)return;pi(d.relativePath.startsWith(r),`Absolute route path "${d.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),d.relativePath=d.relativePath.slice(r.length)}let m=Md([r,d.relativePath]),p=n.concat(d);o.children&&o.children.length>0&&(pi(o.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${m}".`),lX(o.children,e,p,m,c)),!(o.path==null&&!o.index)&&e.push({path:m,score:lde(m,o.index),routesMeta:p})};return t.forEach((o,l)=>{if(o.path===""||!o.path?.includes("?"))s(o,l);else for(let c of cX(o.path))s(o,l,!0,c)}),e}function cX(t){let e=t.split("/");if(e.length===0)return[];let[n,...r]=e,a=n.endsWith("?"),s=n.replace(/\?$/,"");if(r.length===0)return a?[s,""]:[s];let o=cX(r.join("/")),l=[];return l.push(...o.map(c=>c===""?s:[s,c].join("/"))),a&&l.push(...o),l.map(c=>t.startsWith("/")&&c===""?"/":c)}function tde(t){t.sort((e,n)=>e.score!==n.score?n.score-e.score:cde(e.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}var nde=/^:[\w-]+$/,rde=3,ide=2,ade=1,sde=10,ode=-2,D8=t=>t==="*";function lde(t,e){let n=t.split("/"),r=n.length;return n.some(D8)&&(r+=ode),e&&(r+=ide),n.filter(a=>!D8(a)).reduce((a,s)=>a+(nde.test(s)?rde:s===""?ade:sde),r)}function cde(t,e){return t.length===e.length&&t.slice(0,-1).every((r,a)=>r===e[a])?t[t.length-1]-e[e.length-1]:0}function ude(t,e,n=!1){let{routesMeta:r}=t,a={},s="/",o=[];for(let l=0;l{if(d==="*"){let y=l[p]||"";o=s.slice(0,s.length-y.length).replace(/(.)\/+$/,"$1")}const f=l[p];return m&&!f?u[d]=void 0:u[d]=(f||"").replace(/%2F/g,"/"),u},{}),pathname:s,pathnameBase:o,pattern:t}}function dde(t,e=!1,n=!0){ql(t==="*"||!t.endsWith("*")||t.endsWith("/*"),`Route path "${t}" will be treated as if it were "${t.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${t.replace(/\*$/,"/*")}".`);let r=[],a="^"+t.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(o,l,c)=>(r.push({paramName:l,isOptional:c!=null}),c?"/?([^\\/]+)?":"/([^\\/]+)")).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return t.endsWith("*")?(r.push({paramName:"*"}),a+=t==="*"||t==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?a+="\\/*$":t!==""&&t!=="/"&&(a+="(?:(?=\\/|$))"),[new RegExp(a,e?void 0:"i"),r]}function mde(t){try{return t.split("/").map(e=>decodeURIComponent(e).replace(/\//g,"%2F")).join("/")}catch(e){return ql(!1,`The URL path "${t}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${e}).`),t}}function zd(t,e){if(e==="/")return t;if(!t.toLowerCase().startsWith(e.toLowerCase()))return null;let n=e.endsWith("/")?e.length-1:e.length,r=t.charAt(n);return r&&r!=="/"?null:t.slice(n)||"/"}var hde=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function pde(t,e="/"){let{pathname:n,search:r="",hash:a=""}=typeof t=="string"?Bx(t):t,s;return n?(n=n.replace(/\/\/+/g,"/"),n.startsWith("/")?s=R8(n.substring(1),"/"):s=R8(n,e)):s=e,{pathname:s,search:bde(r),hash:xde(a)}}function R8(t,e){let n=e.replace(/\/+$/,"").split("/");return t.split("/").forEach(a=>{a===".."?n.length>1&&n.pop():a!=="."&&n.push(a)}),n.length>1?n.join("/"):"/"}function GM(t,e,n,r){return`Cannot include a '${t}' character in a manually specified \`to.${e}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function fde(t){return t.filter((e,n)=>n===0||e.route.path&&e.route.path.length>0)}function PL(t){let e=fde(t);return e.map((n,r)=>r===e.length-1?n.pathname:n.pathnameBase)}function ML(t,e,n,r=!1){let a;typeof t=="string"?a=Bx(t):(a={...t},pi(!a.pathname||!a.pathname.includes("?"),GM("?","pathname","search",a)),pi(!a.pathname||!a.pathname.includes("#"),GM("#","pathname","hash",a)),pi(!a.search||!a.search.includes("#"),GM("#","search","hash",a)));let s=t===""||a.pathname==="",o=s?"/":a.pathname,l;if(o==null)l=n;else{let m=e.length-1;if(!r&&o.startsWith("..")){let p=o.split("/");for(;p[0]==="..";)p.shift(),m-=1;a.pathname=p.join("/")}l=m>=0?e[m]:"/"}let c=pde(a,l),u=o&&o!=="/"&&o.endsWith("/"),d=(s||o===".")&&n.endsWith("/");return!c.pathname.endsWith("/")&&(u||d)&&(c.pathname+="/"),c}var Md=t=>t.join("/").replace(/\/\/+/g,"/"),gde=t=>t.replace(/\/+$/,"").replace(/^\/*/,"/"),bde=t=>!t||t==="?"?"":t.startsWith("?")?t:"?"+t,xde=t=>!t||t==="#"?"":t.startsWith("#")?t:"#"+t,yde=class{constructor(t,e,n,r=!1){this.status=t,this.statusText=e||"",this.internal=r,n instanceof Error?(this.data=n.toString(),this.error=n):this.data=n}};function vde(t){return t!=null&&typeof t.status=="number"&&typeof t.statusText=="string"&&typeof t.internal=="boolean"&&"data"in t}function wde(t){return t.map(e=>e.route.path).filter(Boolean).join("/").replace(/\/\/*/g,"/")||"/"}var uX=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function dX(t,e){let n=t;if(typeof n!="string"||!hde.test(n))return{absoluteURL:void 0,isExternal:!1,to:n};let r=n,a=!1;if(uX)try{let s=new URL(window.location.href),o=n.startsWith("//")?new URL(s.protocol+n):new URL(n),l=zd(o.pathname,e);o.origin===s.origin&&l!=null?n=l+o.search+o.hash:a=!0}catch{ql(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:a,to:n}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var mX=["POST","PUT","PATCH","DELETE"];new Set(mX);var Sde=["GET",...mX];new Set(Sde);var Hx=w.createContext(null);Hx.displayName="DataRouter";var tT=w.createContext(null);tT.displayName="DataRouterState";var _de=w.createContext(!1),hX=w.createContext({isTransitioning:!1});hX.displayName="ViewTransition";var Nde=w.createContext(new Map);Nde.displayName="Fetchers";var kde=w.createContext(null);kde.displayName="Await";var sl=w.createContext(null);sl.displayName="Navigation";var pw=w.createContext(null);pw.displayName="Location";var Jl=w.createContext({outlet:null,matches:[],isDataRoute:!1});Jl.displayName="Route";var EL=w.createContext(null);EL.displayName="RouteError";var pX="REACT_ROUTER_ERROR",Cde="REDIRECT",Tde="ROUTE_ERROR_RESPONSE";function Ade(t){if(t.startsWith(`${pX}:${Cde}:{`))try{let e=JSON.parse(t.slice(28));if(typeof e=="object"&&e&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.location=="string"&&typeof e.reloadDocument=="boolean"&&typeof e.replace=="boolean")return e}catch{}}function Pde(t){if(t.startsWith(`${pX}:${Tde}:{`))try{let e=JSON.parse(t.slice(40));if(typeof e=="object"&&e&&typeof e.status=="number"&&typeof e.statusText=="string")return new yde(e.status,e.statusText,e.data)}catch{}}function Mde(t,{relative:e}={}){pi($x(),"useHref() may be used only in the context of a component.");let{basename:n,navigator:r}=w.useContext(sl),{hash:a,pathname:s,search:o}=bw(t,{relative:e}),l=s;return n!=="/"&&(l=s==="/"?n:Md([n,s])),r.createHref({pathname:l,search:o,hash:a})}function $x(){return w.useContext(pw)!=null}function Tu(){return pi($x(),"useLocation() may be used only in the context of a component."),w.useContext(pw).location}var fX="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function gX(t){w.useContext(sl).static||w.useLayoutEffect(t)}function ol(){let{isDataRoute:t}=w.useContext(Jl);return t?qde():Ede()}function Ede(){pi($x(),"useNavigate() may be used only in the context of a component.");let t=w.useContext(Hx),{basename:e,navigator:n}=w.useContext(sl),{matches:r}=w.useContext(Jl),{pathname:a}=Tu(),s=JSON.stringify(PL(r)),o=w.useRef(!1);return gX(()=>{o.current=!0}),w.useCallback((c,u={})=>{if(ql(o.current,fX),!o.current)return;if(typeof c=="number"){n.go(c);return}let d=ML(c,JSON.parse(s),a,u.relative==="path");t==null&&e!=="/"&&(d.pathname=d.pathname==="/"?e:Md([e,d.pathname])),(u.replace?n.replace:n.push)(d,u.state,u)},[e,n,s,a,t])}var bX=w.createContext(null);function fw(){return w.useContext(bX)}function jde(t){let e=w.useContext(Jl).outlet;return w.useMemo(()=>e&&w.createElement(bX.Provider,{value:t},e),[e,t])}function gw(){let{matches:t}=w.useContext(Jl),e=t[t.length-1];return e?e.params:{}}function bw(t,{relative:e}={}){let{matches:n}=w.useContext(Jl),{pathname:r}=Tu(),a=JSON.stringify(PL(n));return w.useMemo(()=>ML(t,JSON.parse(a),r,e==="path"),[t,a,r,e])}function Dde(t,e){return xX(t,e)}function xX(t,e,n,r,a){pi($x(),"useRoutes() may be used only in the context of a component.");let{navigator:s}=w.useContext(sl),{matches:o}=w.useContext(Jl),l=o[o.length-1],c=l?l.params:{},u=l?l.pathname:"/",d=l?l.pathnameBase:"/",m=l&&l.route;{let _=m&&m.path||"";vX(u,!m||_.endsWith("*")||_.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${u}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. -Please change the parent to .`)}let p=Tu(),f;if(e){let _=typeof e=="string"?Bx(e):e;pi(d==="/"||_.pathname?.startsWith(d),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${d}" but pathname "${_.pathname}" was given in the \`location\` prop.`),f=_}else f=p;let y=f.pathname||"/",v=y;if(d!=="/"){let _=d.replace(/^\//,"").split("/");v="/"+y.replace(/^\//,"").split("/").slice(_.length).join("/")}let b=oX(t,{pathname:v});ql(m||b!=null,`No routes matched location "${f.pathname}${f.search}${f.hash}" `),ql(b==null||b[b.length-1].route.element!==void 0||b[b.length-1].route.Component!==void 0||b[b.length-1].route.lazy!==void 0,`Matched leaf route at location "${f.pathname}${f.search}${f.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let g=Ide(b&&b.map(_=>Object.assign({},_,{params:Object.assign({},c,_.params),pathname:Md([d,s.encodeLocation?s.encodeLocation(_.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:_.pathname]),pathnameBase:_.pathnameBase==="/"?d:Md([d,s.encodeLocation?s.encodeLocation(_.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:_.pathnameBase])})),o,n,r,a);return e&&g?w.createElement(pw.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...f},navigationType:"POP"}},g):g}function Rde(){let t=$de(),e=vde(t)?`${t.status} ${t.statusText}`:t instanceof Error?t.message:JSON.stringify(t),n=t instanceof Error?t.stack:null,r="rgba(200,200,200, 0.5)",a={padding:"0.5rem",backgroundColor:r},s={padding:"2px 4px",backgroundColor:r},o=null;return console.error("Error handled by React Router default ErrorBoundary:",t),o=w.createElement(w.Fragment,null,w.createElement("p",null,"💿 Hey developer 👋"),w.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",w.createElement("code",{style:s},"ErrorBoundary")," or"," ",w.createElement("code",{style:s},"errorElement")," prop on your route.")),w.createElement(w.Fragment,null,w.createElement("h2",null,"Unexpected Application Error!"),w.createElement("h3",{style:{fontStyle:"italic"}},e),n?w.createElement("pre",{style:a},n):null,o)}var Fde=w.createElement(Rde,null),yX=class extends w.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,e){return e.location!==t.location||e.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:e.error,location:e.location,revalidation:t.revalidation||e.revalidation}}componentDidCatch(t,e){this.props.onError?this.props.onError(t,e):console.error("React Router caught the following error during render",t)}render(){let t=this.state.error;if(this.context&&typeof t=="object"&&t&&"digest"in t&&typeof t.digest=="string"){const n=Pde(t.digest);n&&(t=n)}let e=t!==void 0?w.createElement(Jl.Provider,{value:this.props.routeContext},w.createElement(EL.Provider,{value:t,children:this.props.component})):this.props.children;return this.context?w.createElement(Lde,{error:t},e):e}};yX.contextType=_de;var WM=new WeakMap;function Lde({children:t,error:e}){let{basename:n}=w.useContext(sl);if(typeof e=="object"&&e&&"digest"in e&&typeof e.digest=="string"){let r=Ade(e.digest);if(r){let a=WM.get(e);if(a)throw a;let s=dX(r.location,n);if(uX&&!WM.get(e))if(s.isExternal||r.reloadDocument)window.location.href=s.absoluteURL||s.to;else{const o=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(s.to,{replace:r.replace}));throw WM.set(e,o),o}return w.createElement("meta",{httpEquiv:"refresh",content:`0;url=${s.absoluteURL||s.to}`})}}return t}function Ode({routeContext:t,match:e,children:n}){let r=w.useContext(Hx);return r&&r.static&&r.staticContext&&(e.route.errorElement||e.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=e.route.id),w.createElement(Jl.Provider,{value:t},n)}function Ide(t,e=[],n=null,r=null,a=null){if(t==null){if(!n)return null;if(n.errors)t=n.matches;else if(e.length===0&&!n.initialized&&n.matches.length>0)t=n.matches;else return null}let s=t,o=n?.errors;if(o!=null){let d=s.findIndex(m=>m.route.id&&o?.[m.route.id]!==void 0);pi(d>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(o).join(",")}`),s=s.slice(0,Math.min(s.length,d+1))}let l=!1,c=-1;if(n)for(let d=0;d=0?s=s.slice(0,c+1):s=[s[0]];break}}}let u=n&&r?(d,m)=>{r(d,{location:n.location,params:n.matches?.[0]?.params??{},unstable_pattern:wde(n.matches),errorInfo:m})}:void 0;return s.reduceRight((d,m,p)=>{let f,y=!1,v=null,b=null;n&&(f=o&&m.route.id?o[m.route.id]:void 0,v=m.route.errorElement||Fde,l&&(c<0&&p===0?(vX("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),y=!0,b=null):c===p&&(y=!0,b=m.route.hydrateFallbackElement||null)));let g=e.concat(s.slice(0,p+1)),_=()=>{let N;return f?N=v:y?N=b:m.route.Component?N=w.createElement(m.route.Component,null):m.route.element?N=m.route.element:N=d,w.createElement(Ode,{match:m,routeContext:{outlet:d,matches:g,isDataRoute:n!=null},children:N})};return n&&(m.route.ErrorBoundary||m.route.errorElement||p===0)?w.createElement(yX,{location:n.location,revalidation:n.revalidation,component:v,error:f,children:_(),routeContext:{outlet:null,matches:g,isDataRoute:!0},onError:u}):_()},null)}function jL(t){return`${t} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function zde(t){let e=w.useContext(Hx);return pi(e,jL(t)),e}function Ude(t){let e=w.useContext(tT);return pi(e,jL(t)),e}function Bde(t){let e=w.useContext(Jl);return pi(e,jL(t)),e}function DL(t){let e=Bde(t),n=e.matches[e.matches.length-1];return pi(n.route.id,`${t} can only be used on routes that contain a unique "id"`),n.route.id}function Hde(){return DL("useRouteId")}function $de(){let t=w.useContext(EL),e=Ude("useRouteError"),n=DL("useRouteError");return t!==void 0?t:e.errors?.[n]}function qde(){let{router:t}=zde("useNavigate"),e=DL("useNavigate"),n=w.useRef(!1);return gX(()=>{n.current=!0}),w.useCallback(async(a,s={})=>{ql(n.current,fX),n.current&&(typeof a=="number"?await t.navigate(a):await t.navigate(a,{fromRouteId:e,...s}))},[t,e])}var F8={};function vX(t,e,n){!e&&!F8[t]&&(F8[t]=!0,ql(!1,n))}w.memo(Vde);function Vde({routes:t,future:e,state:n,onError:r}){return xX(t,void 0,n,r,e)}function lx({to:t,replace:e,state:n,relative:r}){pi($x()," may be used only in the context of a component.");let{static:a}=w.useContext(sl);ql(!a," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:s}=w.useContext(Jl),{pathname:o}=Tu(),l=ol(),c=ML(t,PL(s),o,r==="path"),u=JSON.stringify(c);return w.useEffect(()=>{l(JSON.parse(u),{replace:e,state:n,relative:r})},[l,u,r,e,n]),null}function wX(t){return jde(t.context)}function kr(t){pi(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Gde({basename:t="/",children:e=null,location:n,navigationType:r="POP",navigator:a,static:s=!1,unstable_useTransitions:o}){pi(!$x(),"You cannot render a inside another . You should never have more than one in your app.");let l=t.replace(/^\/*/,"/"),c=w.useMemo(()=>({basename:l,navigator:a,static:s,unstable_useTransitions:o,future:{}}),[l,a,s,o]);typeof n=="string"&&(n=Bx(n));let{pathname:u="/",search:d="",hash:m="",state:p=null,key:f="default"}=n,y=w.useMemo(()=>{let v=zd(u,l);return v==null?null:{location:{pathname:v,search:d,hash:m,state:p,key:f},navigationType:r}},[l,u,d,m,p,f,r]);return ql(y!=null,` is not able to match the URL "${u}${d}${m}" because it does not start with the basename, so the won't render anything.`),y==null?null:w.createElement(sl.Provider,{value:c},w.createElement(pw.Provider,{children:e,value:y}))}function Wde({children:t,location:e}){return Dde(u3(t),e)}function u3(t,e=[]){let n=[];return w.Children.forEach(t,(r,a)=>{if(!w.isValidElement(r))return;let s=[...e,a];if(r.type===w.Fragment){n.push.apply(n,u3(r.props.children,s));return}pi(r.type===kr,`[${typeof r.type=="string"?r.type:r.type.name}] is not a component. All component children of must be a or `),pi(!r.props.index||!r.props.children,"An index route cannot have child routes.");let o={id:r.props.id||s.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,middleware:r.props.middleware,loader:r.props.loader,action:r.props.action,hydrateFallbackElement:r.props.hydrateFallbackElement,HydrateFallback:r.props.HydrateFallback,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.hasErrorBoundary===!0||r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(o.children=u3(r.props.children,s)),n.push(o)}),n}var J1="get",eN="application/x-www-form-urlencoded";function nT(t){return typeof HTMLElement<"u"&&t instanceof HTMLElement}function Kde(t){return nT(t)&&t.tagName.toLowerCase()==="button"}function Yde(t){return nT(t)&&t.tagName.toLowerCase()==="form"}function Xde(t){return nT(t)&&t.tagName.toLowerCase()==="input"}function Qde(t){return!!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)}function Zde(t,e){return t.button===0&&(!e||e==="_self")&&!Qde(t)}function d3(t=""){return new URLSearchParams(typeof t=="string"||Array.isArray(t)||t instanceof URLSearchParams?t:Object.keys(t).reduce((e,n)=>{let r=t[n];return e.concat(Array.isArray(r)?r.map(a=>[n,a]):[[n,r]])},[]))}function Jde(t,e){let n=d3(t);return e&&e.forEach((r,a)=>{n.has(a)||e.getAll(a).forEach(s=>{n.append(a,s)})}),n}var u_=null;function eme(){if(u_===null)try{new FormData(document.createElement("form"),0),u_=!1}catch{u_=!0}return u_}var tme=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function KM(t){return t!=null&&!tme.has(t)?(ql(!1,`"${t}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${eN}"`),null):t}function nme(t,e){let n,r,a,s,o;if(Yde(t)){let l=t.getAttribute("action");r=l?zd(l,e):null,n=t.getAttribute("method")||J1,a=KM(t.getAttribute("enctype"))||eN,s=new FormData(t)}else if(Kde(t)||Xde(t)&&(t.type==="submit"||t.type==="image")){let l=t.form;if(l==null)throw new Error('Cannot submit a