Files
mc-webui/app/translations/en.json
T
MarekWoandClaude Opus 5 60faa44d37 feat(updater): one-click updates for Docker Hub installations
The version check already worked for an installation running the published
image - CI freezes the version before building, so version_frozen.py ships
inside it - but "Update now" never appeared, because the webhook behind it
was installed from scripts/, a folder such an installation does not have.
And update.sh could not have served it anyway: it does git pull + rebuild.

- both installers accept being piped through bash (curl | sudo bash). They
  then download what they need into /opt/mc-webui-{updater,watchdog} and
  find the instance by looking for docker-compose.yml. A checkout install
  keeps running its scripts from the checkout, unchanged
- updater.py picks the update path per request: git checkout -> update.sh,
  bare docker-compose.yml -> the new update-image.sh (pull + recreate).
  Resolved per request, so cloning the repo later flips it without a restart
- both update scripts end with MC_UPDATE_RESULT=updated|no_change, which
  the webhook parses and /api/updater/status passes on. The UI used to
  watch only for the version to change and then claim it timed out after
  two minutes - the normal outcome right after a push, while the image for
  that commit is still building. It now says so as soon as the run ends,
  in a neutral state rather than a red cross
- the marker prints only when stdout is not a terminal, so mcupdate output
  stays as it was

Also corrects the claim, in the README and in updater.py's own docstring,
that the webhook listens on localhost. It binds 0.0.0.0 - the container
reaches it over the Docker bridge - and sends no token, so port 5050 is
worth keeping off untrusted networks.

Verified: update-image.sh reports updated then no_change against a real
docker compose project; the standalone installer installs, runs and
uninstalls cleanly under systemd (WSL), with the app reaching it through
host.docker.internal and a triggered run coming back changed=false; the
resulting modal checked in a browser in both en and pl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 18:53:35 +02:00

