const en = { common: { help: "Help", refresh: "Refresh", refreshing: "Refreshing…", query: "Search", clearFilters: "Clear filters", prevPage: "Previous", nextPage: "Next", perPage: "{{n}}/page", pagerMeta: "{{total}} items · page {{page}}/{{pages}}", opFailed: "Operation failed", empty: "-", }, workbench: { title: "Workbench", backToWorkbench: "Back to Workbench", openInNewTab: "Open in new tab", openModule: "Open or switch to this module tab", monitoring: "Monitoring", operations: "Operations", system: "System", cards: { umeSync: "UME Sync", umeSyncDesc: "UME alarms, subscription & inventory", managedNe: "NE Management", managedNeDesc: "Multi-vendor SSH/Telnet NEs & connectivity test", collect: "Batch Collect", collectDesc: "Run custom CLI commands on connected NEs and save logs", webcrt: "WebCRT", webcrtDesc: "Interactive browser login to connected NEs (SSH/Telnet)", topology: "Topology", topologyDesc: "Drag NE topology maps; discover links via LLDP/CDP", users: "Users", usersDesc: "Admin: create, disable users and reset passwords", audit: "Audit", auditDesc: "View login and operation records", apiKeys: "API Keys", apiKeysDesc: "Issue MCP/script tokens per user with expiry", }, }, layout: { title: "UME Integration", titleUme: "UME Sync", titleManagedNe: "NE Management", titleCollect: "Batch Collect", titleWebcrt: "WebCRT", titleTopology: "Topology", titleUsers: "Users", titleAudit: "Audit", titleApiKeys: "API Keys", navUme: "UME", netxApi: "netx api", oclawBridge: "oclaw WSS", language: "Language", moreMenu: "More", langZh: "中文", langEn: "English", }, auth: { loginTitle: "Sign in to NetX", loginHint: "Use a local account to access the ops platform", username: "Username", password: "Password", login: "Sign in", loggingIn: "Signing in…", loginFailed: "Login failed", logout: "Sign out", usersTitle: "User management", usersHint: "Only admins can create and manage local accounts.", addUser: "Add user", role: "Role", roleAdmin: "Admin", roleUser: "User", status: "Status", active: "Active", disabled: "Disabled", enable: "Enable", disable: "Disable", actions: "Actions", newPassword: "New password", resetPassword: "Reset password", userCreated: "User created", userUpdated: "User updated", auditTitle: "Audit log", auditHintAdmin: "View login and operation records for all users.", auditHintUser: "View your own operation records.", filterUsername: "Filter username", filterAction: "Filter action", colTime: "Time", colUser: "User", colAction: "Action", colMethod: "Method", colPath: "Path", colStatus: "Status", colIp: "IP", apiKeysTitle: "API Key management", apiKeysHint: "Create long-lived tokens for MCP/scripts. The secret is shown only once. Admins can issue keys for other users.", tokenName: "Name", expiresIn: "Expiry", expire7d: "7 days", expire30d: "30 days", expire90d: "90 days", expire365d: "1 year", expireNever: "Never", tokenOwner: "Owner", tokenOwnerSelf: "Myself ({{user}})", createToken: "Create key", tokenCreated: "API key created", tokenRevoked: "Revoked", tokenOnceHint: "Copy and store this secret now; it will not be shown again:", copyToken: "Copy", tokenCopied: "Copied", tokenCopyFailed: "Copy failed", expiresAt: "Expires", lastUsed: "Last used", tokenStatusActive: "Active", tokenStatusExpired: "Expired", tokenStatusRevoked: "Revoked", revokeToken: "Revoke", revokeConfirm: "Revoke this API key?", forceChangeTitle: "Change initial password", forceChangeHint: "Account {{user}} is still using the default password. You must change it before continuing.", oldPassword: "Current password", confirmPassword: "Confirm new password", savePassword: "Save new password", savingPassword: "Saving…", passwordTooShort: "New password must be at least 6 characters", passwordMismatch: "New passwords do not match", passwordMustChange: "New password must differ from the default/old password", }, collect: { create: { title: "New collection job", hint: "Configure commands and NEs, then create the job. Click Start in the job list to run collection.", selectedTitle: "Selected NEs", selectedEmpty: "Filter below and add NEs one by one.", selectedCount: "{{count}} selected", clearSelected: "Clear selection", remove: "Remove", pickTitle: "Eligible NEs (connectivity passed)", pickHint: "Only NEs with connect_status=pass. Run connectivity test in NE Management first.", pickEmpty: "No matching NEs.", filterKeyword: "Filter", filterKeywordPh: "Name / IP / vendor / device type", add: "Add", addBatch: "Add selected ({{count}})", added: "Added", create: "Create job", creating: "Creating…", expand: "Expand", collapse: "Collapse", meta: "{{ne}} NE(s) selected · {{cmd}} command(s)", }, eligible: { title: "Eligible NEs (connectivity passed)", hint: "Only NEs with connect_status=pass. Run connectivity test in NE Management first.", empty: "No NEs with passed connectivity test.", }, form: { title: "Collection setup", jobTitle: "Job title", jobTitlePh: "Auto-generated if empty", commands: "Commands (one per line, # for comments)", commandsPh: "display version\ndisplay current-configuration", commandsHint: "Commands run in order on each device; output is saved as a text log (legacy NetX format).", meta: "{{ne}} NE(s) selected · {{cmd}} command(s)", start: "Start collection", starting: "Submitting…", }, created: "Collection job created: {{id}}", started: "Collection started: {{id}}", paused: "Job paused", restarted: "Collection restarted", retryFailedDone: "Retrying failed devices", deleted: "Job deleted", nothingToRetry: "No failed devices to retry", neBusy: "Some NEs are busy in another collection job; wait for it to finish before starting", jobRunning: "This job is already running", confirmDelete: "Delete this collection job and its log files?", jobs: { title: "Collection jobs", col: { title: "Job", status: "Status", progress: "Progress", created: "Created", lastRun: "Last run", actions: "Actions", }, ok: "ok", fail: "fail", expand: "Details", collapse: "Hide", pause: "Pause", start: "Start", starting: "Starting…", restart: "Restart", retryFailed: "Retry failed", downloadResults: "Download results", delete: "Delete", autoRefresh: "Auto-refresh every 2s while jobs are running", runsInProgress: "Some devices are still being collected…", }, runs: { status: "Status", message: "Message", download: "Log", downloadFile: "Download", filterStatus: "Status filter", filterDevice: "Device filter", deviceKeywordPh: "Name / IP", allStatus: "All statuses", }, }, managedNe: { title: "Network Elements", credsNotConfigured: "NETX_CREDENTIAL_SECRET_KEY is not set. Configure a Fernet key in .env and restart the API to store passwords.", add: "Add NE", importBtn: "Import", downloadTemplate: "Download template", importing: "Importing…", edit: "Edit", delete: "Delete", openTerminal: "Terminal", confirmDelete: "Delete this network element?", batchDelete: "Batch delete", batchDeleting: "Deleting…", batchDeleteConfirm: "Delete {{n}} selected network element(s)? This cannot be undone.", batchDeleteDone: "Deleted {{n}} network element(s)", batchDeleteSelectRequired: "Select network elements to delete", keywordPh: "name / IP / user / tags", allVendors: "All vendors", allConnectStatus: "All connect status", stats: { title: "Overview", total: "{{n}} NEs managed", pass: "Connected", fail: "Failed", unknown: "Untested", testing: "Testing", batchProxy: "Batch add proxy", batchTest: "Batch connect-test", tagFilter: "Filter by tag", all: "All", allTag: "All (no tag filter)", noTag: "No tags", tagStats: "Top tags", confirm: "Apply to {{n}} NE(s). Continue?", proxyDone: "Proxy applied to {{n}} NE(s)", testDone: "Connect-test submitted for {{n}} NE(s)", loadingIds: "Loading…", }, col: { name: "Name", vendor: "Vendor", deviceType: "Device type", ip: "Management IP", port: "Port", protocol: "Protocol", user: "Username", password: "Password", tags: "Tags", remark: "Remark", connect: "Connect", testedAt: "Tested at", actions: "Actions", }, connect: { run: "Connectivity test", running: "Testing…", submitted: "Submitted tests for {{n}} device(s)", retest: "Test again", }, connectDetail: "Details", connectDetailTitle: "Connectivity test log", connectDetailEmpty: "No log yet. Run a connectivity test first; failures store full errors and hop context (passwords excluded).", umeSync: { sync: "Sync UME NEs", syncing: "Syncing…", delete: "Delete UME NEs", deleting: "Deleting…", deleteConfirm: "Delete all managed NEs created from UME sync? This cannot be undone.", done: "UME sync done: {{inserted}} inserted, {{updated}} updated, {{deleted}} deleted, {{total}} inventory total", deletedDone: "Deleted {{n}} UME-synced NEs", }, account: { batchAdd: "Batch add account", batchByTag: "Batch account by tag", batchTitle: "Batch add account", batchHint: "Apply the account info below to {{n}} selected NE(s).", batchByTagHint: "Apply the account info below to NEs under the selected tag; leave password blank to update only username.", batchDone: "Account updated for {{n}} NE(s)", selectRequired: "Select network elements first", applying: "Applying…", apply: "Apply to NEs", usernameOrPasswordRequired: "Enter at least a username or a password", passwordOptionalBatch: "leave blank to keep unchanged", }, help: "[Bulk import]\n" + "· Required columns: device_type, ip, username, port, protocol, name, vendor. Download the template first.\n" + "· password may be empty (required for direct login; optional for bastion-managed or batch proxy later).\n" + "· Recommended flow: import NEs first, select rows, then use Batch add proxy.\n\n" + "[Jump / bastion]\n" + "· Bastion SSH username template: {hop_user}@{target_user}@{target_ip} (bastion host is separate); CLI: ssh user@target@ip@bastion-host.\n" + "· Bastion-managed: set Jump password (Vault); target password optional. Manual mode needs target password.\n" + "· JumpServer/CBH often use port 2222; some sites use 22.\n\n" + "[Connectivity / edit]\n" + "· NETX_CREDENTIAL_SECRET_KEY required to store passwords.\n" + "· Leave password blank on edit to keep unchanged; open Details after failed connect-test for hop context (no secrets).", importResult: { done: "Import done: {{inserted}} inserted, {{updated}} updated, {{failed}} failed row(s)", }, form: { requiredHint: "Fields marked with * are required", nameConnectHint: "After a successful connectivity test, name is replaced with the device hostname", createTitle: "Add network element", editTitle: "Edit network element", save: "Save", saving: "Saving…", cancel: "Cancel", created: "Network element created", updated: "Network element updated", deleted: "Network element deleted", passwordRequired: "Password is required", passwordOptional: "leave blank to keep unchanged", }, hop: { sectionTitle: "Jump host / proxy", type: "Jump type", enable: "Connect to target via jump host", vendor: { zte: "ZTE device (CLI jump)", huawei: "Huawei device (CLI jump)", cisco: "Cisco device (CLI jump)", linux: "Linux server (SSH tunnel)", bastion: "Bastion host (SSH protocol proxy)", }, zteHint: "Run ssh/telnet on the ZTE device to reach the target; target credentials use secondary auth.", huaweiHint: "Run telnet / stelnet (SSH) on the Huawei hop; stelnet is SSH. Target credentials use secondary auth.", ciscoHint: "Run Cisco ssh -vrf / telnet /vrf jump commands; target credentials use secondary auth.", linuxHint: "SSH to the Linux bastion, then direct-tcpip tunnel to target IP:port (ProxyJump-style).", bastionHint: "SSH with composite username via bastion protocol proxy. Example: bastion-user@target-user@2.2.2.2@1.1.1.1; password is the bastion/Vault password. JumpServer/CBH often use port 2222.", targetAuthMode: "Target credentials", targetAuthBastionManaged: "Bastion-managed (target password optional)", targetAuthManual: "Manual (secondary auth after connect)", targetAuthHint: "Bastion-managed needs only bastion password; manual mode requires target NE password.", usernameTemplate: "SSH username template", templateHintBastion: "Default {hop_user}@{target_user}@{target_ip}. Bastion address is the hop host field. CLI example: ssh bastion-user@target-user@2.2.2.2@1.1.1.1.", host: "Jump host", port: "Jump port", protocol: "Jump protocol", protocolSshStelnet: "ssh (stelnet)", username: "Jump username", password: "Jump password", vrf: "Mgmt VRF (optional)", vpnInstance: "VPN-Instance (optional)", vrfCisco: "VRF (optional, e.g. MGMT)", commandTemplate: "Jump command template", templateHint: "ZTE CLI: telnet {target_ip}, telnet {target_ip} vrf {vrf}, ssh {target_ip}, ssh {target_ip} vrf {vrf}. Auto-suggested from jump protocol/VRF; same when left blank. Target credentials via secondary auth prompts.", templateHintHuawei: "Huawei CLI: telnet {target_ip}, telnet vpn-instance {vrf} {target_ip}, stelnet {target_ip}, stelnet {target_ip} -vpn-instance {vrf}. SSH protocol maps to stelnet. Auto-suggested when left blank.", templateHintCisco: "Cisco CLI: ssh {target_ip}, ssh -vrf {vrf} {target_ip}, telnet {target_ip}, telnet {target_ip} /vrf {vrf}. Auto-suggested when left blank.", badge: { zte: "ZTE hop", huawei: "Huawei hop", cisco: "Cisco hop", linux: "Linux hop", bastion: "Bastion", }, hostRequired: "Jump host is required", userRequired: "Jump username is required", passwordRequired: "Jump password is required", batchAdd: "Add proxy", batchTitle: "Batch add jump proxy", batchHint: "Apply the jump-host settings below to {{n}} selected NE(s) (overwrites existing proxy config).", batchDone: "Proxy applied to {{n}} NE(s)", selectRequired: "Select network elements first", applying: "Applying…", apply: "Apply to selected", }, }, ume: { token: { title: "UME Token", disconnected: "disconnected", connected: "connected", connectedRenew: "connected (renew required)", expiresIn: "expires_in", needsRenew: "renew / 0s", preview: "preview", refreshStatus: "Refresh status", renewLogin: "Renew / login", renewing: "Renewing…", disconnect: "Disconnect token", disconnecting: "Disconnecting…", renewOkChanged: "Token renewed and updated", renewOkSame: "Renew completed (unchanged)", disconnectOk: "UME token disconnected", renewHint: "Token missing expiry or expired. Use Renew / login or wait for background keepalive.", }, subscription: { title: "UME alarm subscription (WebSocket)", help: "Subscriptions must be created/cancelled manually. After establish (or on restart if a valid subscription exists in DB), the backend auto-connects WSS for live alarms. Pause/resume on runtime task alarms_current_ws_consumer stops/resumes WSS only (does not cancel the UME subscription).", sub: "subscription", subActive: "active", subInactive: "inactive", wssPaused: "paused", wssModeBanner: "Current alarms are maintained via WSS (mode: {{mode}}); scheduled REST full sync is paused. Use Sync current alarms below for full reconcile.", serverLostBanner: "UME server subscription is missing or expired; local state may still show active. Clear local record and re-subscribe.", establish: "Establish subscription", establishing: "Establishing…", reestablish: "Clear local & re-subscribe", reestablishing: "Re-subscribing…", cancel: "Cancel subscription", cancelling: "Cancelling…", refreshStatus: "Refresh subscription", clearLocalOnly: "Clear local only", clearing: "Clearing…", opFailed: "Subscription operation failed", confirmClearDefault: "UME alarm subscription no longer exists or has expired.\n\nClear local subscription record? You can establish again afterwards.", confirmReestablish: "Clear local record and re-establish subscription on UME. Continue?", titleLoginFirst: "Log in to UME token first", titleReestablish: "Clear stale local record and re-establish on UME", establishInvalid: "Establish did not return a valid id/uri", establishExists: "Subscription already exists (WSS will stay/connect)", establishOk: "Subscription established; WSS will connect", clearLocalOk: "Local subscription cleared; you can establish again", serverLostMsg: "UME subscription lost. Clear local record and re-subscribe?", cancelFailed: "Cancel failed: subscription may still exist on UME", cancelOkMissing: "Local cleared (UME subscription was already missing)", cancelOk: "Subscription cancelled", wsLogs: "WSS runtime log", wsActivity: "Last activity {{time}}", wsAutoRefresh: "Auto refresh", wsLogsEmpty: "No logs yet (connect WSS after establishing subscription)", }, keyAlert: { title: "AI alarm monitor (OClaw WhatsApp)", showPanel: "Expand", hidePanel: "Collapse", help: "Exact match by notificationId, or substring match on alarm description (case-insensitive; nativeProbableCause, etc.). Optionally restrict to UME inventory ne_type values (multi-select; empty = all devices). Pushed via OClaw WebSocket. Shares NETX_OCLAW_ANALYZE_TOKEN.", ws: "OClaw WSS", wsConnected: "connected", wsDisconnected: "disconnected", wsDisabled: "disabled", wsPaused: "paused", publishedOk: "published ok", publishedFail: "published fail", queue: "queue", matchType: "Match by", matchNotificationId: "notificationId", matchKeyword: "description keyword", matchValuePhNotificationId: "Enter notificationId", matchValuePhKeyword: "Enter keyword (case-insensitive)", label: "Rule description", labelPh: "Required, e.g. BGP Down", neTypesLabel: "Device types (optional, multi-select)", neTypesHint: "Leave empty to match all device types. Options come from synced UME Inventory ne_type.", neTypesEmpty: "No device types yet — sync Inventory first", neTypesAll: "All", forwardOnClear: "Push on alarm clear", forwardOnClearHelp: "Global switch for all monitor rules. By default only new/updated alarms are pushed; when enabled, clear notifications are also sent when UME reports alarms cleared.", add: "Add rules", adding: "Adding…", delete: "Delete", edit: "Edit", editTitle: "Edit monitor rule", save: "Save", saving: "Saving…", cancel: "Cancel", editOk: "Rule updated", pickFromAlarms: "Load alarm hints", emptyRules: "No monitor rules yet", colType: "type", colMatch: "match", colLabel: "description", colNeTypes: "device types", colForwardClear: "on clear", colPublished: "published", colAttempts: "attempts", colLast: "last push", colActions: "actions", colMonitor: "monitor", monitorOn: "on", monitorOff: "off", filterKeywordPh: "Filter match / label", filterMatchAll: "All types", filterEnabledAll: "All states", filterEnabledOn: "Monitoring on", filterEnabledOff: "Monitoring off", addOk: "Monitor rule added", deleteOk: "Deleted", confirmDelete: "Delete this monitor rule?", yes: "yes", no: "no", }, sync: { title: "UME sync", inventory: "Sync inventory", alarmsCurrent: "Sync current alarms", full: "Full sync", failed: "Sync failed", }, tasks: { currentTitle: "Running jobs", running: "running", noRunning: "No running jobs", runtimeTitle: "Background tasks", runtimeOpFailed: "Background task operation failed", noRuntime: "No background task status", intervalTitle: "Configured sleep between loop iterations (clamped at process start)", lastRunTitle: "Last finished sync for scheduled tasks; refreshed at each loop tick", deletedTitle: "Reconcile deletes: inventory = NE rows; alarms_current = current alarm rows", lastErrorCol: "last_error", runtimeError: { startup_alarm_sync_before_ws: "Starting up: syncing current alarms (WSS connects after completion)…", startup_gate_waiting: "Startup REST snapshot pending; scheduled REST and WSS on standby", wss_active_skip_rest: "WSS receiving live alarms; REST sync skipped", pulling_alarms_current: "Pulling UME current alarms…", alarms_sync_in_progress_skip: "Another current-alarm REST sync in progress; skipped", pulling_inventory: "Pulling UME inventory…", ume_ws_disabled_no_base_url: "Disabled or UME_BASE_URL not configured", oclaw_fwd_disabled: "Disabled or NETX_OCLAW_ALARM_WS / token / url not configured", resumed_sync_soon: "Resumed: skipping debounce wait, sync soon", resumed_wss_reconnect: "Resumed: reconnecting WSS soon", resumed_oclaw_wss_reconnect: "Resumed: reconnecting OClaw WSS soon", resumed: "Resumed", keepalive_failed: "Token keepalive failed", }, wsState: { init: "Initializing", connected: "Connected", connecting: "Connecting", disconnected: "Disconnected", no_subscription: "No subscription", waiting_token: "Waiting for token", paused: "Paused", error: "Connection error", reconnecting: "Reconnecting", subscription_lost: "UME subscription lost", disabled_no_base_url: "Disabled or UME_BASE_URL not configured", }, fwdState: { paused: "Paused", disabled: "Disabled", connecting: "Connecting", connected: "Connected", }, runtimeTask: { token_keepalive: "UME token keepalive", alarms_current_auto_sync: "Current alarms auto sync", alarms_current_ws_consumer: "UME alarm WSS", oclaw_alarm_forwarder: "OClaw key-alert WSS", inventory_auto_sync: "Inventory auto sync", }, }, syncStatus: { title: "Sync history", showPanel: "Expand", hidePanel: "Collapse", empty: "No sync records", }, ne: { title: "Network elements", showPanel: "Expand", hidePanel: "Collapse", keywordPh: "keyword(ne_id/ne_name/user_label/ip/host_name)", clearTitle: "Clear keyword and reset to page 1", expand: "Expand details", collapse: "Collapse details", cliConnect: "CLI connect", }, tabs: { main: "UME sync", cli: "CLI connect", }, cli: { title: "UME CLI connection profile", showPanel: "Expand", hidePanel: "Collapse", hint: "Unified SSH credentials and hop/bastion for UME inventory NEs. OClaw MCP can exec show commands via ume_ne_id without per-device managed_ne entries.", statusReady: "CLI profile is ready for UME NE command execution.", statusNotReady: "Set target username and hop/bastion settings (if enabled).", profileName: "Profile name", targetUsername: "Target SSH username", targetPassword: "Target SSH password", passwordKeep: "Leave blank to keep unchanged", newProfile: "New profile", deleteConfirm: "Delete this CLI profile?", deleted: "Profile deleted", saved: "CLI profile saved", connectTestTitle: "Sample NE connectivity test", sampleNePh: "ume_ne_id (copy from NE list below)", sampleNeRequired: "ume_ne_id is required", connectTestSubmitted: "Connectivity test submitted", connectTestWaiting: "Waiting for test result…", connectTestRunning: "Connectivity test in progress…", connectTestResult: "Test result", existingProfiles: "Existing profiles", default: "default", }, alarms: { title: "Current alarms", showPanel: "Expand", hidePanel: "Collapse", keywordPh: "keyword(alarm key/cause/ne_name/host_name/ip…)", hostNamePh: "host_name (contains)", allSeverity: "All severities", clearedAll: "is_cleared: all", clearTitle: "Clear filters and reset to page 1", filterByHost: "Filter by this host", noHostName: "No host_name (sync inventory first)", }, }, webcrt: { title: "WebCRT", deviceList: "Device list", sessionManager: "Device list", connect: "Connect", hint: "Pick an NE and open an interactive terminal in the browser. Credentials stay on the server.", hintCrt: "Click an NE on the left to open a terminal. Includes managed and UME inventory; search + paging for large fleets.", filterKeyword: "Filter", filterKeywordPh: "Filter by name / IP / tag", empty: "No matching NEs. Switch source or refine the search.", open: "Open terminal", opening: "Connecting…", opened: "Opened {{name}}", disconnect: "Disconnect", closed: "Session closed", termPlaceholder: "No terminal session open", sessionMeta: "{{name}} · {{ip}}", source: { all: "All", managed: "Managed", ume: "UME", }, status: { idle: "Idle", connecting: "Connecting", connected: "Connected", error: "Error", closed: "Closed", }, actions: { reconnect: "Reconnect", recordLog: "Record log", clear: "Clear", copy: "Copy", recording: "Recording", recordingOn: "Log recording started", recordingSaved: "Log saved", copied: "Copied to clipboard", copyEmpty: "Terminal is empty", copyFailed: "Copy failed", }, err: { sessionLimit: "Session limit reached; close other terminals first", neNotFound: "Network element not found", credsIncomplete: "Incomplete username/password on this NE", cliProfile: "UME NEs require a CLI connect profile (default account / hop)", connectFailed: "Device login failed: {{detail}}", }, }, topology: { title: "Topology", maps: "Maps", newMap: "New", newMapName: "New topology", rename: "Rename", renamePrompt: "Topology map name", renameHint: "Double-click to rename", renamed: "Topology map renamed", deleteMap: "Delete map", deleteMapConfirm: "Delete map \"{{name}}\"? Nodes and edges will be removed.", emptyMaps: "No maps yet. Create one to start.", palette: "NE library", paletteHint: "Click to add to canvas", filterPh: "Filter name / IP", save: "Save", saving: "Saving…", saved: "Topology saved", discover: "Discover links", discovering: "Discovering…", discovered: "Discovery done: +{{added}} updated {{updated}} missing {{stale}}", discoverFail: "Discovery failed: {{detail}}", selectMap: "Select or create a topology map", canvasHint: "Add NEs from the left; blue dashed=found, red dashed=missing (deletable)", selected: "Selected", selectedEdge: "Selected edge", openWebcrt: "Open terminal", openNe: "NE details", removeNode: "Remove from canvas", removeEdge: "Delete edge", noNeLink: "Not linked to a managed NE", edgeManual: "Manual", edgeDiscovered: "Discovered", edgeStale: "Missing", fit: "Fit view", display: "Display", hideIp: "Hide IP", hideVendor: "Hide vendor", hidePorts: "Hide ports", edgeFlow: "Edge flow", collapseSidebar: "Collapse sidebar", expandSidebar: "Expand sidebar", hideAdded: "Hide added", paletteEmpty: "No matching NEs.", paletteHiddenAll: "Added NEs are hidden; uncheck to show them again.", paletteSource: "NE source", paletteManaged: "Managed", paletteUme: "UME", paletteLoading: "Loading…", discoverReport: "Discovery progress", discoverClose: "Close", discoverProgress: "Scanning {{count}} managed NE(s) on the map (serial LLDP; please wait)…", discoverProgressLive: "Scanning {{index}}/{{total}}: {{name}}", discoverSummary: "Scanned {{scanned}} · added {{added}} · updated {{updated}} · missing {{stale}} · failed {{failed}}", discoverNeOk: "neighbors {{neighbors}} · added {{added}} · updated {{updated}}", discoverNeFail: "Collect failed", discoverNoTargets: "No scannable NEs on the map. Add managed NEs, or UME NEs with a CLI connect profile.", removeStale: "Clear missing ({{count}})", removeStaleHint: "Delete red-marked missing edges", staleRemoved: "Removed {{count}} missing edge(s)", }, }; export default en;