The host has published every lifecycle transition on GET /api/v1/events since the API existed — client connect/disconnect, session and stream start/end, pairing decisions, display create/release, library, store and plugin changes — and nothing consumed a byte of it. The console instead polled ten endpoints on 1-5 s timers, so a change was up to 5 s stale and two pages could disagree while you looked at them. The Library page polled not at all: install a game in Steam and it never appeared until a full reload. The console now subscribes once and invalidates exactly the queries an event affects. Events never carry data into the cache — they only say "this is stale" — so an unknown future kind costs nothing and a missed event degrades to the polling that is still there underneath, now at a slow safety-net interval. The fast ticks that remain are the ones events cannot express: the live stream numbers while streaming, and a lingering display's teardown countdown. Four things had to be true for this to work, and none of them were. Each was found by measuring, not by reading: - Nitro's `localFetch` accumulates the response and only builds it when the handler returns, so nothing streams through the deployed Bun server. Three frames sent a second apart arrived together, three seconds late, when the upstream closed — and an SSE stream never closes, so nothing would ever have arrived. /api/v1/events gets its own route that hands back a web Response wrapping the upstream stream, which passes straight through. - Hydration mounts the app shell and discards it ~15 ms later. A subscription owned by that effect opened, closed, and never came back. It is a refcounted module singleton now, with a grace period so a remount re-attaches instead of reconnecting. - `getRouter()` runs more than once in the browser, and each call built its own QueryClient. The subscription held the first, the live pages read the second, and every invalidation went to a cache nobody was reading. One client per browser session; the server still gets a fresh one per request, which it must. - `invalidateQueries` only refetches queries that currently have an observer. An event means the HOST changed, so every cached copy is wrong whether or not something is watching it. Two features fall out of the same work: - **Automation** — a page for GET/PUT /api/v1/hooks. The host has run these hooks all along and the console never showed them, so the only way to see what your machine does when a stream starts was to open the config file. Writing one means writing a shell command the host will execute, so saving re-asks for the console password, like an update or an unreviewed install. - The Host page warns when another Moonlight-compatible server (Sunshine, Apollo) is running on the same machine. The host has detected this at startup for ages and reported it in /local/summary; nothing surfaced it. It is the most common reason a host looks installed and working but no client can reach it. Verified in a real browser against a mock host: three events drive three refetches of a query with no polling timer, the conflicts card names the intruder, the hook list and its dialog render, and the console reports no errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
528 lines
32 KiB
JSON
528 lines
32 KiB
JSON
{
|
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
|
"app_name": "Punktfunk",
|
|
"app_tagline": "management console",
|
|
"display_settings_saved": "Display configuration saved",
|
|
"nav_dashboard": "Dashboard",
|
|
"nav_host": "Host",
|
|
"nav_displays": "Virtual displays",
|
|
"nav_clients": "Paired clients",
|
|
"nav_pairing": "Pairing",
|
|
"nav_library": "Library",
|
|
"nav_automation": "Automation",
|
|
"automation_title": "Automation",
|
|
"automation_subtitle": "Run a command, or call a webhook, when something happens on this host.",
|
|
"automation_hooks_title": "Event hooks",
|
|
"automation_add": "Add hook",
|
|
"automation_empty": "No hooks yet. Add one to run something when a stream starts, a client pairs, or a game exits.",
|
|
"automation_edit": "Edit hook",
|
|
"automation_delete": "Delete hook",
|
|
"automation_delete_confirm": "Delete this hook?",
|
|
"automation_unsaved": "Unsaved changes",
|
|
"automation_saved": "Automation saved",
|
|
"automation_save_failed": "Could not save the automation.",
|
|
"automation_debounce_badge": "min {ms} ms apart",
|
|
"automation_hook_title": "Hook",
|
|
"automation_hook_help": "Pick the event, then what should happen. A trailing .* matches every event in that domain.",
|
|
"automation_hook_save": "Done",
|
|
"automation_field_on": "When",
|
|
"automation_field_on_help": "The event that fires this hook — the same names the host publishes on its event stream.",
|
|
"automation_field_action": "Then",
|
|
"automation_action_run": "Run a command",
|
|
"automation_action_webhook": "Call a webhook",
|
|
"automation_action_run_help": "Runs detached, as the host user, with the event JSON on stdin and PF_EVENT_* in the environment.",
|
|
"automation_action_webhook_help": "The event JSON is POSTed to this URL.",
|
|
"automation_field_hmac": "HMAC secret file (optional)",
|
|
"automation_field_hmac_help": "A private, operator-owned file holding the signing secret. The request carries X-Punktfunk-Signature so the receiver can verify it really came from this host.",
|
|
"automation_field_filter": "Only for a specific client or game",
|
|
"automation_filter_client": "Client name",
|
|
"automation_filter_app": "Game / app id",
|
|
"automation_field_debounce": "Minimum gap (ms)",
|
|
"automation_field_timeout": "Timeout (s)",
|
|
"automation_confirm_title": "Save automation?",
|
|
"automation_confirm_body": "These commands run on this machine, as the host user, whenever their event fires. Confirm with the console password.",
|
|
"nav_settings": "Settings",
|
|
"nav_more": "More",
|
|
"nav_plugins": "Plugins",
|
|
"plugin_offline_title": "This plugin isn't running",
|
|
"plugin_offline_hint": "Start the scripting runner, then retry.",
|
|
"plugin_retry": "Retry",
|
|
"plugin_open_new_tab": "Open in new tab",
|
|
"status_title": "Live status",
|
|
"status_video": "Video",
|
|
"status_audio": "Audio",
|
|
"status_streaming": "Streaming",
|
|
"status_idle": "Idle",
|
|
"status_session": "Session",
|
|
"status_sessions_active": "{count} active",
|
|
"status_no_session": "No active session",
|
|
"status_paired_count": "Paired clients",
|
|
"status_pin_pending": "Pairing PIN pending",
|
|
"stream_codec": "Codec",
|
|
"stream_resolution": "Resolution",
|
|
"stream_fps": "Frame rate",
|
|
"stream_bitrate": "Bitrate",
|
|
"action_stop_session": "Stop session",
|
|
"action_request_idr": "Request keyframe",
|
|
"action_unpair": "Unpair",
|
|
"host_identity": "Identity",
|
|
"host_hostname": "Hostname",
|
|
"host_os": "Operating system",
|
|
"host_local_ip": "Local IP",
|
|
"host_version": "Version",
|
|
"host_abi": "ABI version",
|
|
"host_codecs": "Codecs",
|
|
"host_ports": "Ports",
|
|
"host_uniqueid": "Unique ID",
|
|
"host_compositors": "Compositors",
|
|
"host_compositors_help": "Backends the host can drive a virtual output on. Pass an id to a client's --compositor flag; the host honors it if available, else auto-detects.",
|
|
"compositor_available": "Available",
|
|
"compositor_unavailable": "Unavailable",
|
|
"compositor_default": "Default",
|
|
"compositor_none": "This host has no compositor backends — they are a Linux concept. Virtual displays here run on the bundled pf-vdisplay driver.",
|
|
"host_gpus": "GPUs",
|
|
"host_gpus_help": "The GPU the host captures and encodes on. Automatic picks the best GPU; preferring one pins capture + encode to it. A change applies to the next session.",
|
|
"gpu_automatic": "Automatic",
|
|
"gpu_prefer": "Prefer",
|
|
"gpu_preferred": "Preferred",
|
|
"gpu_in_use": "In use · {backend}",
|
|
"gpu_next_session": "Next session",
|
|
"gpu_none": "No GPUs detected.",
|
|
"gpu_missing_warning": "The preferred GPU “{name}” is not present — automatic selection is used instead.",
|
|
"gpu_env_note": "PUNKTFUNK_RENDER_ADAPTER={value} pins the GPU while in automatic mode.",
|
|
"gpu_encoder_pin_note": "PUNKTFUNK_ENCODER={value} pins the encoder backend.",
|
|
"gpu_encoder_pin_warning": "PUNKTFUNK_ENCODER={value} pins a {vendor} encoder, but the next session's GPU is “{name}” — remove the stale pin from host.env.",
|
|
"host_conflicts_title": "Another game-streaming server is running on this machine",
|
|
"host_conflicts_help": "It listens on the same ports as punktfunk, so whichever one started first answers your clients — which is usually why a working-looking host cannot be connected to. Stop or uninstall the other server, then restart punktfunk.",
|
|
"host_displays": "Virtual displays",
|
|
"host_displays_help": "How virtual displays are created, kept alive, and arranged. Pick a preset, or choose Custom to set options directly. A change applies to the next session.",
|
|
"display_config_title": "Configuration",
|
|
"display_preset": "Preset",
|
|
"display_preset_custom": "Custom",
|
|
"display_preset_default": "Default",
|
|
"display_preset_gaming_rig": "Headless box",
|
|
"display_preset_shared_desktop": "Shared desktop",
|
|
"display_preset_hotdesk": "Hot-desk",
|
|
"display_preset_workstation": "Workstation",
|
|
"display_keep_alive": "Keep alive after disconnect",
|
|
"display_keep_alive_off": "Off",
|
|
"display_keep_alive_keep": "Keep for",
|
|
"display_keep_alive_forever": "Forever",
|
|
"display_keep_alive_seconds": "seconds",
|
|
"display_topology": "Topology",
|
|
"display_topology_auto": "Automatic",
|
|
"display_topology_extend": "Extend",
|
|
"display_topology_primary": "Primary",
|
|
"display_topology_exclusive": "Exclusive",
|
|
"display_max": "Max displays",
|
|
"display_save": "Save",
|
|
"display_effective": "In effect",
|
|
"display_pending_note": "Changes apply from the next connection — a streaming session keeps the display it opened on.",
|
|
"display_live": "Live displays",
|
|
"display_none_live": "No virtual displays right now.",
|
|
"display_state_active": "Active",
|
|
"display_state_lingering": "Lingering",
|
|
"display_state_pinned": "Pinned",
|
|
"display_release_btn": "Release",
|
|
"display_release_all": "Release all kept",
|
|
"display_expires_in": "tears down in {sec}s",
|
|
"display_sessions": "{count} streaming",
|
|
"display_arrange": "Arrange displays",
|
|
"display_arrange_help": "Set where each streamed display sits on the desktop, in pixels. Saving switches to a manual layout; it applies from the next connect.",
|
|
"display_arrange_save": "Save arrangement",
|
|
"display_custom_desc": "Set every option yourself.",
|
|
"display_preset_current": "Active",
|
|
"display_preset_soon": "coming soon",
|
|
"display_keep_alive_help": "Off tears the display down as soon as the client disconnects. Keep it alive (and, on gamescope, its game) so a quick reconnect resumes instantly instead of rebuilding.",
|
|
"display_topology_help": "How the streamed display fits into the host's desktop. Extend: an extra screen alongside your monitors. Primary: the streamed display becomes the primary output, your monitors kept. Exclusive: the streamed display is the sole output — physical monitors are disabled while streaming and restored after. On a headless host it just sets whether the streamed display is the primary.",
|
|
"display_conflict": "When another client connects",
|
|
"display_conflict_help": "What happens if a second client connects while one is already streaming and asks for a different resolution.",
|
|
"display_conflict_separate": "Own display",
|
|
"display_conflict_steal": "Take over",
|
|
"display_conflict_join": "Share view",
|
|
"display_conflict_reject": "Busy — reject",
|
|
"display_identity": "Per-client identity",
|
|
"display_identity_help": "Whether the streamed display carries a stable per-client identity, so the host's desktop remembers that client's per-monitor settings (scaling, resolution) and reapplies them when it reconnects. Shared: one identity for everyone. Per client: each device keeps its own. Per client + resolution: a device keeps separate settings per resolution it connects at.",
|
|
"display_game_session": "Dedicated game sessions",
|
|
"display_game_session_help": "How a session that launches a game from the library is served. “Dedicated” always gives the launch its own headless gamescope at your exact resolution — the game boots straight in, no Steam Big Picture, no game mode. “Auto” uses whatever session the box is in. gamescope must be installed; otherwise Dedicated falls back to Auto.",
|
|
"display_game_session_auto": "Auto",
|
|
"display_game_session_dedicated": "Dedicated",
|
|
"display_experimental": "Experimental",
|
|
"display_ddc": "Turn monitors off while streaming (DDC/CI)",
|
|
"display_ddc_help": "Windows only, takes effect with Exclusive topology. Before disabling your physical monitors, the host also tells them to power their panel off over the DDC/CI monitor-control channel, and wakes them again when the stream ends. This can eliminate a periodic stutter some setups see when the streamed display is the only active one (the dark monitor keeps probing its inputs; a panel that was told to sleep doesn't). Monitors without DDC/CI support are skipped — if your monitor doesn't wake up afterwards, press its power button once and turn this off.",
|
|
"display_ddc_disabled": "Off",
|
|
"display_ddc_enabled": "On",
|
|
"display_ddc_badge": "Monitors off via DDC/CI",
|
|
"display_pnp": "Disable monitor devices while streaming (PnP)",
|
|
"display_pnp_help": "Windows only, takes effect with Exclusive topology. On top of removing your physical monitors from the desktop, the host disables their Windows device entries for the duration of the stream and re-enables them afterwards. A standby monitor or TV that keeps waking its connection (auto input scan, instant-on) can no longer interrupt the stream — Windows ignores its wake events entirely while the device is disabled. If the host crashes mid-stream, the monitors are re-enabled the next time it starts; until then you can re-enable them manually in Device Manager.",
|
|
"display_pnp_disabled": "Off",
|
|
"display_pnp_enabled": "On",
|
|
"display_pnp_badge": "Monitor devices disabled (PnP)",
|
|
"display_identity_shared": "Shared",
|
|
"display_identity_per_client": "Per client",
|
|
"display_identity_per_client_mode": "Per client + resolution",
|
|
"display_layout_mode": "Multi-monitor layout",
|
|
"display_layout_help": "Auto lays displays out side by side, left to right. Manual: you position each one yourself — a per-display X/Y editor appears in the Live displays section below once two or more are streaming.",
|
|
"display_layout_auto_row": "Auto (side by side)",
|
|
"display_layout_manual": "Manual",
|
|
"display_preset_custom_label": "Custom presets",
|
|
"display_preset_save_as": "Save as preset…",
|
|
"display_preset_name": "Preset name",
|
|
"display_preset_edit": "Rename",
|
|
"display_preset_update": "Update to current settings",
|
|
"display_preset_delete": "Delete",
|
|
"display_preset_delete_confirm": "Delete this custom preset?",
|
|
"display_custom_title": "Custom settings",
|
|
"display_unsaved": "Unsaved changes",
|
|
"display_unsaved_hint": "These options only take effect once you save.",
|
|
"display_all_saved": "All changes saved",
|
|
"display_revert": "Discard changes",
|
|
"display_discard_confirm": "You have unsaved custom settings. Discard them?",
|
|
"clients_title": "Paired clients",
|
|
"clients_empty": "No paired clients yet.",
|
|
"clients_name": "Name",
|
|
"clients_fingerprint": "Fingerprint",
|
|
"clients_unpair_confirm": "Unpair this client? It will need to pair again to connect.",
|
|
"pairing_title": "Pairing",
|
|
"pairing_idle": "No pairing in progress. Start pairing from a Moonlight client, then enter its PIN here.",
|
|
"pairing_waiting": "A client is waiting to pair. Enter the PIN it shows:",
|
|
"pairing_pin_label": "PIN",
|
|
"pairing_submit": "Submit PIN",
|
|
"pairing_pin_sent": "PIN sent. If it's correct, the client finishes pairing and appears under Paired devices below.",
|
|
"pairing_failed": "Couldn't submit the PIN — check it's correct and that the client is still waiting to pair.",
|
|
"pairing_native_title": "Pair a device",
|
|
"pairing_native_desc": "Show a one-time PIN here, then enter it in your Punktfunk app to pair this device.",
|
|
"pairing_native_disabled": "The native host isn't running. Start it with `serve --native` to pair Punktfunk devices.",
|
|
"pairing_native_arm": "Pair a device",
|
|
"pairing_native_enter": "Enter this PIN on your device:",
|
|
"pairing_native_expires": "Expires in",
|
|
"pairing_native_cancel": "Cancel",
|
|
"pairing_native_devices": "Paired devices",
|
|
"pairing_native_empty": "No devices paired yet.",
|
|
"pairing_native_unpair_confirm": "Unpair this device? It will need to pair again to connect.",
|
|
"pairing_protocol": "Protocol",
|
|
"pairing_protocol_native": "punktfunk/1",
|
|
"pairing_protocol_moonlight": "Moonlight",
|
|
"pairing_pending_title": "Waiting for approval",
|
|
"pairing_pending_desc": "These devices tried to connect. Approving pairs a device immediately — no PIN needed.",
|
|
"pairing_pending_approve": "Approve",
|
|
"pairing_pending_deny": "Deny",
|
|
"pairing_pending_name_prompt": "Name this device:",
|
|
"pairing_pending_age_just_now": "just now",
|
|
"pairing_pending_age_secs": "{s}s ago",
|
|
"pairing_pending_age_mins": "{min} min ago",
|
|
"pairing_moonlight_title": "Moonlight (GameStream) pairing",
|
|
"library_title": "Library",
|
|
"library_empty": "No games found yet.",
|
|
"library_sources_title": "Game sources",
|
|
"library_sources_help": "Launchers this host scans for installed games. Turn one off to hide its games from every device — nothing is deleted, and turning it back on brings them right back.",
|
|
"library_sources_failed": "Could not update the game source.",
|
|
"library_store_steam": "Steam",
|
|
"library_store_custom": "Custom",
|
|
"library_add_title": "Add a custom game",
|
|
"library_edit_title": "Edit custom game",
|
|
"library_add_button": "Add custom game",
|
|
"library_field_title": "Title",
|
|
"library_field_portrait": "Portrait art URL",
|
|
"library_field_hero": "Hero art URL",
|
|
"library_field_header": "Header art URL",
|
|
"library_field_logo": "Logo art URL",
|
|
"library_field_command": "Launch command",
|
|
"library_field_command_help": "Optional. The command the host runs to launch this title.",
|
|
"library_field_platform": "Platform",
|
|
"library_field_platform_help": "The system this title runs on, e.g. PS2, Xbox 360, SNES, PC.",
|
|
"library_field_description": "Description",
|
|
"library_field_developer": "Developer",
|
|
"library_field_publisher": "Publisher",
|
|
"library_field_release_year": "Release year",
|
|
"library_field_genres": "Genres",
|
|
"library_field_genres_help": "Comma-separated, e.g. RPG, Platformer.",
|
|
"library_field_tags": "Tags",
|
|
"library_field_tags_help": "Comma-separated labels for organizing, e.g. co-op, kids.",
|
|
"library_field_region": "Region",
|
|
"library_field_region_help": "e.g. NTSC-U, PAL, NTSC-J.",
|
|
"library_field_players": "Players",
|
|
"library_details_legend": "Details (optional)",
|
|
"library_owned_by": "via {provider}",
|
|
"library_save": "Save",
|
|
"library_create": "Add",
|
|
"library_cancel": "Cancel",
|
|
"library_edit": "Edit",
|
|
"library_delete": "Delete",
|
|
"library_delete_confirm": "Delete this custom game? This can't be undone.",
|
|
"settings_title": "Settings",
|
|
"settings_token_label": "API token",
|
|
"settings_token_help": "Bearer token for the management API. Leave empty for a loopback host with no token.",
|
|
"settings_language": "Language",
|
|
"settings_save": "Save",
|
|
"settings_saved": "Saved.",
|
|
"common_loading": "Loading…",
|
|
"common_error": "Something went wrong.",
|
|
"common_retry": "Retry",
|
|
"common_yes": "Yes",
|
|
"common_cancel": "Cancel",
|
|
"common_unauthorized": "Session expired — redirecting to sign in…",
|
|
"login_title": "Sign in",
|
|
"login_subtitle": "Enter the management password to continue. Don't know what to do?",
|
|
"login_docs_link": "Visit the documentation",
|
|
"login_password": "Password",
|
|
"login_submit": "Sign in",
|
|
"login_error": "Wrong password.",
|
|
"login_signing_in": "Signing in…",
|
|
"action_logout": "Sign out",
|
|
"settings_logout_failed": "Sign-out failed — you're still signed in. Please try again.",
|
|
"nav_stats": "Performance",
|
|
"nav_logs": "Logs",
|
|
"logs_title": "Logs",
|
|
"logs_subtitle": "The host's recent log stream — follow live, filter by level, search.",
|
|
"logs_follow": "Follow",
|
|
"logs_pause": "Pause",
|
|
"logs_clear": "Clear",
|
|
"logs_search": "Search logs…",
|
|
"logs_empty": "No log entries match — adjust the filter or wait for host activity.",
|
|
"logs_dropped": "Some entries were evicted before they could be fetched",
|
|
"logs_download": "Download logs",
|
|
"logs_stalled": "Log polling failed — showing the last lines received.",
|
|
"logs_share": "Share logs",
|
|
"logs_copy": "Copy logs to clipboard",
|
|
"logs_copied": "Logs copied to clipboard",
|
|
"logs_share_failed": "Couldn't share the logs",
|
|
"stats_title": "Performance",
|
|
"stats_subtitle": "Record a session's pipeline timings and review them as graphs.",
|
|
"stats_capture_title": "Capture",
|
|
"stats_capture_desc": "Arm capture, run a session, then stop to save a recording. Sampling runs at the host's aggregation boundary — no per-frame overhead.",
|
|
"stats_recording": "Recording",
|
|
"stats_idle": "Idle",
|
|
"stats_start": "Start capture",
|
|
"stats_stop": "Stop & save",
|
|
"stats_elapsed": "Elapsed",
|
|
"stats_samples": "Samples",
|
|
"stats_kind": "Path",
|
|
"stats_kind_native": "Native",
|
|
"stats_kind_gamestream": "GameStream",
|
|
"stats_live_title": "Live",
|
|
"stats_live_waiting": "Armed — waiting for the first samples. Start a session to begin recording.",
|
|
"stats_latency_title": "Latency by stage",
|
|
"stats_latency_axis": "µs",
|
|
"stats_latency_desc": "Per-stage pipeline time, stacked — the \"where does the time go\" view.",
|
|
"stats_throughput_title": "Throughput",
|
|
"stats_health_title": "Health",
|
|
"stats_fps_new": "New fps",
|
|
"stats_fps_repeat": "Repeat fps",
|
|
"stats_mbps": "Mb/s",
|
|
"stats_bitrate_target": "Target Mb/s",
|
|
"stats_health_gamestream_note": "Packet loss, send-drops, and FEC recovery are receiver-side metrics the GameStream path doesn't instrument — only frame drops are shown here.",
|
|
"stats_p99": "p99",
|
|
"stats_p50": "p50",
|
|
"stats_frames_dropped": "Frames dropped",
|
|
"stats_packets_dropped": "Packets dropped",
|
|
"stats_send_dropped": "Send drops",
|
|
"stats_fec_recovered": "FEC recovered",
|
|
"stats_recordings_title": "Recordings",
|
|
"stats_recordings_empty": "No recordings yet. Start a capture to record one.",
|
|
"stats_col_time": "Time",
|
|
"stats_col_kind": "Path",
|
|
"stats_col_resolution": "Resolution",
|
|
"stats_col_codec": "Codec",
|
|
"stats_col_duration": "Duration",
|
|
"stats_col_samples": "Samples",
|
|
"stats_view": "View",
|
|
"stats_download": "Download",
|
|
"stats_delete": "Delete",
|
|
"stats_delete_confirm": "Delete this recording? This can't be undone.",
|
|
"stats_detail_title": "Recording detail",
|
|
"stats_close": "Close",
|
|
"stats_no_samples": "This recording has no samples.",
|
|
"store_title": "Plugins",
|
|
"store_subtitle": "Browse the plugin catalog, install and remove plugins, and choose which catalogs this host trusts.",
|
|
"store_tab_browse": "Browse",
|
|
"store_tab_installed": "Installed",
|
|
"store_tab_sources": "Sources",
|
|
"store_tier_verified": "Verified",
|
|
"store_tier_verified_hint": "From the built-in unom catalog — unom reviewed this exact package.",
|
|
"store_tier_external": "External source",
|
|
"store_tier_external_hint": "Pinned and integrity-checked, but curated by someone other than unom. Nobody at unom reviewed this code.",
|
|
"store_tier_unverified": "Unverified",
|
|
"store_tier_unverified_hint": "Installed from a raw package spec. No catalog, no review — nobody checked this code.",
|
|
"store_tier_cli": "Installed via CLI",
|
|
"store_tier_cli_hint": "Installed from the command line, so the host has no record of where it came from.",
|
|
"store_from_source": "from",
|
|
"store_search_placeholder": "Search plugins…",
|
|
"store_filter_all": "All sources",
|
|
"store_empty": "No plugins in the catalog yet.",
|
|
"store_no_match": "No plugin matches your search.",
|
|
"store_by_author": "by {author}",
|
|
"store_homepage": "Homepage",
|
|
"store_install": "Install",
|
|
"store_installed_label": "Installed",
|
|
"store_update_to": "Update to {version}",
|
|
"store_incompatible": "Not compatible with this host.",
|
|
"store_blocked": "Blocked by the host: {reason}",
|
|
"store_spec_open": "Install from package spec…",
|
|
"store_runner_title": "Plugin runner",
|
|
"store_runner_help": "The service every plugin runs inside. Disabling it stops all plugins at once without uninstalling anything.",
|
|
"store_runner_not_installed": "The plugin runner isn't installed on this host, so plugins can't start.",
|
|
"store_runner_banner": "The plugin runner is switched off — plugins won't start until you enable it.",
|
|
"store_runner_enable": "Enable runner",
|
|
"store_runner_disable": "Disable runner",
|
|
"store_runner_state_running": "Running",
|
|
"store_runner_state_stopped": "Stopped",
|
|
"store_runner_state_disabled": "Disabled",
|
|
"store_runner_state_missing": "Not installed",
|
|
"store_runner_unit": "Service",
|
|
"store_runner_principal": "Runs as",
|
|
"store_runner_failed": "Could not change the plugin runner.",
|
|
"store_installed_title": "Installed plugins",
|
|
"store_installed_empty": "No plugins installed yet.",
|
|
"store_running": "Running",
|
|
"store_stopped": "Not running",
|
|
"store_uninstall": "Uninstall",
|
|
"store_uninstall_confirm": "Uninstall {title}? You can install it again from the catalog.",
|
|
"store_uninstall_failed": "Could not start the removal.",
|
|
"store_update_no_entry": "That plugin isn't in any catalog right now — refresh the sources and try again.",
|
|
"store_sources_title": "Catalog sources",
|
|
"store_sources_help": "Where this host looks for plugins. The built-in unom catalog is always present; every other source is one you added and vouch for yourself.",
|
|
"store_refresh_all": "Refresh all",
|
|
"store_refresh_failed": "Could not refresh the catalogs.",
|
|
"store_source_builtin": "Built in",
|
|
"store_source_signed": "Signed",
|
|
"store_source_unsigned": "Unsigned",
|
|
"store_source_stale": "Out of date",
|
|
"store_source_entries": "{count} plugins",
|
|
"store_source_fetched": "last fetched {when}",
|
|
"store_source_never": "never",
|
|
"store_source_remove": "Remove source",
|
|
"store_source_remove_confirm": "Remove the source “{name}”? Plugins already installed from it stay installed.",
|
|
"store_source_remove_failed": "Could not remove the source.",
|
|
"store_source_builtin_locked": "The built-in unom catalog can't be removed.",
|
|
"store_add_source_title": "Add a catalog source",
|
|
"store_field_source_name": "Name",
|
|
"store_field_source_url": "Index URL",
|
|
"store_field_source_key": "Public key (optional)",
|
|
"store_field_source_key_help": "An ed25519:… key. With a key set, the host only accepts a signed index from this source.",
|
|
"store_add_source": "Add source",
|
|
"store_add_source_failed": "Could not save the source — check the name and the URL.",
|
|
"store_source_trust_title": "Trust this source?",
|
|
"store_source_trust_body": "Everything you install from “{name}” is code unom has not reviewed. It runs on this host with the plugin runner's privileges. Only add a catalog whose operator you trust.",
|
|
"store_source_trust_unsigned": "Without a public key the host can't tell whether this index was tampered with in transit.",
|
|
"store_source_password": "Console password",
|
|
"store_source_trust_confirm": "I understand — add the source",
|
|
"store_install_title": "Install {title}?",
|
|
"store_install_verified_body": "Version {version} from the built-in unom catalog. unom reviewed this exact package.",
|
|
"store_install_confirm": "Install",
|
|
"store_install_external_title": "Install {title} from an external source?",
|
|
"store_install_external_body": "Version {version} comes from “{source}”, a catalog you added yourself.",
|
|
"store_install_external_note": "unom has not reviewed this code. The package is pinned and integrity-checked, but it will run on this host with the plugin runner's privileges.",
|
|
"store_install_external_confirm": "Install anyway",
|
|
"store_install_failed": "Could not start the install.",
|
|
"store_busy": "The host is already installing or removing a plugin. Try again once it's finished.",
|
|
"store_spec_title": "Install from a package spec",
|
|
"store_spec_lead": "This installs code straight from a package registry. It is in no catalog, nobody has reviewed it, and it will run on this host with the plugin runner's privileges — the same access it has to your files and your session.",
|
|
"store_spec_permanent": "A plugin installed this way stays marked Unverified for as long as it is installed.",
|
|
"store_spec_field": "Package spec",
|
|
"store_spec_field_help": "For example @scope/plugin-name@1.2.3.",
|
|
"store_spec_confirm_field": "Type the package spec again to confirm",
|
|
"store_spec_checkbox": "I understand that this runs unreviewed code with operator privileges.",
|
|
"store_spec_confirm": "Install unverified",
|
|
"store_spec_password": "Console password",
|
|
"store_spec_password_help": "Running unreviewed code needs the password again — a browser session on its own can't do this.",
|
|
"store_job_install": "Installing {target}",
|
|
"store_job_uninstall": "Removing {target}",
|
|
"store_job_done_install": "Installed.",
|
|
"store_job_done_uninstall": "Removed.",
|
|
"store_job_failed": "The job failed.",
|
|
"store_job_restarting": "The plugin runner is restarting — the sidebar catches up in a moment.",
|
|
"store_job_lost": "Lost track of this job",
|
|
"store_job_lost_hint": "The host restarted while it ran. Check the Installed tab to see whether it finished.",
|
|
"store_job_log": "Show log",
|
|
"store_job_dismiss": "Dismiss",
|
|
"store_phase_queued": "Queued",
|
|
"store_phase_verifying": "Verifying the package",
|
|
"store_phase_installing": "Installing",
|
|
"store_phase_removing": "Removing",
|
|
"store_phase_checking": "Checking the install",
|
|
"store_phase_rolling_back": "Rolling back",
|
|
"store_phase_recording": "Recording provenance",
|
|
"store_phase_restarting": "Restarting the plugin runner",
|
|
"store_phase_done": "Done",
|
|
"games_title": "Running games",
|
|
"games_state_launching": "Starting",
|
|
"games_state_running": "Running",
|
|
"games_state_exited": "Ended",
|
|
"games_state_grace": "Waiting for client",
|
|
"games_closing_in": "Its client is gone — closing in {time} unless it comes back",
|
|
"games_end_all_waiting_confirm": "This game has no id the host can single out, so ending it now ends all {count} games waiting to close. Continue?",
|
|
"action_stop_session_all_confirm": "The host has one stop, and it ends every live session — all {count} of them, not just this one. Continue?",
|
|
"games_end_now": "End now",
|
|
"session_game_title": "When a game or a session ends",
|
|
"session_game_help": "A streaming session and the game it launched can share a fate. These settings are about the game; the keep-alive above is about the display, and the two have separate timers.",
|
|
"session_game_on_exit": "When the game exits",
|
|
"session_game_on_exit_help": "Quitting the game hands the client back to its own library instead of leaving it on your desktop. Turn this off if you stream the desktop and treat the game as incidental.",
|
|
"session_game_on_exit_end": "End the session",
|
|
"session_game_on_exit_keep": "Keep streaming",
|
|
"session_game_end_game": "When the session ends",
|
|
"session_game_end_game_help": "Whether stopping (or losing) a session also closes the game it launched. Never applies to a game you started yourself — only one this host launched for the session.",
|
|
"session_game_end_keep": "Leave it running",
|
|
"session_game_end_on_quit": "Close it on Stop",
|
|
"session_game_end_always": "Always close it",
|
|
"session_game_always_warning": "Closing a game costs whatever it had not saved. The host asks it to close first and only forces the issue if it refuses — but a network drop is not someone pressing Stop, so a dropped client gets the reconnect window below before anything happens. A display kept forever stays up regardless; this setting governs the game, not the screen.",
|
|
"session_game_grace": "Reconnect window",
|
|
"session_game_grace_help": "How long a client that vanished has to come back before its game is closed. The console shows the countdown, and reconnecting cancels it.",
|
|
"session_game_saved": "Session and game settings saved",
|
|
"session_game_inert": "This host has no way to launch games, so these settings do nothing here",
|
|
"session_game_nested_note": "On a gamescope game session the game runs *inside* the streamed display, so it lives exactly as long as that display does — which is what Keep alive above decides, not this setting. A deliberate Stop tears that display down at once and takes the game with it, whichever option you pick here.",
|
|
"display_monitor_title": "Streamed screen",
|
|
"display_monitor_intro": "By default the host creates its own virtual screen for each client, sized to that client. Instead, you can stream one of the screens this computer already has — what you see on that monitor is what the client sees.",
|
|
"display_monitor_virtual": "Virtual screen (default)",
|
|
"display_monitor_virtual_hint": "Each client gets its own screen at its own resolution.",
|
|
"display_monitor_mirror_hint": "Every client sees this monitor, at its resolution.",
|
|
"display_monitor_none": "This host reports no monitors.",
|
|
"display_monitor_unavailable": "Monitors could not be listed on this host.",
|
|
"display_monitor_env_locked": "Pinned by PUNKTFUNK_CAPTURE_MONITOR on this host — unset it to choose here.",
|
|
"display_monitor_unsupported": "Streaming one of these screens isn't supported on this host yet — it's available on Linux hosts only. The screens are listed so you can see what this computer has; clients keep getting their own virtual screen.",
|
|
"display_monitor_primary": "primary",
|
|
"display_monitor_disabled": "off",
|
|
"display_monitor_saved": "Streamed screen saved",
|
|
"update_title": "Updates",
|
|
"update_available_badge": "Update available",
|
|
"update_current": "Installed",
|
|
"update_install_kind": "Install type",
|
|
"update_latest": "Latest release",
|
|
"update_notes": "Release notes",
|
|
"update_up_to_date": "You're up to date.",
|
|
"update_how": "Update this host with:",
|
|
"update_check_now": "Check now",
|
|
"update_checking": "Checking…",
|
|
"update_last_checked": "Last checked",
|
|
"update_never_checked": "Not checked yet",
|
|
"update_stale": "The update feed hasn't changed in over 45 days — checks succeed but nothing new arrives. If that seems wrong, check this host's connectivity to git.unom.io.",
|
|
"update_disabled": "Update checks are disabled on this host (PUNKTFUNK_UPDATE_CHECK=0).",
|
|
"update_error": "Last check failed:",
|
|
"update_copy": "Copy",
|
|
"update_copied": "Copied",
|
|
"update_apply_ready": "Version {version} can be installed from here — the host downloads, verifies, and restarts itself.",
|
|
"update_apply_button": "Update now",
|
|
"update_apply_confirm_title": "Update the host to {version}?",
|
|
"update_apply_confirm_body": "Confirm with the console password. The host will restart at the end; this page reconnects by itself.",
|
|
"update_apply_force_warning": "A streaming session is live right now — updating will drop it (whoever is playing gets cut off). If that stream is how you're viewing this page, you'll lose it too.",
|
|
"update_apply_force_button": "Update anyway (drops the stream)",
|
|
"update_apply_password_label": "Console password",
|
|
"update_apply_wrong_password": "Wrong password.",
|
|
"update_apply_throttled": "Too many attempts — wait a moment and try again.",
|
|
"update_apply_working": "Starting…",
|
|
"update_apply_timeout": "The host hasn't come back yet. It may still be installing — if this persists, check the service on the machine and the installer log under the punktfunk data directory (logs\\update-*.log).",
|
|
"update_applying_title": "Updating to {version}",
|
|
"update_stage_downloading": "Downloading the installer… {pct}%",
|
|
"update_stage_downloading_indeterminate": "Downloading the installer…",
|
|
"update_stage_verifying": "Verifying the download…",
|
|
"update_stage_applying": "Handing over to the installer…",
|
|
"update_stage_restarting": "The host is restarting…",
|
|
"update_stage_reconnecting": "Waiting for the host to come back…",
|
|
"update_result_ok": "Updated {from} → {to}.",
|
|
"update_result_staged": "Update to {to} is staged — reboot this host to finish.",
|
|
"update_result_noop": "Your package source had nothing newer yet — try again in a few minutes.",
|
|
"update_opt_in": "To enable one-click updates from here, run this once on the host (then log out and back in):",
|
|
"update_result_failed": "Update to {to} failed during {stage}.",
|
|
"update_result_log": "Installer log:"
|
|
}
|