1507 lines
86 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"analyzer.add": "Add analyzer",
"analyzer.choose": "Choose analyzer",
"analyzer.clear_default_title": "Clear default",
"analyzer.confirm.delete": "Delete analyzer \"{name}\"?",
"analyzer.edit": "Edit analyzer",
"analyzer.empty": "No analyzers configured. Click \"Add analyzer\" to add one.",
"analyzer.load_failed": "Failed to load analyzers",
"analyzer.name_ph": "e.g. MarWoj Analyzer",
"analyzer.name_required": "Name is required",
"analyzer.set_default_title": "Mark as default",
"analyzer.toast.default_error": "Network error updating default",
"analyzer.toast.default_failed": "Failed to update default",
"analyzer.toast.delete_error": "Network error deleting analyzer",
"analyzer.toast.delete_failed": "Failed to delete analyzer",
"analyzer.toast.none_configured": "No analyzer configured. Add one in Settings → Analyzer.",
"analyzer.toast.save_error": "Network error saving analyzer",
"analyzer.toast.save_failed": "Failed to save analyzer",
"analyzer.toast.update_error": "Network error updating analyzer",
"analyzer.toast.update_failed": "Failed to update analyzer",
"analyzer.url": "URL template",
"analyzer.url_hint": "Must include <code>{packetHash}</code>.",
"analyzer.url_invalid": "URL must start with http:// or https://",
"analyzer.url_placeholder_missing": "URL must contain the {placeholder} placeholder",
"backup.auto_off": "Auto-backup disabled",
"backup.auto_on": "Auto: daily at {time}, keep {days}d",
"backup.create": "Create Backup",
"backup.created": "Backup created: {filename}",
"backup.creating": "Creating...",
"backup.current_size": "Current size: …",
"backup.download_title": "Download",
"backup.empty": "No backups yet",
"backup.error": "Backup failed",
"backup.failed": "Backup failed: {error}",
"backup.freed": "freed {size}",
"backup.freed_none": "no space to reclaim",
"backup.load_failed": "Failed to load backups",
"backup.optimize_error": "Optimize failed",
"backup.optimize_failed": "Optimize failed: {error}",
"backup.optimize_hint": "Reclaim space freed by message retention. Runs SQLite <code>VACUUM</code>; readers keep working but writes are paused for a few seconds.",
"backup.optimize_now": "Optimize now",
"backup.optimize_stuck": "Optimize is still running after 10 minutes — check container logs",
"backup.optimize_title": "Optimize database",
"backup.optimized": "Optimized: {freed} in {seconds}s",
"backup.optimizing": "Optimizing…",
"backup.size": "Current size: {size}",
"backup.size_unknown": "Size: unknown",
"backup.title": "Database Backup",
"backup.vacuum_running": "Running VACUUM…",
"backup.vacuum_running_for": "Running VACUUM… ({seconds}s)",
"channels.add_new": "Add New Channel",
"channels.confirm.remove": "Remove channel \"{name}\"?",
"channels.create_btn": "Create & Auto-generate Key",
"channels.create_title": "Create New Channel",
"channels.dropdown.none": "No channels",
"channels.fav_title": "Favorite channel",
"channels.join_btn": "Join Channel",
"channels.join_existing": "Join Existing",
"channels.join_title": "Join Existing Channel",
"channels.key": "Channel Key:",
"channels.key_label": "Channel Key (32 hex chars)",
"channels.key_optional": "- optional for channels starting with #",
"channels.key_ph": "485af7e164459d280d8818d9c99fb30d (leave empty for # channels)",
"channels.list.empty": "No channels configured",
"channels.list.load_error": "Error loading channels",
"channels.list.load_failed": "Failed to load channels",
"channels.name": "Channel Name",
"channels.name_hint": "Only letters, numbers, _ and -",
"channels.name_ph": "e.g., Malopolska",
"channels.notify.all": "Every message",
"channels.notify.manage": "Manage profiles…",
"channels.notify.no_profiles": "No profiles defined yet",
"channels.notify.off": "Off",
"channels.notify.profiles_header": "Only messages matching a profile",
"channels.notify.title_all": "Notifications: every message — click to change",
"channels.notify.title_off": "Notifications: off — click to change",
"channels.notify.title_profile": "Notifications: profile {name} — click to change",
"channels.qr_alt": "QR Code",
"channels.region_hint": "Only repeaters allowing the selected region will forward messages from this channel.",
"channels.region_scope_for": "Set Region Scope for",
"channels.scan_qr": "Scan QR Code",
"channels.scope_set_title": "Set region scope",
"channels.scope_title": "Region: {name} — click to change",
"channels.share_hint": "Share this QR code or key with others to let them join this channel.",
"channels.share_name": "Channel: {name}",
"channels.share_title": "Share Channel",
"channels.toast.already_joined": "Already joined channel \"{name}\".",
"channels.toast.copy_failed": "Failed to copy to clipboard",
"channels.toast.create_error": "Failed to create channel",
"channels.toast.create_failed": "Failed to create channel: {error}",
"channels.toast.created": "Channel \"{name}\" created!",
"channels.toast.exists": "Channel \"{name}\" already exists.",
"channels.toast.favorite_failed": "Failed to update favorite state",
"channels.toast.join_error": "Failed to join channel",
"channels.toast.join_failed": "Failed to join channel: {error}",
"channels.toast.joined": "Joined channel \"{name}\"!",
"channels.toast.key_copied": "Channel key copied to clipboard!",
"channels.toast.key_invalid": "Invalid key format. Must be 32 hex characters.",
"channels.toast.key_required": "Channel key is required for channels not starting with #",
"channels.toast.notify_failed": "Failed to change notifications",
"channels.toast.qr_error": "Failed to generate QR code",
"channels.toast.qr_failed": "Failed to generate QR code: {error}",
"channels.toast.qr_soon": "QR scanning feature coming soon! For now, manually enter the channel details.",
"channels.toast.remove_error": "Failed to remove channel",
"channels.toast.remove_failed": "Failed to remove channel: {error}",
"channels.toast.removed": "Channel \"{name}\" removed",
"channels.toast.switched": "Switched to channel: {name}",
"channels.unfav_title": "Unfavorite channel",
"channels.your": "Your Channels",
"chat.channels": "Channels",
"chat.confirm.block": "Block {name}? Their messages will be hidden from chat.",
"chat.confirm.mark_all_read": "Mark all messages as read?\n\nUnread channels:\n{channels}",
"chat.confirm.stale_resend": "This message was sent {age}. Repeaters only remember recent packets, so nothing will suppress it this time — everyone on the channel will see it again as a new message. Resend anyway?",
"chat.copy_route_title": "Tap to copy route",
"chat.edit_title": "Edit message",
"chat.emoji_title": "Insert emoji",
"chat.empty.no_messages": "No messages yet",
"chat.empty.no_messages_hint": "Send a message to get started!",
"chat.error.load_retry": "Will retry automatically",
"chat.error.load_title": "Could not load messages",
"chat.fab.hide": "Hide buttons",
"chat.fab.search_title": "Search Messages",
"chat.fab.show": "Show buttons",
"chat.filter.clear_title": "Clear",
"chat.filter.fab_title": "Filter Messages",
"chat.filter.me_title": "Filter my messages",
"chat.filter.no_matches": "No messages match \"{query}\"",
"chat.filter.ph": "Filter messages...",
"chat.image_alt": "Image",
"chat.image_preview": "Image Preview",
"chat.image_preview_alt": "Preview",
"chat.input_ph": "Type a message...",
"chat.loading_messages": "Loading messages...",
"chat.mentions.none": "No contacts found",
"chat.msg.analyzer_title": "View in Analyzer",
"chat.msg.block_title": "Block {name}",
"chat.msg.echo_title": {
"one": "Heard by {count} repeater: {paths}",
"other": "Heard by {count} repeaters: {paths}"
},
"chat.msg.ignore_title": "Ignore {name}",
"chat.msg.map_title": "Show on map",
"chat.msg.quote_title": "Quote",
"chat.msg.raw_resend_title": "Resend (rebroadcast same packet so unreached repeaters can pick it up)",
"chat.msg.reply_title": "Reply",
"chat.region_none": "Region --",
"chat.region_set_title": "Click to set a region for this channel",
"chat.region_title": "Click to change region for this channel",
"chat.route": "Route: {route}",
"chat.route_analyzer_title": "Show this route on the Path Analyzer map",
"chat.route_delivered": "received",
"chat.route_delivered_title": "The copy your node received and decoded. The other copies arrived later and were dropped as duplicates",
"chat.route_direct": "Direct",
"chat.route_hash_bytes": {
"one": "{count} byte",
"other": "{count} bytes"
},
"chat.route_hash_bytes_short": "{count}B",
"chat.route_hash_title": "Per-hop hash size of the route. Tap to list every route this message was heard on",
"chat.route_hops": "Hops: {count}",
"chat.route_multi": "Route ({count}): {route}",
"chat.route_region": "Region: {name}",
"chat.scroll_bottom_title": "Scroll to bottom",
"chat.title": "Chat",
"chat.toast.load_error": "Error loading messages: {error}",
"chat.toast.load_failed": "Failed to load messages",
"chat.toast.load_timeout": "Loading messages timed out — retrying...",
"chat.toast.refresh_failed": "Refresh failed",
"chat.toast.refreshed": "Messages refreshed",
"chat.toast.resend_failed": "Resend failed: {error}",
"chat.toast.resend_network_error": "Resend network error: {error}",
"chat.toast.resent": "Resent ({bytes} B) — waiting for echoes…",
"chat.toast.send_error": "Failed to send message",
"chat.toast.send_failed": "Failed to send: {error}",
"chat.toast.sent": "Message sent",
"chat.unnamed_channel": "Channel {index}",
"chat.unread_divider": "New messages",
"chat.updated": "Updated: {time}",
"common.add": "Add",
"common.apply": "Apply",
"common.back": "Back",
"common.cancel": "Cancel",
"common.cannot_undo": "This action cannot be undone.",
"common.close": "Close",
"common.connected": "Connected",
"common.connecting": "Connecting...",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.copy_failed": "Copy failed",
"common.copy_route": "Copy route",
"common.days_ago": {
"one": "{count}d ago",
"other": "{count}d ago"
},
"common.days_ago_long": {
"one": "{count} day ago",
"other": "{count} days ago"
},
"common.delete": "Delete",
"common.deleting": "Deleting...",
"common.disabled": "Disabled",
"common.disconnected": "Disconnected",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error.not_json": "The app did not answer — something in front of it did (HTTP {status}). Reload the page, and check the connection if it keeps happening.",
"common.error.not_json_auth": "Your session has expired — a login page in front of the app answered instead of the app (HTTP {status}). Reload the page and sign in again.",
"common.failed_with": "Failed: {error}",
"common.hours_ago": "{count}h ago",
"common.hours_ago_long": {
"one": "{count} hour ago",
"other": "{count} hours ago"
},
"common.just_now": "just now",
"common.load_failed": "Failed to load",
"common.loading": "Loading...",
"common.minutes_ago": "{count} min ago",
"common.minutes_ago_long": {
"one": "{count} minute ago",
"other": "{count} minutes ago"
},
"common.months_ago": {
"one": "{count} month ago",
"other": "{count} months ago"
},
"common.move_down": "Move down",
"common.move_up": "Move up",
"common.name": "Name",
"common.network_error": "Network error",
"common.network_error_detail": "Network error: {error}",
"common.never": "Never",
"common.no": "No",
"common.refresh": "Refresh",
"common.remove": "Remove",
"common.save": "Save",
"common.save_failed": "Save failed",
"common.saving": "Saving...",
"common.settings": "Settings",
"common.share": "Share",
"common.try_again": "Try again",
"common.unknown": "Unknown",
"common.unknown_error": "Unknown error",
"common.warning": "Warning:",
"common.years_ago": {
"one": "{count} year ago",
"other": "{count} years ago"
},
"common.yes": "Yes",
"common.yesterday": "Yesterday",
"console.clear_title": "Clear output history",
"console.connect_failed": "Failed to connect: {error}",
"console.error_prefix": "Error: {error}",
"console.help_hint": "Type 'help' for available commands.",
"console.history_empty": "No commands in history",
"console.history_title": "Command history",
"console.input_ph": "Enter command...",
"console.no_output": "(no output)",
"console.scroll_bottom_aria": "Scroll to latest",
"console.scroll_bottom_title": "Scroll to latest",
"console.title": "Console",
"contacts.active_recent": "Active (advert received recently)",
"contacts.add.adding": "Adding contact...",
"contacts.add.failed": "Failed to add contact.",
"contacts.add.key": "Key:",
"contacts.add.key_count": "{n} / 64 characters",
"contacts.add.name": "Name:",
"contacts.add.name_ph": "Contact name",
"contacts.add.preview": "Preview:",
"contacts.add.pubkey": "Public Key (64 hex chars):",
"contacts.add.pubkey_ph": "e.g. a1b2c3d4...",
"contacts.add.qr_invalid_uri": "QR code does not contain a valid meshcore:// URI.",
"contacts.add.qr_label": "Or upload a QR code image:",
"contacts.add.scanned": "Scanned:",
"contacts.add.start_camera": "Start Camera",
"contacts.add.stop_camera": "Stop Camera",
"contacts.add.success": "Contact added successfully!",
"contacts.add.tab_manual": "Manual",
"contacts.add.title": "Add Contact",
"contacts.add.type": "Contact Type:",
"contacts.add.type_short": "Type:",
"contacts.add.uri_hint": "Paste a meshcore:// URI from the MeshCore mobile app",
"contacts.add.uri_label": "MeshCore URI:",
"contacts.add.uri_ph": "meshcore://contact/add?name=...&public_key=...&type=...",
"contacts.blocked_by_name": "Blocked by name",
"contacts.btn.approve": "Approve",
"contacts.btn.approve_all": "Approve All",
"contacts.btn.block": "Block",
"contacts.btn.ignore": "Ignore",
"contacts.btn.ignore_all": "Ignore All",
"contacts.btn.protect": "Protect",
"contacts.btn.protected": "Protected",
"contacts.btn.to_cache": "To cache",
"contacts.btn.to_cache_title": "Remove from device, keep in cache",
"contacts.btn.to_device": "To device",
"contacts.btn.to_device_title": "Add this contact to the device",
"contacts.btn.unblock": "Unblock",
"contacts.btn.unignore": "Unignore",
"contacts.cache_badge": "Cache",
"contacts.cache_badge_title": "Not on device - type unknown",
"contacts.card.add_desc": "Add from URI, QR code, or manual entry",
"contacts.card.existing_desc": "Manage all stored contacts",
"contacts.card.pending_desc": "Contacts awaiting manual approval",
"contacts.cleanup.auto_enabled": "Enabled (runs daily at {time} {tz})",
"contacts.cleanup.completed": "Cleanup completed: {count} deleted",
"contacts.cleanup.completed_failed": ", {count} failed",
"contacts.cleanup.confirm": "Remove all contacts inactive for more than {hours} hours?",
"contacts.cleanup.confirm_title": "Confirm Contact Cleanup",
"contacts.cleanup.date_field": "Date Field:",
"contacts.cleanup.days": "Days of Inactivity (0 = ignore):",
"contacts.cleanup.days_hint": "Contacts inactive for more than this many days will be selected",
"contacts.cleanup.delete_btn": "Delete Contacts",
"contacts.cleanup.delete_confirm": "The following <span id=\"cleanupContactCount\">0</span> contact(s) will be permanently deleted:",
"contacts.cleanup.enable": "Enable Auto-Cleanup",
"contacts.cleanup.enable_hint": "When enabled, contacts matching the above criteria will be automatically deleted daily at the specified hour",
"contacts.cleanup.error": "Cleanup failed",
"contacts.cleanup.failed": "Cleanup failed: {error}",
"contacts.cleanup.last_advert": "Last Advert",
"contacts.cleanup.last_modified": "Last Modified",
"contacts.cleanup.name_filter": "Name Filter (optional):",
"contacts.cleanup.name_filter_ph": "Enter partial name to search...",
"contacts.cleanup.network_error": "Network error during cleanup",
"contacts.cleanup.preview": "Preview Cleanup",
"contacts.cleanup.run_at": "Run at:",
"contacts.cleanup.status_label": "Status:",
"contacts.cleanup.title": "Cleanup Contacts",
"contacts.cleanup.title_hint": "Remove contacts based on various filter criteria",
"contacts.cleanup.types": "Contact Types:",
"contacts.cleanup.undo_warning": "This action cannot be undone!",
"contacts.confirm.block": "Block this contact? Their messages will be hidden from chat.",
"contacts.confirm.push": "Push \"{name}\" to device?",
"contacts.confirm.to_cache": "Move \"{name}\" from device to cache?",
"contacts.copy_title": "Click to copy",
"contacts.delete.confirm": "Delete Contact",
"contacts.delete.question": "Are you sure you want to delete this contact?",
"contacts.delete.title": "Confirm Delete",
"contacts.existing.load_failed": "Failed to load contacts",
"contacts.existing.loading": "Loading contacts...",
"contacts.existing.none": "No contacts found",
"contacts.existing.none_hint": "Contacts will appear here once added",
"contacts.existing.search_ph": "Search by name or public key...",
"contacts.existing.title": "Existing Contacts",
"contacts.filter.advanced": "Advanced Filters",
"contacts.filter.all_sources": "All sources",
"contacts.filter.all_types": "All Types",
"contacts.filter.blocked": "Blocked",
"contacts.filter.cache_only": "Cache only",
"contacts.filter.ignored": "Ignored",
"contacts.filter.no_match": "No contacts match filters",
"contacts.filter.no_match_hint": "Try changing your filter criteria",
"contacts.filter.on_device": "On device",
"contacts.hops": {
"one": "{count} hop",
"other": "{count} hops"
},
"contacts.inactive": "Inactive",
"contacts.last_advert": "Last advert: {time}",
"contacts.last_advert_unknown": "Last advert: Unknown",
"contacts.manage.title": "Contact Management Settings",
"contacts.map.show": "Show:",
"contacts.map.title": "Map",
"contacts.never_seen": "Never seen",
"contacts.pending.approve_confirm": "You are about to approve <strong id=\"batchApprovalCount\">0</strong> contacts:",
"contacts.pending.approve_note": "These contacts will be added to your device and can receive/send messages.",
"contacts.pending.confirm_approve_title": "Confirm Batch Approval",
"contacts.pending.confirm_ignore_title": "Confirm Batch Ignore",
"contacts.pending.filter_hint": "Filter contacts by type or name, then approve or ignore them in bulk.",
"contacts.pending.ignore_confirm": "You are about to ignore <strong id=\"batchIgnoreCount\">0</strong> contacts:",
"contacts.pending.ignore_note": "Ignored contacts will not trigger new pending requests. You can unignore them later from the Existing Contacts page.",
"contacts.pending.load_failed": "Failed to load pending contacts",
"contacts.pending.loading": "Loading pending contacts...",
"contacts.pending.none": "No pending requests",
"contacts.pending.none_hint": "New contact requests will appear here when manual approval is enabled",
"contacts.pending.title": "Pending Contacts",
"contacts.progress.approving": "Approving {done}/{total}...",
"contacts.progress.ignoring": "Ignoring {done}/{total}...",
"contacts.protected_cannot_block": "Cannot block protected contact",
"contacts.protected_cannot_delete": "Cannot delete protected contact",
"contacts.protected_cannot_ignore": "Cannot ignore protected contact",
"contacts.protected_cannot_move": "Cannot move protected contact",
"contacts.protected_title": "Protected contact",
"contacts.qr.camera_denied": "Camera access denied or not available. Try uploading an image instead.",
"contacts.qr.read_failed": "Could not read QR code from image. Make sure the image contains a valid QR code.",
"contacts.recent_activity": "Recent activity",
"contacts.sort.advert_asc": "Last advert ↑",
"contacts.sort.advert_desc": "Last advert ↓",
"contacts.sort.name_asc": "Name A→Z",
"contacts.sort.name_desc": "Name Z→A",
"contacts.title": "Contact Management",
"contacts.toast.approve_failed": "Failed to approve: {error}",
"contacts.toast.approved": "Approved: {name}",
"contacts.toast.bulk_approve_failed": "Failed to approve contacts. Check console for details.",
"contacts.toast.bulk_approve_partial": "Approved {ok}, failed {failed}. Check console for details.",
"contacts.toast.bulk_approved": {
"one": "Successfully approved {count} contact",
"other": "Successfully approved {count} contacts"
},
"contacts.toast.bulk_ignore_failed": "Failed to ignore contacts. Check console for details.",
"contacts.toast.bulk_ignore_partial": "Ignored {ok}, failed {failed}. Check console for details.",
"contacts.toast.bulk_ignored": {
"one": "Successfully ignored {count} contact",
"other": "Successfully ignored {count} contacts"
},
"contacts.toast.cleanup_failed": "Cleanup failed: {error}",
"contacts.toast.copy_failed": "Failed to copy to clipboard",
"contacts.toast.delete_failed": "Failed to delete: {error}",
"contacts.toast.deleted": "Deleted: {name}",
"contacts.toast.key_copied": "Key copied to clipboard",
"contacts.toast.move_failed": "Failed to move contact",
"contacts.toast.need_days": "Set \"Days of Inactivity\" > 0 before enabling auto-cleanup",
"contacts.toast.need_type": "Please select at least one contact type",
"contacts.toast.need_type_enable": "Select at least one contact type before enabling auto-cleanup",
"contacts.toast.no_match": "No contacts match the selected criteria",
"contacts.toast.none_to_approve": "No contacts to approve",
"contacts.toast.none_to_ignore": "No contacts to ignore",
"contacts.toast.preview_failed": "Preview failed: {error}",
"contacts.toast.preview_network_error": "Network error during preview",
"contacts.toast.protect_failed": "Failed to update protection: {error}",
"contacts.toast.pubkey_copied": "Public key copied to clipboard",
"contacts.toast.push_failed": "Failed to push contact",
"contacts.toast.pushed": "{name} pushed to device",
"contacts.toast.settings_load_error": "Error loading settings",
"contacts.toast.settings_network_error": "Network error saving settings",
"contacts.toast.settings_save_failed": "Failed to save settings: {error}",
"contacts.toast.to_cache": "{name} moved to cache",
"coord.confirm": "Confirm",
"coord.hint": "Click on the map to select coordinates",
"coord.title": "Pick Coordinates",
"demo.badge": "Demo",
"demo.blocked_toast": "This instance runs in demo mode — the action is disabled.",
"demo.console_notice": "Demo mode: the console answers read-only commands such as <code>infos</code>, <code>stats</code>, <code>contacts</code> and <code>get</code>. Commands that change the device are refused.",
"demo.lock_btn": "Lock again",
"demo.locked_title": "Disabled in demo mode",
"demo.settings_notice": "This is a public demo instance, so settings that change the radio, the stored data or this server are read-only. Everything else — reading, sending messages, themes — works normally.",
"demo.unlock_bad": "That code was not accepted.",
"demo.unlock_btn": "Unlock",
"demo.unlock_hint": "If you administer this instance, enter the unlock code to restore full access on this device.",
"demo.unlock_label": "Operator access",
"demo.unlock_ph": "Unlock code",
"demo.unlocked_state": "Unlocked — you have full access on this device.",
"device.cmd.exec_failed": "Failed to execute {command}",
"device.cmd.failed": "Command failed: {error}",
"device.cmd.sent": "{command} sent successfully",
"device.info.bw": "Bandwidth",
"device.info.copy_title": "Copy to clipboard",
"device.info.cr": "Coding Rate",
"device.info.freq": "Frequency",
"device.info.load_failed": "Failed to load device info",
"device.info.loc_sharing": "Location Sharing",
"device.info.location": "Location",
"device.info.location_none": "Not available",
"device.info.manual_add": "Manual Add Contacts",
"device.info.multi_acks": "Multi Acks",
"device.info.name": "Name",
"device.info.none": "No device info available",
"device.info.pubkey": "Public Key",
"device.info.sf": "Spreading Factor",
"device.info.tx_power": "TX Power",
"device.info.type": "Type",
"device.info.type_unknown": "Unknown ({code})",
"device.reboot.btn": "Reboot",
"device.reboot.confirm": "Reboot the device?\n\nIt drops off the mesh for a few seconds while it restarts. Messages sent to you meanwhile may be lost. The app reconnects on its own.",
"device.reboot.failed": "Reboot failed",
"device.reboot.hint": "Restarts the device. It is offline for a few seconds.",
"device.reboot.sent": "Reboot command sent — the device is restarting",
"device.share.copy_uri": "Copy URI",
"device.share.hint": "Share this QR code or URI so others can add your device as a contact.",
"device.share.unavailable": "Device info not available",
"device.share.uri": "Contact URI:",
"device.share_hint": "Click to generate share code",
"device.stats.battery": "Battery",
"device.stats.channel_msgs": "Channel Msgs",
"device.stats.contacts_db": "Contacts (DB)",
"device.stats.database": "Database",
"device.stats.db_size": "DB Size",
"device.stats.direct_msgs": "Direct Msgs",
"device.stats.errors": "Errors",
"device.stats.load_failed": "Failed to load stats",
"device.stats.none": "No statistics available",
"device.stats.packets_rx": "Packets RX",
"device.stats.packets_tx": "Packets TX",
"device.stats.queue": "Queue",
"device.stats.rx_air": "RX Air Time",
"device.stats.tx_air": "TX Air Time",
"device.stats.uptime": "Uptime",
"device.stats_hint": "Click to load stats",
"device.tab.info": "Info",
"device.tab.share": "Share",
"device.tab.stats": "Stats",
"device.title": "Device",
"diag.confirm.delete": "Delete this capture?",
"diag.confirm.upload": "Send this capture to the configured share service? It contains the messages received while it was recording.",
"diag.counters": "<strong>{elapsed}</strong> of {total} &middot; {events} events &middot; {size}",
"diag.download": "Download",
"diag.dropped": "({n} events dropped)",
"diag.load_failed": "Failed to load diagnostics status",
"diag.minutes": "{n} min",
"diag.no_captures": "No captures yet.",
"diag.toast.delete_failed": "Failed to delete capture",
"diag.toast.no_token": "Save an upload token first",
"diag.toast.settings_failed": "Failed to save sharing settings",
"diag.toast.settings_saved": "Sharing settings saved",
"diag.toast.start_failed": "Failed to start the capture",
"diag.toast.started": "Recording started",
"diag.toast.stop_failed": "Failed to stop the capture",
"diag.toast.stopped": "Recording stopped — the capture is ready below",
"diag.toast.upload_failed": "Upload failed",
"diag.toast.uploaded": "Capture sent — the link is shown below",
"diag.token_missing": "No token saved. Ask the maintainer for one when they request a capture.",
"diag.token_set": "A token is saved. Leave the field blank to keep it.",
"diag.upload": "Send to maintainer",
"dm.attempt": "Attempt {n}/{max}",
"dm.auto_retry": "Auto Retry",
"dm.auto_retry_title": "Auto Retry: resend DM if no ACK received",
"dm.clear_selection_title": "Clear selection",
"dm.contact_info": "Contact Info",
"dm.contact_info_title": "Contact info",
"dm.copy_pubkey_title": "Click to copy full public key",
"dm.delivery_unknown": "Delivery unknown — no ACK received. Message may still have been delivered.",
"dm.dropdown.contacts": "Contacts",
"dm.dropdown.no_contacts": "No contacts available",
"dm.dropdown.no_matches": "No matches",
"dm.dropdown.recent": "Recent conversations",
"dm.empty.no_messages_hint": "Send a message to start the conversation",
"dm.empty.select": "Select a conversation",
"dm.empty.select_hint": "Choose from the list or start a new chat from channel messages",
"dm.import_device_path_title": "Import device path to configured paths",
"dm.keep_path": "Keep path",
"dm.keep_path_title": "Keep path: don't auto-reset to FLOOD after failed retries",
"dm.load_messages_error": "Error loading messages",
"dm.msg_input_ph": "Message {name}...",
"dm.no_contact_info": "No contact information available.",
"dm.select_chat_ph": "Select chat...",
"dm.sidebar.search_ph": "Search contacts...",
"dm.status.delivered": "Delivered",
"dm.status.failed": "Delivery failed — all retries exhausted",
"dm.status.sending": "Sending...",
"dm.title": "Direct Messages",
"dm.toast.auto_retry_off": "Auto Retry disabled",
"dm.toast.auto_retry_on": "Auto Retry enabled",
"dm.toast.device_path_imported": "Device path imported",
"dm.toast.import_failed": "Import failed",
"dm.toast.keep_path_off": "Keep path disabled",
"dm.toast.keep_path_on": "Keep path enabled",
"logs.clear_title": "Clear display",
"logs.entries": {
"one": "{count} entry",
"other": "{count} entries"
},
"logs.entries_filtered": {
"one": "{shown} / {count} entry",
"other": "{shown} / {count} entries"
},
"logs.filter.all_levels": "All levels",
"logs.filter.all_modules": "All modules",
"logs.filter.reset_title": "Reset filters",
"logs.filter.search_ph": "Search...",
"logs.load_failed": "Failed to load logs",
"logs.loading": "Loading logs...",
"logs.pause_title": "Pause/Resume",
"logs.title": "System Log",
"map.all_contacts": "All Contacts",
"map.cache": "Cache",
"map.cached_label": "{type} (cached)",
"map.own_device": "Own device",
"map.this_device": "This device",
"menu.advert": "Send Advert",
"menu.advert_desc": "Announce presence (normal)",
"menu.advert_title": "Send single advertisement (recommended for normal operation)",
"menu.archive_option": "{date} ({count} msgs)",
"menu.backup": "Backup",
"menu.backup_desc": "Database backup & restore",
"menu.channels": "Manage Channels",
"menu.check_updates_title": "Check for updates",
"menu.confirm.flood_advert": "Flood Advertisement uses high airtime and should only be used for network recovery.\n\nAre you sure you want to proceed?",
"menu.console_desc": "Direct meshcli commands",
"menu.contacts_desc": "Manage known contacts",
"menu.device_info": "Device Info",
"menu.dm_desc": "Private conversations",
"menu.exact_build_title": "Exact build",
"menu.filter": "Filter messages",
"menu.filter_desc": "Filter current chat",
"menu.flood_advert": "Flood Advert",
"menu.flood_advert_desc": "Network recovery only!",
"menu.flood_advert_title": "Flood advertisement - use sparingly! High airtime usage.",
"menu.history": "Message History",
"menu.logs_desc": "Real-time application logs",
"menu.map": "Map",
"menu.map_desc": "All contacts with GPS",
"menu.map_title": "Show all contacts with GPS on map",
"menu.pa_desc": "Routing path analysis",
"menu.refresh_desc": "Reload messages from server",
"menu.refresh_title": "Reload messages from the server",
"menu.repeaters_desc": "Repeater administration",
"menu.search": "Search messages",
"menu.search_desc": "Search all messages",
"menu.section.network": "Network",
"menu.section.system": "System",
"menu.section.tools": "Tools",
"menu.select_date_title": "Select date",
"menu.settings_desc": "Application settings",
"menu.title": "Menu",
"menu.today_live": "Today (Live)",
"meta.language_english_name": "English",
"meta.language_name": "English",
"meta.translator": "mc-webui",
"nav.ble_title": "Bluetooth Low Energy",
"nav.mark_all_read_title": "Mark all as read",
"nav.select_channel_title": "Select channel",
"nav.tcp_title": "TCP connection",
"notify.channels": {
"one": "{count} channel",
"other": "{count} channels"
},
"notify.dms": {
"one": "{count} private message",
"other": "{count} private messages"
},
"notify.new": "New: {parts}",
"notify.pending": {
"one": "{count} pending contact",
"other": "{count} pending contacts"
},
"notify.title_count": "{name} ({count})",
"observer.add_broker": "Add broker",
"observer.confirm.delete": "Delete broker \"{name}\"?",
"observer.counters": "packets captured: <strong>{seen}</strong>, published: <strong>{published}</strong>",
"observer.edit_broker": "Edit broker",
"observer.empty": "No brokers configured. Click \"Add broker\" to add one.",
"observer.host": "Host",
"observer.host_required": "Host is required",
"observer.iata_invalid": "Location code must be empty or exactly 3 letters",
"observer.load_failed": "Failed to load observer status",
"observer.name_ph": "e.g. My MQTT server",
"observer.name_required": "Name is required",
"observer.off": "Observer is <strong>off</strong>.",
"observer.password": "Password",
"observer.password_hint": "Leave the password blank to keep the current one.",
"observer.port": "Port",
"observer.port_invalid": "Port must be 1-65535",
"observer.ports_hint": "Typical ports: 1883 (plain), 8883 (TLS).",
"observer.state.connected": "connected",
"observer.state.error": "error",
"observer.state.offline": "offline",
"observer.state.running": "running",
"observer.state.waiting": "waiting",
"observer.toast.delete_error": "Network error deleting broker",
"observer.toast.delete_failed": "Failed to delete broker",
"observer.toast.save_error": "Network error saving broker",
"observer.toast.save_failed": "Failed to save broker",
"observer.toast.settings_error": "Network error saving observer settings",
"observer.toast.settings_failed": "Failed to save observer settings",
"observer.toast.update_error": "Network error updating broker",
"observer.toast.update_failed": "Failed to update broker",
"observer.use_tls": "Use TLS",
"observer.username": "Username",
"observer.verify_tls": "Verify TLS certificate",
"pa.ambiguous": "ambiguous ({count})",
"pa.any_hb": "Any HB",
"pa.any_hops": "Any hops",
"pa.byte_2_3": "2/3-byte",
"pa.candidates": {
"one": "{count} candidate",
"other": "{count} candidates"
},
"pa.clear": "Clear",
"pa.clear_filters_title": "Clear filters",
"pa.col.avgsnr": "Avg SNR (last hop)",
"pa.col.avgsnr_title": "Average echo SNR, counted only when this hash is the final hop - SNR is measured at our receiver for the last hop",
"pa.col.channel": "Channel",
"pa.col.contact": "Contact",
"pa.col.echoes": "Echoes",
"pa.col.echoes_seg_title": "Echoes whose path contains this segment (anywhere)",
"pa.col.hash": "Hash",
"pa.col.hb": "HB",
"pa.col.hb_title": "Path hash size in bytes per hop (from routed echoes)",
"pa.col.hops": "Hops",
"pa.col.lasthop": "As last hop",
"pa.col.lasthop_title": "Echoes where this hash is the final hop (heard directly by us)",
"pa.col.message": "Message",
"pa.col.message_plural": "Messages",
"pa.col.msgs_hash_title": "Distinct messages relayed through this hash",
"pa.col.msgs_seg_title": "Distinct messages whose path contains this segment",
"pa.col.pathend": "As path end",
"pa.col.pathend_title": "Echoes where this segment is the end of the path - the route the message took to reach us",
"pa.col.relayed": "Relayed",
"pa.col.relayed_title": "Echoes whose path contains this hash (anywhere)",
"pa.col.repeater": "Repeater",
"pa.col.route": "Route",
"pa.col.sender": "Sender",
"pa.col.time": "Time",
"pa.content_ph": "Message text",
"pa.content_title": "Filter by message content",
"pa.copy_hash_title": "Copy repeater hash {hash}",
"pa.copy_route_title": "Click to copy route",
"pa.copy_title": "Click to copy",
"pa.counter.filtered": "{shown} of {total} messages",
"pa.counter.messages": {
"one": "{count} message",
"other": "{count} messages"
},
"pa.counter.repeaters": {
"one": "{count} repeater",
"other": "{count} repeaters"
},
"pa.counter.routed": {
"one": "{count} routed message",
"other": "{count} routed messages"
},
"pa.counter.segments": {
"one": "{count} segment",
"other": "{count} segments"
},
"pa.direct_flood": "Direct (flood, 0 hops)",
"pa.empty_filtered": "No messages match the current filters.",
"pa.empty_no_echoes": "No routed echoes in the current selection.",
"pa.empty_range": "No messages in the selected time range.",
"pa.filter_by_segment_title": "Filter messages by this segment",
"pa.filter_by_title": "Filter messages by {token}",
"pa.filters": "Filters",
"pa.filters_toggle_title": "Show/hide filters",
"pa.hb_filter_title": "Filter by path hash size (bytes per hop, any routed echo)",
"pa.hops_4plus": "4+ hops",
"pa.hops_filter_title": "Filter by hop count (any echo)",
"pa.last_days": {
"one": "Last {count} day",
"other": "Last {count} days"
},
"pa.loading": "Loading messages…",
"pa.map.alt_path": "Alternative path",
"pa.map.candidate": "Candidate for hop {n}: <code>{token}</code>",
"pa.map.clear_title": "Clear drawn path",
"pa.map.exit_fullscreen": "Exit full screen",
"pa.map.fullscreen": "Maximize the map",
"pa.map.hint": "Click a message, then an echo to draw its path.",
"pa.map.hop": "Hop {n}: <code>{token}</code>",
"pa.map.no_routed": "No messages with routed echoes match the current filters.",
"pa.map.origin": "Origin (sender)",
"pa.me": "Me",
"pa.n_byte": {
"one": "{count}-byte",
"other": "{count}-byte"
},
"pa.n_hops": {
"one": "{count} hop",
"other": "{count} hops"
},
"pa.no_path_data": "no path data",
"pa.no_paths_min_hops": "No paths with at least {count} hops in the current selection.",
"pa.packet_hash": "Packet hash",
"pa.reload_title": "Reload",
"pa.repeater_hash": "Repeater hash",
"pa.reset_picks": "Reset picks",
"pa.reset_picks_title": "Clear all manual repeater assignments on this path",
"pa.seglen_title": "Number of consecutive hops per counted segment",
"pa.segment_length": "Segment length:",
"pa.sender_own": "{name} (own)",
"pa.sender_ph": "Sender",
"pa.sender_title": "Filter by sender name",
"pa.show_on_map_title": "Show this path on the map",
"pa.time_range": "Time range:",
"pa.title": "Path Analyzer",
"pa.toast.copied": "{what} copied to clipboard",
"pa.toast.copy_failed": "Failed to copy {what}",
"pa.toast.load_failed": "Failed to load messages: {error}",
"pa.toast.msg_gone": "This message is no longer in the analyzer data (max 7 days).",
"pa.toast.no_echoes": "This message has no routed echoes to draw.",
"pa.toggle_all_repeaters": "All repeaters",
"pa.toggle_alt_paths": "Alternative paths",
"pa.token_ph": "Repeater (3B or name)",
"pa.token_title": "Match a repeater anywhere in a path: hash prefix (hex) or contact name. Chain with '>' for a consecutive sequence, e.g. AFE6>6E9A",
"pa.undo_pick_title": "Undo this manual assignment",
"pa.unknown_position": "— unknown / no position",
"pa.use_contact_title": "Use this contact for the hop",
"pa.view.map": "Map",
"pa.view.map_title": "Map view",
"pa.view.messages": "Messages",
"pa.view.messages_title": "Message list",
"pa.view.repeaters": "Repeaters",
"pa.view.repeaters_title": "Per-repeater statistics",
"pa.view.routes": "Routes",
"pa.view.routes_title": "Route segment statistics",
"pa.view_aria": "View",
"quote.full": "Full quote",
"quote.max_bytes": "Max bytes:",
"quote.title": "Quote message",
"quote.truncated": "Truncated",
"repeaters.add": "Add repeater",
"repeaters.add_loading": "Loading device contacts...",
"repeaters.add_modal_title": "Add Repeater",
"repeaters.add_none_found": "No repeaters found on the device.",
"repeaters.add_search_ph": "Search repeaters on device...",
"repeaters.added_badge": "Added",
"repeaters.addpath.by_id": "ID",
"repeaters.addpath.by_name": "Name",
"repeaters.addpath.hash_1b": "1B (max 64)",
"repeaters.addpath.hash_2b": "2B (max 32)",
"repeaters.addpath.hash_3b": "3B (max 21)",
"repeaters.addpath.hash_size": "Hash Size",
"repeaters.addpath.label": "Label (optional)",
"repeaters.addpath.label_ph": "e.g. via Mountain RPT",
"repeaters.addpath.path_hex": "Path (hex)",
"repeaters.addpath.path_hex_ph": "e.g. 5e,e7 or 5e34,e761",
"repeaters.addpath.pick_list_title": "Pick repeater from list",
"repeaters.addpath.pick_map_title": "Pick repeater from map",
"repeaters.addpath.search_id_ph": "Search by first {chars} hex chars...",
"repeaters.addpath.search_ph": "Search by name...",
"repeaters.addpath.title": "Add Path",
"repeaters.confirm.clear_paths": "Clear all configured paths?\n\nThis will delete all paths from the database. The device path will not be changed.",
"repeaters.confirm.reset_flood": "Reset device path to FLOOD?\n\nThis resets the path on the device only. Your configured paths will be kept.",
"repeaters.confirm.set_direct": "Set device path to Direct?\n\nMessages will go straight to the node, with no repeater in between. This changes the path on the device only — your configured paths will be kept.",
"repeaters.count": {
"one": "{count} repeater",
"other": "{count} repeaters"
},
"repeaters.empty": "No repeaters yet.",
"repeaters.empty_hint": "Use <strong>Add repeater</strong> to pick repeaters stored on your device.",
"repeaters.empty_note": "Only repeaters saved in the device contacts can be managed.",
"repeaters.load_failed": "Failed to load repeaters",
"repeaters.map.cached": "Cached",
"repeaters.map.hint": "Click a repeater on the map",
"repeaters.map.last_seen": "Last seen: {time}",
"repeaters.map.title": "Select Repeater from Map",
"repeaters.on_device_count": {
"one": "{count} repeater on device",
"other": "{count} repeaters on device"
},
"repeaters.password.change_title": "Change password",
"repeaters.password.input_ph": "Repeater password",
"repeaters.password.login_btn": "Log in",
"repeaters.password.login_info": "Enter the repeater password to log in.",
"repeaters.password.login_title": "Log in",
"repeaters.password.remember": "Remember password (stored in the app database)",
"repeaters.password.retry_hint": "Check the password and try again.",
"repeaters.password.set_info": "The password is stored in the app database and used to log in to this repeater without asking again.",
"repeaters.password.set_title": "Set password",
"repeaters.password.toggle_title": "Show/hide password",
"repeaters.paths.add_title": "Add path",
"repeaters.paths.apply_title": "Set as device path",
"repeaters.paths.clear": "Clear Paths",
"repeaters.paths.clear_title": "Delete all configured paths from database",
"repeaters.paths.configured": "Configured paths",
"repeaters.paths.device_path": "Device path:",
"repeaters.paths.is_primary_title": "Primary path",
"repeaters.paths.load_failed": "Failed to load paths",
"repeaters.paths.none": "No paths configured. Use + to add.",
"repeaters.paths.reset_flood": "Reset to FLOOD",
"repeaters.paths.reset_flood_title": "Reset device path to FLOOD mode",
"repeaters.paths.set_direct": "Set Direct",
"repeaters.paths.set_direct_title": "Set device path to Direct (no repeaters)",
"repeaters.paths.set_primary_title": "Set as primary",
"repeaters.paths.title": "Paths",
"repeaters.picker.none": "No repeaters found",
"repeaters.picker.shared_prefix": {
"one": "{count} repeater shares this prefix",
"other": "{count} repeaters share this prefix"
},
"repeaters.refresh_title": "Refresh list",
"repeaters.remove.note": "The saved password will be deleted. The contact on the device is not affected.",
"repeaters.remove.question": "Remove <strong>{name}</strong> from My Repeaters?",
"repeaters.remove.title": "Remove Repeater",
"repeaters.row.last_login": "last login: {role}",
"repeaters.row.logging_in": "Logging in… (may take up to 60 s on flood paths)",
"repeaters.row.not_on_device": "Not stored on the device",
"repeaters.row.remove_title": "Remove from list",
"repeaters.row.set_path_title": "Set path",
"repeaters.term": "repeater",
"repeaters.title": "My Repeaters",
"repeaters.toast.add_failed": "Failed to add repeater",
"repeaters.toast.added": "{name} added",
"repeaters.toast.adjacent_dupes": {
"one": "Adjacent duplicate hop: {hops}",
"other": "Adjacent duplicate hops: {hops}"
},
"repeaters.toast.ambiguous_prefix": {
"one": "⚠ Ambiguous prefix: {hops}. Consider using a larger hash size.",
"other": "⚠ Ambiguous prefixes: {hops}. Consider using a larger hash size."
},
"repeaters.toast.clear_failed": "Clear failed",
"repeaters.toast.contacts_load_failed": "Failed to load device contacts",
"repeaters.toast.dupes": {
"one": "Duplicate hop: {hops}",
"other": "Duplicate hops: {hops}"
},
"repeaters.toast.hop_adjacent_self": "{hop} cannot be adjacent to itself",
"repeaters.toast.hop_already_used": "{hop} is already in the path",
"repeaters.toast.logged_in": "Logged in to {name} as {role}",
"repeaters.toast.login_failed": "Login failed",
"repeaters.toast.login_in_progress": "Another login is already in progress",
"repeaters.toast.not_on_device": "This repeater is not stored on the device — it cannot be managed",
"repeaters.toast.password_empty": "Password cannot be empty",
"repeaters.toast.password_save_failed": "Failed to save password",
"repeaters.toast.password_saved": "Password saved",
"repeaters.toast.path_add_failed": "Failed to add path",
"repeaters.toast.path_added": "Path added",
"repeaters.toast.path_duplicate": "This path is already on the list",
"repeaters.toast.path_hex_required": "Path hex is required",
"repeaters.toast.path_set_failed": "Failed to set device path",
"repeaters.toast.path_updated": "Device path updated",
"repeaters.toast.paths_cleared": {
"one": "{count} path cleared",
"other": "{count} paths cleared"
},
"repeaters.toast.remove_failed": "Failed to remove repeater",
"repeaters.toast.removed": "Repeater removed from list",
"repeaters.toast.reset_failed": "Reset failed",
"repeaters.toast.reset_flood_done": "Device path reset to FLOOD",
"repeaters.toast.set_direct_done": "Device path set to Direct",
"repeaters.toast.set_direct_failed": "Failed to set Direct path",
"rptmgmt.act.clock_sync": "Sync clock",
"rptmgmt.act.clock_sync_desc": "Set the repeater's clock from the server's current time. The firmware refuses to move the clock backwards.",
"rptmgmt.act.flood": "Send flood advert",
"rptmgmt.act.flood_desc": "Not recommended — the advert is flooded across the whole mesh (high network load).",
"rptmgmt.act.poweroff": "Power off repeater",
"rptmgmt.act.poweroff_btn": "Power off",
"rptmgmt.act.poweroff_desc": "Shuts the repeater down. It cannot be powered back on over the mesh — someone has to go to it. Requires firmware v1.17 or newer.",
"rptmgmt.act.reboot": "Reboot repeater",
"rptmgmt.act.reboot_btn": "Reboot",
"rptmgmt.act.reboot_desc": "The repeater drops off the mesh for a few seconds while it restarts.",
"rptmgmt.act.send": "Send",
"rptmgmt.act.sync": "Sync",
"rptmgmt.act.zerohop": "Send zero-hop advert",
"rptmgmt.act.zerohop_desc": "Announce this repeater to its direct neighbours only.",
"rptmgmt.action_failed": "Action failed",
"rptmgmt.admin_required": "Admin login required",
"rptmgmt.ago_dh": "{d}d {h}h ago",
"rptmgmt.ago_hm": "{h}h {m}m ago",
"rptmgmt.ago_ms": "{m}m {s}s ago",
"rptmgmt.ago_s": "{s}s ago",
"rptmgmt.back_title": "Back to My Repeaters",
"rptmgmt.back_to_list": "Back to list",
"rptmgmt.back_tools_title": "Back to tools",
"rptmgmt.cli.empty_reply": "(empty reply)",
"rptmgmt.cli.input_ph": "Enter command (e.g. get name)",
"rptmgmt.cli.intro": "Commands go to {name}. One command at a time — replies travel over the mesh.",
"rptmgmt.cli.the_repeater": "the repeater",
"rptmgmt.cli.waiting": "Waiting for reply…",
"rptmgmt.command_failed": "Command failed",
"rptmgmt.confirm.radio": "Change radio parameters to {params}?\n\nWrong values can make the repeater unreachable over the mesh. The change takes effect after a reboot.",
"rptmgmt.confirm.reboot": "Reboot {name}?\n\nIt will drop off the mesh for a few seconds. The firmware does not reply to this command.",
"rptmgmt.copy_pubkey_title": "Copy full public key",
"rptmgmt.danger_zone": "Danger zone",
"rptmgmt.done": "Done",
"rptmgmt.erase_fs_note": "Erase file system is not available over the mesh — the firmware only accepts it on the USB serial console (use the MeshCore flasher instead).",
"rptmgmt.error_generic": "Something went wrong.",
"rptmgmt.failed": "Failed",
"rptmgmt.invalid_pubkey": "Invalid repeater public key in URL.",
"rptmgmt.load_failed": "Failed to load repeater",
"rptmgmt.loc.hint": "Click the map to set the repeater location.",
"rptmgmt.loc.title": "Pick Location",
"rptmgmt.loc.use": "Use this location",
"rptmgmt.login.empty_password": "Password cannot be empty",
"rptmgmt.login.failed": "Login failed",
"rptmgmt.login.logged_in": "Logged in as {role}",
"rptmgmt.login.modal_title": "Log in — {name}",
"rptmgmt.login.progress": "Logging in to {name}… (may take up to 60 s on flood paths)",
"rptmgmt.login.prompt": "Enter the repeater password to log in.",
"rptmgmt.login.repeater_fallback": "repeater",
"rptmgmt.login.retry_hint": "Check the password and try again.",
"rptmgmt.login.waiting": "Waiting for password…",
"rptmgmt.login_request_failed": "Login request failed",
"rptmgmt.logout": "Logout",
"rptmgmt.logout_failed": "Logout failed",
"rptmgmt.logout_title": "Log out of this repeater",
"rptmgmt.managed_repeater": "managed repeater",
"rptmgmt.neigh.confirm_remove_all": "Remove all neighbours from {name}?\n\nThe list is stored on the repeater. Neighbours reappear as soon as they are heard again.",
"rptmgmt.neigh.count": {
"one": "{count} neighbor",
"other": "{count} neighbors"
},
"rptmgmt.neigh.discover": "Discover neighbours",
"rptmgmt.neigh.discover_sending": "Sending the discovery request…",
"rptmgmt.neigh.discover_sent": "Discovery request sent. Nearby repeaters can take up to 60 seconds to answer — refresh the list to see new neighbours.",
"rptmgmt.neigh.heard": "Heard",
"rptmgmt.neigh.heard_at": "Heard: {time}",
"rptmgmt.neigh.list": "List",
"rptmgmt.neigh.loading": "Requesting neighbours from the repeater…",
"rptmgmt.neigh.loading_hint": "Long lists are fetched in pages and can take a while.",
"rptmgmt.neigh.map": "Map",
"rptmgmt.neigh.menu_title": "Neighbour actions",
"rptmgmt.neigh.position": "Position",
"rptmgmt.neigh.position_title": "Position known",
"rptmgmt.neigh.pubkey": "Public key",
"rptmgmt.neigh.refresh_title": "Refresh neighbours",
"rptmgmt.neigh.remove_all": "Remove all neighbours",
"rptmgmt.neigh.remove_btn": "Remove from list",
"rptmgmt.neigh.remove_note": "The list lives on the repeater — a removed neighbour comes back as soon as it is heard again.",
"rptmgmt.neigh.removed_all": "Neighbour list cleared.",
"rptmgmt.neigh.removed_one": "{name} removed from the neighbour list.",
"rptmgmt.neigh.removing": "Removing…",
"rptmgmt.neigh.repeater": "Repeater",
"rptmgmt.neigh.row_hint": "Select a neighbour to see details.",
"rptmgmt.neigh.showing": "(showing {count})",
"rptmgmt.neighbours_failed": "Failed to get neighbours",
"rptmgmt.neighbours_none": "No neighbours reported yet.",
"rptmgmt.neighbours_none_hint": "Neighbours are learned from zero-hop repeater adverts.",
"rptmgmt.no_result": "No result",
"rptmgmt.not_on_device": "This repeater is not stored on the device — it cannot be managed.",
"rptmgmt.poweroff.no_name": "This repeater has no saved name, so the power-off confirmation cannot be completed.",
"rptmgmt.poweroff.prompt": "Type the repeater name ({name}) to confirm:",
"rptmgmt.poweroff.title": "Power off repeater",
"rptmgmt.poweroff.warning": "This cannot be undone over the mesh. The repeater will stay off until someone powers it on physically.",
"rptmgmt.pubkey_copied": "Public key copied",
"rptmgmt.read_failed": "Failed to read",
"rptmgmt.reboot_required": "reboot required",
"rptmgmt.reg.action_failed": "Region command failed",
"rptmgmt.reg.action_ok": "Done",
"rptmgmt.reg.add": "Add region",
"rptmgmt.reg.added": "Region {name} added",
"rptmgmt.reg.added_under": "Region {name} added inside {parent}",
"rptmgmt.reg.allow_flood": "Allow flood",
"rptmgmt.reg.confirm_leave": "You have region changes that have not been saved to the repeater yet.\n\nThey are active now but will be lost when it reboots. Leave without saving?",
"rptmgmt.reg.confirm_remove": "Remove region {name} from this repeater?\n\nA region that still has regions nested under it cannot be removed.",
"rptmgmt.reg.default_current": "This repeater currently scopes its own flood packets to {name}.",
"rptmgmt.reg.default_current_none": "This repeater currently sends its own flood packets unscoped.",
"rptmgmt.reg.default_help": "The region this repeater stamps on flood packets it sends itself. Only repeaters that allow that region will forward them. Saved on the repeater immediately — it does not need the Save button.",
"rptmgmt.reg.default_none": "— none (unscoped) —",
"rptmgmt.reg.default_pick_first": "Choose a default scope first, or refresh to read the current one.",
"rptmgmt.reg.default_saved": "Default scope updated",
"rptmgmt.reg.default_title": "Default scope",
"rptmgmt.reg.default_unknown": "— could not be read —",
"rptmgmt.reg.default_unread": "Could not read the default scope: {error}",
"rptmgmt.reg.deny_flood": "Deny flood",
"rptmgmt.reg.dirty_note": "Unsaved changes. They are already active, but the repeater will forget them when it reboots — use Save to keep them.",
"rptmgmt.reg.err_char": "That character is not allowed in a region name.",
"rptmgmt.reg.err_empty": "Enter a region name.",
"rptmgmt.reg.err_long": "Name too long (max {max} characters).",
"rptmgmt.reg.flood_allowed": "Flood allowed",
"rptmgmt.reg.flood_denied": "Flood denied",
"rptmgmt.reg.home": "home",
"rptmgmt.reg.intro": "Which flood-scoped packets this repeater passes on. A region set to Flood allowed is repeated; one set to Flood denied is dropped. Changes take effect at once but live in memory only until you save them.",
"rptmgmt.reg.menu_title": "Region actions",
"rptmgmt.reg.move": "Move to another region",
"rptmgmt.reg.move_confirm": "Move",
"rptmgmt.reg.move_help": "Choose the region to nest it under, or top level. Its own nested regions move with it, and stay in the same order underneath.",
"rptmgmt.reg.move_label": "Move {name} into",
"rptmgmt.reg.moved": "Region {name} moved into {parent}",
"rptmgmt.reg.moved_flood_lost": "{name} was moved, but its Flood denied setting could not be restored and it is now allowing flood. Set it again.",
"rptmgmt.reg.moved_root": "Region {name} moved to the top level",
"rptmgmt.reg.name_help": "Letters, digits and - $ # are allowed; no spaces. Max 30 characters.",
"rptmgmt.reg.name_placeholder": "Region name, e.g. pl",
"rptmgmt.reg.none": "This repeater has no regions configured.",
"rptmgmt.reg.parent": "Inside",
"rptmgmt.reg.parent_help": "Nest the new region inside an existing one, or leave it at the top level.",
"rptmgmt.reg.parent_root": "— top level —",
"rptmgmt.reg.read_failed": "Could not read the regions",
"rptmgmt.reg.reading": "Reading regions from the repeater…",
"rptmgmt.reg.refresh_title": "Re-read the region list from the repeater",
"rptmgmt.reg.remove": "Delete region",
"rptmgmt.reg.row_desc": "Which regions this repeater floods",
"rptmgmt.reg.save": "Save",
"rptmgmt.reg.save_failed": "Could not save the regions",
"rptmgmt.reg.save_title": "Write the region list to the repeater so it survives a reboot",
"rptmgmt.reg.saved": "Regions saved on the repeater",
"rptmgmt.reg.set_home": "Set as home region",
"rptmgmt.reg.title": "Regions",
"rptmgmt.reg.truncated": "The repeater sent more regions than fit in one reply, so this list may be incomplete. Use the CLI (`region list allowed`) to see the rest.",
"rptmgmt.reg.wildcard": "Packets without a region scope",
"rptmgmt.request_failed": "Request failed",
"rptmgmt.set.advanced": "Advanced",
"rptmgmt.set.advert": "Advertisement",
"rptmgmt.set.advert_interval": "Local advert interval (minutes)",
"rptmgmt.set.advert_interval_help": "Firmware accepts 60–240 minutes, or 0 to disable.",
"rptmgmt.set.agc_reset": "AGC reset interval",
"rptmgmt.set.agc_reset_help": "Multiple of 4; 0 disables periodic AGC resets.",
"rptmgmt.set.allow_read_only": "Allow read-only (guest) access",
"rptmgmt.set.allow_read_only_help": "Lets anyone log in with the guest password for read-only access: they can see status and settings, but change nothing.",
"rptmgmt.set.apply_count": "Apply ({count})",
"rptmgmt.set.basic": "Basic",
"rptmgmt.set.cad": "Channel activity detection (CAD)",
"rptmgmt.set.cad_help": "The radio checks whether the channel is busy before transmitting and defers if it is. Independent of the interference threshold. Requires firmware v1.17 or newer.",
"rptmgmt.set.direct_txdelay": "Direct TX delay factor (0–2)",
"rptmgmt.set.direct_txdelay_help": "The same random wait, for packets repeated along a known direct route instead of flooded. Usually lower than the flood delay, because a direct packet only concerns the repeaters on that one path.",
"rptmgmt.set.dutycycle": "Duty cycle (%)",
"rptmgmt.set.dutycycle_help": "The largest share of time the repeater may spend transmitting, in percent. 100 means no throttling; lower values make a busy repeater hold packets back. This replaced the older airtime factor (af) setting.",
"rptmgmt.set.features": "Features",
"rptmgmt.set.flood_advert_interval": "Flood advert interval (hours)",
"rptmgmt.set.flood_advert_interval_help": "How often the repeater floods a self-advert across the whole mesh, in hours. Firmware accepts 3–168, or 0 to stop flooding adverts. Every one of them reaches the entire network, so a short interval costs everybody airtime.",
"rptmgmt.set.flood_max": "Max flood hops",
"rptmgmt.set.flood_max_advert": "Max flood hops (adverts)",
"rptmgmt.set.flood_max_advert_help": "Hop limit applied to flooded adverts only. Default 8.",
"rptmgmt.set.flood_max_help": "Hop limit for every flood packet the repeater forwards: one that has already passed this many hops is dropped instead of repeated. Default 64. The two narrower limits below apply on top of this one.",
"rptmgmt.set.flood_max_unscoped": "Max flood hops (unscoped)",
"rptmgmt.set.flood_max_unscoped_help": "Hop limit for flood packets that carry no region scope. Default 64.",
"rptmgmt.set.guest_password": "Guest password",
"rptmgmt.set.guest_password_help": "Password for read-only guest logins.",
"rptmgmt.set.int_thresh": "Interference threshold",
"rptmgmt.set.int_thresh_help": "Interference threshold in dB above the measured noise floor: while the repeater hears something stronger than this it treats the channel as busy and waits before transmitting. 0 turns the check off. Works independently of CAD.",
"rptmgmt.set.intro": "Nothing is read from the repeater until you ask for it — every field is one round-trip through the mesh. Use the button next to a field to read that field, or Read all fields for the whole section. Values shown without a fresh read are the ones read last time.",
"rptmgmt.set.lat": "Latitude",
"rptmgmt.set.lat_help": "Latitude in decimal degrees, sent out with the repeater's adverts and used to place it on maps. Pick from map fills this in from a map click.",
"rptmgmt.set.load_errors": {
"one": "{count} field failed to load — Refresh retries the whole section.",
"other": "{count} fields failed to load — Refresh retries the whole section."
},
"rptmgmt.set.location": "Location",
"rptmgmt.set.lon": "Longitude",
"rptmgmt.set.lon_help": "Longitude in decimal degrees, sent out with the repeater's adverts and used to place it on maps. Pick from map fills this in from a map click.",
"rptmgmt.set.loop_detect": "Loop detection",
"rptmgmt.set.loop_detect_help": "Drops flood packets that keep coming back to this node — what a routing loop between neighbouring repeaters looks like from here. Stricter levels catch a loop sooner, but are also quicker to drop a genuine packet that simply travelled a long way round.",
"rptmgmt.set.map_pick": "Pick from map",
"rptmgmt.set.map_pick_title": "Read latitude and longitude first, then pick a point",
"rptmgmt.set.multi_acks": "Send multiple ACKs",
"rptmgmt.set.multi_acks_help": "How many acknowledgements the repeater sends back for a packet it delivers. 0 sends one; 1 sends it twice, which gets through more often at the cost of extra airtime.",
"rptmgmt.set.name": "Repeater name",
"rptmgmt.set.name_help": "The name this repeater advertises to the mesh and shows up under in contact lists. The firmware rejects unusual characters. Others see the new name after the repeater's next advert.",
"rptmgmt.set.network": "Network health",
"rptmgmt.set.operator": "Operator info",
"rptmgmt.set.owner_info": "Owner info",
"rptmgmt.set.owner_info_help": "Free-form operator / contact info shown to clients. Multiple lines allowed.",
"rptmgmt.set.password": "Admin password",
"rptmgmt.set.password_help": "Write-only — the current password is never shown. Changing it does not log out already active sessions.",
"rptmgmt.set.path_hash_mode": "Path hash mode (0–2)",
"rptmgmt.set.path_hash_mode_help": "Bytes per hop in the routing paths this repeater builds. 0 = 1 byte (shortest paths, most prefix collisions), 1 = 2 bytes, 2 = 3 bytes (longest, fewest collisions).",
"rptmgmt.set.pw_unchanged_ph": "(unchanged)",
"rptmgmt.set.radio": "Radio",
"rptmgmt.set.radio_note": "Frequency, bandwidth, SF and CR are applied after a reboot. TX power applies immediately.",
"rptmgmt.set.radio_params": "Radio parameters",
"rptmgmt.set.radio_params_help": "Frequency in MHz, bandwidth in kHz, spreading factor and coding rate. These must match the rest of your mesh exactly, or the repeater becomes invisible to it — including to you. Firmware limits: 150–2500 MHz, 7–500 kHz, SF 5–12, CR 5–8. Takes effect after a reboot.",
"rptmgmt.set.read_all": "Read all fields",
"rptmgmt.set.read_field_title": "Read this field from the repeater",
"rptmgmt.set.reading": "Reading from repeater… (one mesh round-trip per field)",
"rptmgmt.set.reboot_note": "Some changes take effect after a reboot — use Actions → Reboot.",
"rptmgmt.set.repeat": "Repeat packets",
"rptmgmt.set.repeat_help": "The master switch for forwarding. Off stops the node repeating other people's packets entirely — it still advertises itself and still answers admin commands, it just stops being a repeater.",
"rptmgmt.set.rxdelay": "RX delay base (0–20)",
"rptmgmt.set.rxdelay_help": "Staggers the repeating of flood packets by reception quality — a node that heard the packet well repeats it sooner, one that barely heard it waits and then drops it if a better-placed repeater got there first. 0 disables it, which is the firmware default.",
"rptmgmt.set.rxgain": "RX boosted gain",
"rptmgmt.set.rxgain_help": "Boosted LNA gain on receive: a little more sensitivity for slightly higher current draw. Not every radio chip has it — nodes without it report the field as not supported.",
"rptmgmt.set.stale_field_title": "From the last read — may be out of date",
"rptmgmt.set.stale_note": "The values above were read on {when} and may no longer be what the repeater holds. Read a field again to see its current value.",
"rptmgmt.set.tx": "TX power (dBm)",
"rptmgmt.set.tx_help": "Transmit power in dBm. The usable range depends on the board and on the power limits that apply where the repeater stands. Takes effect immediately.",
"rptmgmt.set.txdelay": "TX delay factor (0–2)",
"rptmgmt.set.txdelay_help": "Random wait before repeating a flood packet, as a multiple of that packet's airtime. It scatters neighbouring repeaters in time so they do not all answer at the same instant and collide. 0 repeats immediately.",
"rptmgmt.settings_apply_failed": "Failed to apply settings",
"rptmgmt.settings_read_failed": "Failed to read settings",
"rptmgmt.status.battery": "Battery",
"rptmgmt.status.clock": "Clock",
"rptmgmt.status.clock_fetch": "fetch",
"rptmgmt.status.duplicates": "Duplicates",
"rptmgmt.status.events": "Debug / error events",
"rptmgmt.status.last_rssi": "Last RSSI",
"rptmgmt.status.last_snr": "Last SNR",
"rptmgmt.status.loading": "Requesting status from the repeater…",
"rptmgmt.status.noise": "Noise floor",
"rptmgmt.status.packets": "Packet Statistics",
"rptmgmt.status.queue": "Queue length",
"rptmgmt.status.radio": "Radio Statistics",
"rptmgmt.status.received": "Received",
"rptmgmt.status.refresh_title": "Refresh status",
"rptmgmt.status.rx_airtime": "RX airtime",
"rptmgmt.status.rx_errors": "RX errors",
"rptmgmt.status.sent": "Sent",
"rptmgmt.status.system": "System Information",
"rptmgmt.status.tx_airtime": "TX airtime",
"rptmgmt.status.uptime": "Uptime",
"rptmgmt.status.utilization": "Channel utilization",
"rptmgmt.status_failed": "Failed to get status",
"rptmgmt.tel.channel": "Channel {n}",
"rptmgmt.tel.device_suffix": "device",
"rptmgmt.tel.loading": "Requesting telemetry from the repeater…",
"rptmgmt.tel.loading_hint": "Multi-hop paths can take up to a minute.",
"rptmgmt.tel.mcu_temp": "MCU temperature",
"rptmgmt.tel.refresh_title": "Refresh telemetry",
"rptmgmt.telemetry_failed": "Failed to get telemetry",
"rptmgmt.telemetry_none": "No telemetry data reported.",
"rptmgmt.this_repeater": "This repeater",
"rptmgmt.title": "Repeater Management",
"rptmgmt.toast.admin_required": "Admin login required for this tool",
"rptmgmt.toast.applied": "Settings applied",
"rptmgmt.toast.applied_reboot": "Applied — reboot required for some changes",
"rptmgmt.toast.apply_failed_count": {
"one": "{count} setting failed to apply",
"other": "{count} settings failed to apply"
},
"rptmgmt.toast.apply_partial_unsupported": {
"one": "Applied — {count} setting is not supported by this repeater",
"other": "Applied — {count} settings are not supported by this repeater"
},
"rptmgmt.toast.apply_unsupported_count": {
"one": "{count} setting is not supported by this repeater — nothing was applied",
"other": "{count} settings are not supported by this repeater — nothing was applied"
},
"rptmgmt.toast.field_empty": "\"{field}\" cannot be empty",
"rptmgmt.toast.password_synced": "Saved password updated to the new one",
"rptmgmt.tool_coming": "The <strong>{tool}</strong> tool is coming in a later stage.",
"rptmgmt.tools.actions": "Actions",
"rptmgmt.tools.actions_desc": "Advert, clock sync, reboot, power off",
"rptmgmt.tools.cli": "CLI",
"rptmgmt.tools.cli_desc": "Send text commands to the repeater",
"rptmgmt.tools.neighbors": "Neighbors",
"rptmgmt.tools.neighbors_desc": "Zero-hop repeaters heard",
"rptmgmt.tools.settings": "Settings",
"rptmgmt.tools.settings_desc": "Configure repeater parameters",
"rptmgmt.tools.status": "Status",
"rptmgmt.tools.status_desc": "Battery, radio and packet statistics",
"rptmgmt.tools.telemetry": "Telemetry",
"rptmgmt.tools.telemetry_desc": "Sensor channels (Cayenne LPP)",
"rptmgmt.tools_heading": "Management Tools",
"rptmgmt.unsupported": "not supported",
"rptmgmt.unsupported_hint": "This repeater's firmware or hardware does not have this setting.",
"rptmgmt.updated_ago": "Updated {time} ago",
"rptmgmt.updated_just_now": "Updated just now",
"search.count": {
"one": "{count} result",
"other": "{count} results"
},
"search.dm_received": "(received)",
"search.dm_sent": "(sent)",
"search.empty": "Search across all channel and direct messages",
"search.failed": "Search failed. Please try again.",
"search.fts5_ref": "Full FTS5 syntax reference",
"search.help_title": "Search syntax help",
"search.min_chars": "Type at least 2 characters to search",
"search.no_results": "No results for \"{query}\"",
"search.ph": "Search all messages...",
"search.searching": "Searching...",
"search.special_chars": "Special characters (<code>. , - :</code>) should be wrapped in quotes.",
"search.tip_and": "messages containing both words",
"search.tip_not": "hello but not world",
"search.tip_or": "either word",
"search.tip_phrase": "exact phrase",
"search.tip_prefix": "prefix match (hello,hellas...)",
"search.tips": "Search tips:",
"settings.analyzer.intro": "Add MeshCore Analyzer services to choose from when clicking <i class=\"bi bi-clipboard-data\"></i> under a group chat message. The URL must contain the <code>{packetHash}</code> placeholder — it is replaced with the message's packet hash.",
"settings.analyzer.services": "Analyzer Services",
"settings.analyzer.tip": "Tip: star one analyzer to use it without being asked. Clear the star to be prompted on every click. Disabled analyzers stay in the list but are hidden from the chooser.",
"settings.appear.btn_size": "Button size (px)",
"settings.appear.item": "Item",
"settings.appear.language": "Language",
"settings.appear.language_hint": "Applies to this browser and becomes the server default for others. Add your own by dropping a catalog into the translations folder — see <code>docs/translations.md</code>.",
"settings.appear.place_fab": "Quick Access",
"settings.appear.place_menu": "Main Menu",
"settings.appear.placement": "Placement",
"settings.appear.placement_aria": "{item} placement",
"settings.appear.position": "Position",
"settings.appear.quick_access": "Quick Access Buttons",
"settings.appear.reload_title": "Re-scan the translations folder",
"settings.appear.reset_default": "Reset to default",
"settings.appear.show_fab": "Show Quick Access buttons",
"settings.appear.show_fab_hint": "Off by default. When off, all items appear in the Main Menu.",
"settings.appear.spacing": "Spacing (px)",
"settings.appear.theme": "Theme",
"settings.appear.theme_dark": "Dark",
"settings.appear.theme_dark_desc": "Easy on the eyes, deep navy palette",
"settings.appear.theme_light": "Light",
"settings.appear.theme_light_desc": "Classic bright interface",
"settings.appear.toast.reload_failed": "Failed to reload translations",
"settings.appear.toast.reloaded": "Translations reloaded: {names}",
"settings.chat.autoclose": "Auto-close after (s)",
"settings.chat.autoclose_help": "Seconds before the route popup closes automatically",
"settings.chat.no_autoclose": "Don't close automatically",
"settings.chat.no_autoclose_help": "Popup stays open until you tap outside it",
"settings.chat.quote": "Quote",
"settings.chat.quote_len": "Quote length (bytes)",
"settings.chat.quote_len_help": "Default max UTF-8 bytes for truncated quotes",
"settings.chat.route_popup": "Route popup",
"settings.chat.route_popup_desc": "The popup shown when tapping the Route line under a message. Also applies to DMs.",
"settings.contacts.auto_ignore": "Automatically add new contacts to \"Ignored\"",
"settings.contacts.auto_ignore_help": "Every new advert is automatically marked as Ignored — no notifications, no badge. Contacts still appear under Existing Contacts (Cache) and can be promoted with 'To Device'. Requires Manual approval ON.",
"settings.contacts.manual_approval": "Manual approval enabled",
"settings.contacts.manual_approval_help": "When enabled, new contacts must be manually approved before they can communicate with your node",
"settings.contacts.suppress_notifs": "Suppress new advert notifications",
"settings.contacts.suppress_notifs_help": "Hide the badge over Contact Management and the browser notification when new pending contacts arrive. The Pending Contacts list itself still shows them. Requires Manual approval ON.",
"settings.device.bw": "Bandwidth (kHz)",
"settings.device.confirm.radio": "Changing radio settings will disconnect from the mesh network. Continue?",
"settings.device.cr": "Coding Rate",
"settings.device.freq": "Frequency (MHz)",
"settings.device.hash_1b": "1 byte (default)",
"settings.device.hash_2b": "2 bytes",
"settings.device.hash_3b": "3 bytes",
"settings.device.load_preset": "Load preset...",
"settings.device.name_ph": "Device name",
"settings.device.path_hash_mode": "Path hash mode",
"settings.device.path_hash_mode_help": "Bytes per hop in routing paths. 1B = shortest path, more collisions; 3B = longest, fewest collisions.",
"settings.device.public_info": "Public Info",
"settings.device.radio": "Radio Settings",
"settings.device.sf": "Spreading Factor",
"settings.device.share_pos": "Share position in advert",
"settings.device.share_pos_help": "Include GPS coordinates in device advertisement",
"settings.device.toast.cr_invalid": "Coding rate must be 5-8",
"settings.device.toast.freq_invalid": "Invalid frequency",
"settings.device.toast.info_save_failed": "Failed to save public info",
"settings.device.toast.info_saved": "Public info saved",
"settings.device.toast.name_empty": "Device name cannot be empty",
"settings.device.toast.radio_save_failed": "Failed to save radio settings",
"settings.device.toast.radio_saved": "Radio settings saved",
"settings.device.toast.sf_invalid": "Spreading factor must be 5-12",
"settings.device.toast.tx_invalid": "TX power must be 0-30 dBm",
"settings.device.tx_power": "TX Power (dBm)",
"settings.diag.captures": "Saved captures",
"settings.diag.debug_logs": "Record detailed logs",
"settings.diag.debug_logs_hint": "Raises the log level to DEBUG while recording. Leave this on — most of what the analysis needs is only written at that level.",
"settings.diag.duration": "Record for",
"settings.diag.indicator": "Recording",
"settings.diag.indicator_title": "A diagnostic capture is running. Open Settings → Diagnostics to stop it.",
"settings.diag.intro": "Records what the app receives from the radio into a file you can send to the maintainer. Use it when something is wrong and a screenshot cannot show why — a missing repeater badge, messages that never arrive.",
"settings.diag.note": "Note",
"settings.diag.note_ph": "e.g. no repeater badge on #general",
"settings.diag.privacy": "The capture contains the <strong>text of every message received while it runs</strong>, along with contact names and public keys. Only send it to someone you trust.",
"settings.diag.recording": "Recording…",
"settings.diag.reproduce_hint": "Now reproduce the problem — send the message that misbehaves, then come back here and stop the recording.",
"settings.diag.sharing": "Sending",
"settings.diag.sharing_hint": "Optional. With a token from the maintainer a capture can be uploaded straight from here; without one, download it and send it yourself.",
"settings.diag.start": "Start recording",
"settings.diag.stop": "Stop and save",
"settings.diag.upload_token": "Upload token",
"settings.diag.upload_token_ph": "paste the token you were given",
"settings.diag.upload_url": "Upload URL",
"settings.iface.breakpoint": "Sidebar breakpoint (px)",
"settings.iface.breakpoint_help": "Default: 992. Range: 600-2000.",
"settings.iface.breakpoint_reset_title": "Reset to default (992)",
"settings.iface.layout": "Layout",
"settings.iface.layout_desc": "Window width above which the channel/contact list is shown as a sidebar. Below this width it collapses to a dropdown at the top of the screen. Saved per device (browser).",
"settings.iface.pos_bottom_left": "Bottom left",
"settings.iface.pos_bottom_right": "Bottom right",
"settings.iface.pos_center": "Center",
"settings.iface.pos_top_left": "Top left",
"settings.iface.pos_top_right": "Top right",
"settings.iface.position": "Position on screen",
"settings.iface.toast.autoclose_invalid": "Invalid auto-close duration",
"settings.iface.toast_autoclose_help": "Seconds before a notification closes automatically",
"settings.iface.toast_desc": "Controls the small toasts shown after actions (e.g. \"Advert Sent\", errors).",
"settings.iface.toast_no_autoclose_help": "Notifications stay until dismissed via their close button",
"settings.msg.direct_header": "When path is known (DIRECT)",
"settings.msg.direct_interval_help": "Seconds to wait between direct retries",
"settings.msg.direct_retries": "Direct retries",
"settings.msg.direct_retries_help": "Attempts via known path before switching to flood",
"settings.msg.flood_header": "When no path (FLOOD)",
"settings.msg.flood_interval_help": "Seconds to wait between flood retries",
"settings.msg.flood_retries": "Flood retries",
"settings.msg.flood_retries_help": "Flood attempts after direct retries exhausted (when no configured paths)",
"settings.msg.grace": "Grace period (s)",
"settings.msg.grace_help": "Wait for late ACKs after all retries exhausted",
"settings.msg.interval": "Interval (s)",
"settings.msg.intro": "Retries are counted after the initial send, e.g. 3 retries = 4 total attempts.",
"settings.msg.max_retries": "Max retries",
"settings.msg.max_retries_help": "Flood retry attempts (also used after path rotation)",
"settings.msg.other": "Other",
"settings.notif.blocked": "Blocked",
"settings.notif.desc": "Browser notifications appear when the app is hidden or in the background.",
"settings.notif.profiles.add": "Add profile",
"settings.notif.profiles.add_rule": "Add rule",
"settings.notif.profiles.confirm.delete": "Delete profile \"{name}\"?",
"settings.notif.profiles.confirm.delete_in_use": "Delete profile \"{name}\"? Channels using it ({count}) will go back to notifying about every message.",
"settings.notif.profiles.desc": "A profile narrows what counts as news on a channel: only messages matching its rules raise the unread badge and post a notification. Assign it per channel with the bell in Manage Channels. Direct messages always notify.",
"settings.notif.profiles.edit": "Edit profile",
"settings.notif.profiles.empty": "No profiles yet. Click \"Add profile\" to create one.",
"settings.notif.profiles.in_use": {
"one": "{count} channel",
"other": "{count} channels"
},
"settings.notif.profiles.load_failed": "Failed to load profiles",
"settings.notif.profiles.match_all": "all rules match",
"settings.notif.profiles.match_any": "any rule matches",
"settings.notif.profiles.match_label": "Notify when",
"settings.notif.profiles.name_ph": "e.g. Mentions and webui",
"settings.notif.profiles.name_required": "Name is required",
"settings.notif.profiles.rule.mention": "Mentions me",
"settings.notif.profiles.rule.mention_hint": "looks for \"{name}\"",
"settings.notif.profiles.rule.no_name": "device name unknown — matches nothing until the device reports it",
"settings.notif.profiles.rule.sender": "Sender name contains",
"settings.notif.profiles.rule.text": "Message contains",
"settings.notif.profiles.rule.value_ph": "text or part of a word",
"settings.notif.profiles.rule_value_required": "Every text rule needs some text",
"settings.notif.profiles.rules": "Rules",
"settings.notif.profiles.rules_hint": "Text rules match a word or any part of one.",
"settings.notif.profiles.rules_required": "Add at least one rule",
"settings.notif.profiles.summary.all": "all of: {rules}",
"settings.notif.profiles.summary.any": "any of: {rules}",
"settings.notif.profiles.summary.mention": "mentions me",
"settings.notif.profiles.summary.sender": "from \"{value}\"",
"settings.notif.profiles.summary.text": "contains \"{value}\"",
"settings.notif.profiles.tip": "Matching ignores letter case and diacritics, so <code>krakow</code> also finds <em>Kraków</em>. A mention means your device name anywhere in the message, whether written as <code>@[name]</code> or plainly.",
"settings.notif.profiles.title": "Notification profiles",
"settings.notif.profiles.toast.delete_failed": "Failed to delete profile",
"settings.notif.profiles.toast.save_error": "Error saving profile",
"settings.notif.profiles.toast.save_failed": "Failed to save profile",
"settings.notif.toast.blocked_help": "Notifications are blocked. Change browser settings: Settings → Site Settings → Notifications",
"settings.notif.toast.denied": "Notifications blocked. Change browser settings to enable them.",
"settings.notif.toast.disabled": "Notifications disabled",
"settings.notif.toast.enabled": "Notifications enabled",
"settings.notif.toast.error": "Error enabling notifications",
"settings.notif.toast.unsupported": "Notifications are not supported in this browser",
"settings.notif.unavailable": "Unavailable",
"settings.notif.works_hidden": "Works when app is hidden",
"settings.observer.advert_interval": "Flood advert interval (h)",
"settings.observer.advert_interval_ph": "0 = off",
"settings.observer.brokers": "MQTT Brokers",
"settings.observer.enable": "Enable observer",
"settings.observer.hint": "The 3-letter location code is used in MQTT topics; leave it empty for the flat <code>meshcore/packets</code> topics. Advert interval 0 disables scheduled flood adverts.",
"settings.observer.iata": "Location code (IATA)",
"settings.observer.iata_ph": "e.g. KRK",
"settings.observer.intro": "Publish every mesh packet this device overhears to MQTT brokers, in the <code>meshcore-packet-capture</code> format (topics <code>meshcore/IATA/PUBKEY/packets</code>). Compatible with letsmesh-style packet analyzers.",
"settings.regions.confirm.delete": "Delete region \"{name}\"?\nChannels using this region will revert to no scope.",
"settings.regions.delete_title": "Delete region",
"settings.regions.empty": "No regions defined. Add one below.",
"settings.regions.intro": "Only repeaters allowing a region will forward messages tagged with it. Find standardised region names at <a href=\"https://regions.meshcore.nz/\" target=\"_blank\" rel=\"noopener\">regions.meshcore.nz</a>.",
"settings.regions.is_default": "default",
"settings.regions.load_failed": "Failed to load regions",
"settings.regions.name_ph": "Region name (e.g. pl, pl-ma)",
"settings.regions.none_row": "None — use firmware default",
"settings.regions.picker_empty": "No regions defined yet.",
"settings.regions.picker_manage": "Manage Regions",
"settings.regions.registry": "Region Registry",
"settings.regions.tip": "Tip: pick the default region via the radio button, or select <em>None</em> to fall back to the firmware default. The chosen region is also pushed to the firmware so any untagged channel uses it.",
"settings.regions.toast.add_error": "Network error adding region",
"settings.regions.toast.add_failed": "Failed to add region",
"settings.regions.toast.clear_error": "Network error clearing default",
"settings.regions.toast.clear_failed": "Failed to clear default region",
"settings.regions.toast.default_error": "Network error setting default",
"settings.regions.toast.default_failed": "Failed to set default region",
"settings.regions.toast.delete_error": "Network error deleting region",
"settings.regions.toast.delete_failed": "Failed to delete region",
"settings.regions.toast.name_invalid": "Invalid region name. Allowed: letters, digits, - $ # (max 30 bytes, no spaces).",
"settings.regions.toast.scope_error": "Network error saving region scope",
"settings.regions.toast.scope_failed": "Failed to save region scope",
"settings.reset_defaults": "Reset to defaults",
"settings.tab.analyzer": "Analyzer",
"settings.tab.appearance": "Appearance",
"settings.tab.chat": "Group Chat",
"settings.tab.contacts": "Contacts",
"settings.tab.diagnostics": "Diagnostics",
"settings.tab.interface": "Interface",
"settings.tab.messages": "Messages",
"settings.tab.notifications": "Notifications",
"settings.tab.observer": "Observer",
"settings.tab.regions": "Regions",
"settings.toast.invalid_value": "Invalid value for {field}",
"settings.toast.save_failed": "Failed to save settings",
"settings.toast.saved": "Settings saved",
"settings.toast.setting_network_error": "Network error saving setting",
"settings.toast.setting_save_failed": "Failed to save setting",
"share.btn_title": "Share a contact, channel or location",
"share.card.add_channel": "Add Channel",
"share.card.add_contact": "Add Contact",
"share.card.channel_conflict": "Name used by a different key",
"share.card.channel_joined": "Already joined",
"share.card.copy_coords": "Copy coordinates",
"share.card.from_you": "{distance} · {bearing} from you",
"share.card.hashtag_channel": "Hashtag Channel",
"share.card.in_cache": "In cache",
"share.card.in_contacts": "Already in contacts",
"share.card.open_apple": "Apple Maps",
"share.card.open_google": "Google Maps",
"share.card.open_in_map": "Shared location",
"share.card.open_osm": "OpenStreetMap",
"share.card.private_channel": "Private Channel",
"share.card.push_to_device": "Push to device",
"share.card.region": "Region: {name}",
"share.card.update_name": "Update name to {name}",
"share.card.view_on_map": "View on map",
"share.card.you_shared_channel": "Shared channel",
"share.card.you_shared_contact": "Shared contact",
"share.menu.channel": "Share Channel",
"share.menu.contact": "Share Contact",
"share.menu.location": "Share Location",
"share.pick.channel_title": "Share a channel",
"share.pick.contact_title": "Share a contact",
"share.pick.empty": "Nothing to share here yet",
"share.pick.location_title": "Share a location",
"share.pick.map_hint": "Or tap the map to pick any point",
"share.pick.me": "Me",
"share.pick.my_position": "My device position",
"share.pick.no_position": "This device has no position set. Add one in Settings, or pick a point on the map below.",
"share.pick.search_ph": "Search contacts...",
"share.pick.truncated": "Showing {shown} of {total} — type to narrow it down",
"share.preview.label": "Will be sent as:",
"share.toast.action_failed": "Action failed",
"share.toast.channel_unshareable": "This channel has no key to share",
"share.toast.contact_added": "Contact {name} added to cache",
"share.toast.contact_pushed": "{name} saved on the device",
"share.toast.contact_updated": "Contact renamed to {name}",
"share.toast.map_unavailable": "The map is not available here",
"share.toast.over_budget": "The message is now {bytes} of {max} bytes and will not send",
"share.toast.scope_applied": "Region scope {name} applied to this channel",
"share.toast.scope_unknown": "Region \"{name}\" is not configured here — add it in Settings to match the sender's scope",
"share.toast.too_long": "This does not fit in one message ({bytes} of {max} bytes)",
"share.type.1": "Companion",
"share.type.2": "Repeater",
"share.type.3": "Room Server",
"share.type.4": "Sensor",
"update.checking": "Checking for updates...",
"update.done": "Updated to {version}",
"update.link_available": "Update available",
"update.link_available_title": "Update available: {version}",
"update.link_now": "Update now",
"update.link_now_title": "Click to update",
"update.network_error": "Network error during update",
"update.new_version": "New version available: {version}",
"update.no_new_commit": "Already on the newest commit - nothing was pulled.",
"update.no_new_image": "The change is already on GitHub, but its Docker image is still being built. Try again in a few minutes.",
"update.now": "Update Now",
"update.rebuilding": "Rebuilding containers... ({attempt}/{max})",
"update.reload": "Reload Page",
"update.start_failed": "Failed to start update",
"update.starting": "Starting update...",
"update.timed_out": "Update timed out. Please check server manually.",
"update.title": "Update mc-webui",
"update.toast.available": "Update available: {version}",
"update.toast.check_error": "Network error checking for updates",
"update.toast.check_failed": "Failed to check for updates",
"update.toast.latest": "You are running the latest version",
"update.updating": "Updating...",
"update.waiting": "Update started. Waiting for server to restart...",
"update.whats_new": "What's new?"
}