Compare commits

..

2 Commits

Author SHA1 Message Date
Marc Brooks ad188365b2
Merge e6fdd4a73b into 74e64f69a7 2025-10-16 21:26:45 -05:00
Marc Brooks e6fdd4a73b
Add inlang/paraglide-js localization
Remove the temporary directory after extracting buildkit
Localize the extension popovers.
Update package and fix tsconfig.json
Expand development directory guide
Move messages under localization
Popovers and sidebar
Update Chinese translations
Accidentally lost the changes that @ym provided, brought them back
File formatting pass
Localized all components, hooks, providers, hooks
Localize all pages except Settings
Bump packages
Settings Access page
Settings local auth page
Fix ref lint warning
Settings Advanced page
Fix UI lint warnings there were a bunch of ref and useEffect violations.
Settings appearance page
Settings general pages
Settings hardware page
Settings keyboard page
Settings macros pages
Settings mouse page
Settings page
Settings video page
Settings network page
Fix compilation issues
Ran machine translate
Use getLocale for date, relative time, and money formatting
Fix eslint
Delete unused messages
Added setting to choose locale
Merged in dev hotfix
Fix update status rendering
2025-10-16 21:26:31 -05:00
30 changed files with 933 additions and 541 deletions

View File

@ -15,5 +15,36 @@
"containerUser": "vscode",
"containerEnv": {
"HOME": "/home/vscode"
},
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached"
],
"onCreateCommand": ".devcontainer/install-deps.sh",
"customizations": {
"vscode": {
"extensions": [
// coding styles
"chrislajoie.vscode-modelines",
"editorconfig.editorconfig",
// GitHub
"GitHub.vscode-pull-request-github",
"github.vscode-github-actions",
// Golang
"golang.go",
// C / C++
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
// CMake / Makefile
"ms-vscode.makefile-tools",
"ms-vscode.cmake-tools",
// Frontend
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss",
"codeandstuff.package-json-upgrade",
// Localization
"inlang.vs-code-extension"
]
}
}
}

2
.gitignore vendored
View File

@ -13,3 +13,5 @@ node_modules
# generated during the build process
#internal/native/include
#internal/native/lib
ui/reports

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
import json
from pathlib import Path
messages_dir = Path(__file__).resolve().parent.parent / 'ui' / 'localization' / 'messages'
files = list(messages_dir.glob('*.json'))
for f in files:
data = json.loads(f.read_text(encoding='utf-8'))
# Keep $schema first if present
schema = None
if '$schema' in data:
schema = data.pop('$schema')
sorted_items = dict(sorted(data.items()))
if schema is not None:
out = {'$schema': schema}
out.update(sorted_items)
else:
out = sorted_items
f.write_text(json.dumps(out, ensure_ascii=False, indent=4) + '\n', encoding='utf-8')
print(f'Processed {len(files)} files in {messages_dir}')

View File

@ -31,7 +31,7 @@
"access_no_device_id": "Intet enheds-ID tilgængeligt",
"access_private_key_description": "Af sikkerhedsmæssige årsager vil den ikke blive vist efter lagring.",
"access_private_key_label": "Privat nøgle",
"access_provider_custom": "Skik",
"access_provider_custom": "Tilpasset",
"access_provider_jetkvm": "JetKVM Cloud",
"access_remote_description": "Administrer tilstanden for fjernadgang til enheden",
"access_security_encryption": "End-to-end-kryptering ved hjælp af WebRTC (DTLS og SRTP)",
@ -42,15 +42,14 @@
"access_title": "Adgang",
"access_tls_certificate_description": "Indsæt dit TLS-certifikat nedenfor. For certifikatkæder skal du inkludere hele kæden (blad-, mellem- og rodcertifikater).",
"access_tls_certificate_title": "TLS-certifikat",
"access_tls_custom": "Skik",
"access_tls_disabled": "Handicappet",
"access_tls_custom": "Tilpasset",
"access_tls_disabled": "Deaktiveret",
"access_tls_self_signed": "Selvsigneret",
"access_tls_updated": "TLS-indstillingerne er blevet opdateret",
"access_update_tls_settings": "Opdater TLS-indstillinger",
"action_bar_connection_stats": "Forbindelsesstatistik",
"action_bar_extension": "Udvidelse",
"action_bar_fullscreen": "Fuldskærm",
"action_bar_paste_text": "Indsæt tekst",
"action_bar_settings": "Indstillinger",
"action_bar_virtual_keyboard": "Virtuelt tastatur",
"action_bar_virtual_media": "Virtuelle medier",
@ -116,7 +115,7 @@
"atx_power_control_get_state_error": "Kunne ikke hente ATX-strømtilstand: {error}",
"atx_power_control_hdd_led": "HDD-LED",
"atx_power_control_long_power_button": "Langt tryk",
"atx_power_control_power_button": "Magt",
"atx_power_control_power_button": "Strøm",
"atx_power_control_power_led": "Strøm-LED",
"atx_power_control_reset_button": "Nulstil",
"atx_power_control_send_action_error": "Kunne ikke sende ATX-strømfunktion {action} : {error}",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "Bekræft din adgangskode",
"auth_mode_local_password_confirm_label": "Bekræft adgangskode",
"auth_mode_local_password_description": "Sikr din enhed med en adgangskode for ekstra beskyttelse.",
"auth_mode_local_password_do_not_match": "Adgangskoderne stemmer ikke overens",
"auth_mode_local_password_failed_set": "Kunne ikke angive adgangskode: {error}",
"auth_mode_local_password_note": "Denne adgangskode vil blive brugt til at sikre dine enhedsdata og beskytte mod uautoriseret adgang.",
"auth_mode_local_password_note_local": "Alle data forbliver på din lokale enhed.",
@ -156,8 +154,8 @@
"auth_signup_create_account_description": "Opret din konto, og begynd nemt at administrere dine enheder.",
"back": "Tilbage",
"back_to_devices": "Tilbage til Enheder",
"cancel": "Ophæve",
"close": "Tæt",
"cancel": "Annuller",
"close": "Luk",
"cloud_kvms": "Cloud KVM'er",
"cloud_kvms_description": "Administrer dine cloud-KVM'er, og opret forbindelse til dem sikkert.",
"cloud_kvms_no_devices": "Ingen enheder fundet",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "Rundturstid",
"connection_stats_round_trip_time_description": "Rundrejsetid for det aktive ICE-kandidatpar mellem peers.",
"connection_stats_sidebar": "Forbindelsesstatistik",
"connection_stats_unit_frames_per_second": " fps",
"connection_stats_unit_milliseconds": " ms",
"connection_stats_unit_packets": " pakker",
"connection_stats_video": "Video",
"connection_stats_video_description": "Videostreamen fra JetKVM'en til klienten.",
"continue": "Fortsætte",
@ -188,7 +189,7 @@
"dc_power_control_current": "Strøm",
"dc_power_control_current_unit": "EN",
"dc_power_control_get_state_error": "Kunne ikke hente DC-strømtilstand: {error}",
"dc_power_control_power": "Magt",
"dc_power_control_power": "Strøm",
"dc_power_control_power_off_button": "Sluk",
"dc_power_control_power_off_state": "Sluk",
"dc_power_control_power_on_button": "Tænd",
@ -207,6 +208,8 @@
"deregister_error": "Der opstod en fejl {status} under afregistreringen af din enhed. Prøv igen.",
"deregister_headline": "Afregistrér {device} fra din cloud-konto",
"detach": "Løsrive",
"dhcp_empty_lease_description": "Vi har endnu ikke modtaget nogen DHCP-leaseoplysninger fra enheden.",
"dhcp_empty_lease_headline": "Ingen DHCP-leaseoplysninger",
"dhcp_lease_boot_file": "Boot-fil",
"dhcp_lease_boot_next_server": "Start næste server",
"dhcp_lease_boot_server_name": "Navn på bootserver",
@ -371,7 +374,7 @@
"local_auth_create_new_password_label": "Ny adgangskode",
"local_auth_create_new_password_placeholder": "Indtast en stærk adgangskode",
"local_auth_create_not_now_button": "Ikke nu",
"local_auth_create_secure_button": "Sikker enhed",
"local_auth_create_secure_button": "Sikr enheden",
"local_auth_create_title": "Beskyttelse af lokal enhed",
"local_auth_current_password_label": "Nuværende adgangskode",
"local_auth_disable_local_device_protection_description": "Indtast din nuværende adgangskode for at deaktivere lokal enhedsbeskyttelse.",
@ -396,7 +399,7 @@
"local_auth_success_password_updated_description": "Du har ændret din adgangskode til beskyttelse af din lokale enhed. Husk din nye adgangskode til senere brug.",
"local_auth_success_password_updated_title": "Adgangskode opdateret",
"local_auth_update_password_button": "Opdater adgangskode",
"locale_auto": "Bil",
"locale_auto": "Auto",
"locale_change_success": "Sproget er ændret til {locale}",
"locale_da": "Dansk",
"locale_de": "Tysk",
@ -426,7 +429,8 @@
"macro_name_too_long": "Navnet skal være mindre end 50 tegn",
"macro_please_fix_validation_errors": "Ret venligst valideringsfejlene",
"macro_save": "Gem makro",
"macro_save_error": "Der opstod en fejl under lagring.",
"macro_save_failed": "Der opstod en fejl under lagring.",
"macro_save_failed_error": "Der opstod en fejl under lagring: {error}.",
"macro_step_count": "{steps} / {max} trin",
"macro_step_duration_description": "Tid til at vente, før man udfører det næste trin.",
"macro_step_duration_label": "Trinvarighed",
@ -549,9 +553,9 @@
"mouse_hide_cursor_description": "Skjul markøren, når du sender musebevægelser",
"mouse_hide_cursor_title": "Skjul markør",
"mouse_jiggler_config_updated": "Jiggler-konfigurationen er blevet opdateret",
"mouse_jiggler_custom": "Skik",
"mouse_jiggler_custom": "Tilpasset",
"mouse_jiggler_description": "Simuler bevægelsen af en computermus",
"mouse_jiggler_disabled": "Handicappet",
"mouse_jiggler_disabled": "Deaktiveret",
"mouse_jiggler_error_config": "Der opstod en fejl under indstilling af jiggler-konfigurationen",
"mouse_jiggler_failed_state": "Kunne ikke indstille jiggler-tilstand: {error}",
"mouse_jiggler_frequent": "Hyppig - 30'erne",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "Konfigurer hvilken DHCP-klient der skal bruges",
"network_dhcp_client_jetkvm": "JetKVM Intern",
"network_dhcp_client_title": "DHCP-klient",
"network_dhcp_information": "DHCP-oplysninger",
"network_dhcp_lease_renew": "Forny DHCP-lease",
"network_dhcp_lease_renew_confirm": "Forny lejekontrakt",
"network_dhcp_lease_renew_confirm_description": "Dette vil anmode om en ny IP-adresse fra din DHCP-server. Din enhed kan midlertidigt miste netværksforbindelsen under denne proces.",
@ -586,7 +589,7 @@
"network_dhcp_lease_renew_confirm_new_b": "du skal muligvis genoprette forbindelsen ved hjælp af den nye adresse",
"network_dhcp_lease_renew_failed": "Kunne ikke forny leasing: {error}",
"network_dhcp_lease_renew_success": "DHCP-lease fornyet",
"network_domain_custom": "Skik",
"network_domain_custom": "Tilpasset",
"network_domain_description": "Netværksdomænesuffiks for enheden",
"network_domain_dhcp_provided": "DHCP leveret",
"network_domain_local": ".lokal",
@ -599,39 +602,46 @@
"network_ipv4_address": "IPv4-adresse",
"network_ipv4_dns": "IPv4 DNS",
"network_ipv4_gateway": "IPv4-gateway",
"network_ipv4_invalid": "Ugyldig IPv4-adresse",
"network_ipv4_invalid_cidr": "Ugyldig CIDR-notation for IPv4-adresse",
"network_ipv4_mode_description": "Konfigurer IPv4-tilstanden",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "Statisk",
"network_ipv4_mode_title": "IPv4-tilstand",
"network_ipv4_netmask": "IPv4-netmaske",
"network_ipv6_address": "IPv6-adresse",
"network_ipv6_addresses_header": "IPv6-adresser",
"network_ipv6_cidr_suggestion": "Brug venligst CIDR-notation (f.eks. 2001:db8::1/64)",
"network_ipv6_dns": "IPv6 DNS",
"network_ipv6_flag_dad_failed": "DAD mislykkedes",
"network_ipv6_flag_deprecated": "Udfaset",
"network_ipv6_gateway": "IPv6-gateway",
"network_ipv6_information": "IPv6-oplysninger",
"network_ipv6_invalid": "Ugyldig IPv6-adresse",
"network_ipv6_mode_description": "Konfigurer IPv6-tilstanden",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "Handicappet",
"network_ipv6_mode_disabled": "Deaktiveret",
"network_ipv6_mode_link_local": "Kun link-lokal",
"network_ipv6_mode_slaac": "SLAAC",
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "Statisk",
"network_ipv6_mode_title": "IPv6-tilstand",
"network_ipv6_netmask": "IPv6-netmaske",
"network_ipv6_no_addresses": "Ingen IPv6-adresser konfigureret",
"network_ipv6_prefix": "IP-præfiks",
"network_ipv6_prefix_invalid": "Præfikset skal være mellem 0 og 128",
"network_ll_dp_all": "Alle",
"network_ll_dp_basic": "Grundlæggende",
"network_ll_dp_description": "Styr hvilke TLV'er der sendes via Link Layer Discovery Protocol",
"network_ll_dp_disabled": "Handicappet",
"network_ll_dp_disabled": "Deaktiveret",
"network_ll_dp_title": "LLDP",
"network_mac_address_copy_error": "Kunne ikke kopiere MAC-adressen",
"network_mac_address_copy_success": "MAC-adresse { mac } kopieret til udklipsholder",
"network_mac_address_description": "Hardware-identifikator for netværksgrænsefladen",
"network_mac_address_title": "MAC-adresse",
"network_mdns_auto": "Bil",
"network_mdns_auto": "Auto",
"network_mdns_description": "Styr mDNS (multicast DNS) driftstilstand",
"network_mdns_disabled": "Handicappet",
"network_mdns_disabled": "Deaktiveret",
"network_mdns_ipv4_only": "Kun IPv4",
"network_mdns_ipv6_only": "Kun IPv6",
"network_mdns_title": "mDNS",
"network_no_dhcp_lease": "Ingen DHCP-leaseoplysninger tilgængelige",
"network_no_information_description": "Ingen netværkskonfiguration tilgængelig",
"network_no_information_headline": "Netværksoplysninger",
"network_pending_dhcp_mode_change_description": "Gem indstillinger for at aktivere DHCP-tilstand og se leasingoplysninger",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "Konfigurationsændringer",
"network_save_settings_failed": "Kunne ikke gemme netværksindstillinger: {error}",
"network_save_settings_success": "Netværksindstillinger gemt",
"network_settings_invalid_ipv4_cidr": "Ugyldig CIDR-notation for IPv4-adresse",
"network_settings_add_dns": "Tilføj DNS-server",
"network_settings_load_error": "Kunne ikke indlæse netværksindstillinger: {error}",
"network_static_ipv4_header": "Statisk IPv4-konfiguration",
"network_static_ipv6_header": "Statisk IPv6-konfiguration",
"network_time_sync_description": "Konfigurer indstillinger for tidssynkronisering",
"network_time_sync_http_only": "Kun HTTP",
"network_time_sync_ntp_and_http": "NTP og HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "Kunne ikke indsætte tekst: {error}",
"paste_modal_invalid_chars_intro": "Følgende tegn vil ikke blive indsat:",
"paste_modal_paste_from_host": "Indsæt fra vært",
"paste_modal_paste_text": "Indsæt tekst",
"paste_modal_paste_text_description": "Indsæt tekst fra din klient til den eksterne vært",
"paste_modal_sending_using_layout": "Sender tekst ved hjælp af tastaturlayout: {iso} - {name}",
"paste_text": "Indsæt tekst",
"paste_text_description": "Indsæt tekst fra din klient til den eksterne vært",
"peer_connection_closed": "Lukket",
"peer_connection_closing": "Lukker",
"peer_connection_connected": "Forbundet",
@ -743,7 +755,7 @@
"updates_failed_get_device_version": "Kunne ikke hente enhedsversion: {error}",
"updating_leave_device_on": "Sluk venligst ikke din enhed…",
"usb": "USB",
"usb_config_custom": "Skik",
"usb_config_custom": "Tilpasset",
"usb_config_default": "JetKVM-standard",
"usb_config_dell": "Dell Multimedia Pro-tastatur",
"usb_config_failed_load": "Kunne ikke indlæse USB-konfiguration: {error}",
@ -767,7 +779,7 @@
"usb_config_vendor_id_placeholder": "Indtast leverandør-ID",
"usb_device_classes_description": "USB-enhedsklasser i den sammensatte enhed",
"usb_device_classes_title": "Klasser",
"usb_device_custom": "Skik",
"usb_device_custom": "Tilpasset",
"usb_device_description": "USB-enheder, der skal emuleres på målcomputeren",
"usb_device_enable_absolute_mouse_description": "Aktivér absolut mus (markør)",
"usb_device_enable_absolute_mouse_title": "Aktivér absolut mus (markør)",
@ -802,7 +814,7 @@
"video_description": "Konfigurer skærmindstillinger og EDID for optimal kompatibilitet",
"video_edid_acer_b246wl": "Acer B246WL, 1920x1200",
"video_edid_asus_pa248qv": "ASUS PA248QV, 1920x1200",
"video_edid_custom": "Skik",
"video_edid_custom": "Tilpasset",
"video_edid_dell_d2721h": "DELL D2721H, 1920x1080",
"video_edid_dell_idrac": "DELL IDRAC EDID, 1280x1024",
"video_edid_description": "Juster EDID-indstillingerne for skærmen",

View File

@ -50,7 +50,6 @@
"action_bar_connection_stats": "Verbindungsstatistiken",
"action_bar_extension": "Verlängerung",
"action_bar_fullscreen": "Vollbild",
"action_bar_paste_text": "Text einfügen",
"action_bar_settings": "Einstellungen",
"action_bar_virtual_keyboard": "Virtuelle Tastatur",
"action_bar_virtual_media": "Virtuelle Medien",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "Bestätigen Sie Ihr Passwort",
"auth_mode_local_password_confirm_label": "Passwort bestätigen",
"auth_mode_local_password_description": "Sichern Sie Ihr Gerät für zusätzlichen Schutz mit einem Passwort.",
"auth_mode_local_password_do_not_match": "Passwörter stimmen nicht überein",
"auth_mode_local_password_failed_set": "Kennwort konnte nicht festgelegt werden: {error}",
"auth_mode_local_password_note": "Dieses Passwort wird verwendet, um Ihre Gerätedaten zu sichern und vor unbefugtem Zugriff zu schützen.",
"auth_mode_local_password_note_local": "Alle Daten verbleiben auf Ihrem lokalen Gerät.",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "Round-Trip-Zeit",
"connection_stats_round_trip_time_description": "Roundtrip-Zeit für das aktive ICE-Kandidatenpaar zwischen Peers.",
"connection_stats_sidebar": "Verbindungsstatistiken",
"connection_stats_unit_frames_per_second": " fps",
"connection_stats_unit_milliseconds": " ms",
"connection_stats_unit_packets": " Pakete",
"connection_stats_video": "Video",
"connection_stats_video_description": "Der Videostream vom JetKVM zum Client.",
"continue": "Weitermachen",
@ -207,6 +208,8 @@
"deregister_error": "Beim Abmelden Ihres Geräts ist ein Fehler aufgetreten {status} . Bitte versuchen Sie es erneut.",
"deregister_headline": "Melden Sie {device}",
"detach": "Abtrennen",
"dhcp_empty_lease_description": "Wir haben noch keine DHCP-Lease-Informationen vom Gerät erhalten.",
"dhcp_empty_lease_headline": "Keine DHCP-Lease-Informationen",
"dhcp_lease_boot_file": "Boot-Datei",
"dhcp_lease_boot_next_server": "Nächsten Server starten",
"dhcp_lease_boot_server_name": "Name des Boot-Servers",
@ -426,7 +429,8 @@
"macro_name_too_long": "Der Name muss weniger als 50 Zeichen lang sein",
"macro_please_fix_validation_errors": "Bitte beheben Sie die Validierungsfehler",
"macro_save": "Makro speichern",
"macro_save_error": "Beim Speichern ist ein Fehler aufgetreten.",
"macro_save_failed": "Beim Speichern ist ein Fehler aufgetreten.",
"macro_save_failed_error": "Beim Speichern ist ein Fehler aufgetreten: {error}.",
"macro_step_count": "{steps} / {max} Schritte",
"macro_step_duration_description": "Wartezeit vor der Ausführung des nächsten Schritts.",
"macro_step_duration_label": "Schrittdauer",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "Konfigurieren Sie, welcher DHCP-Client verwendet werden soll",
"network_dhcp_client_jetkvm": "JetKVM intern",
"network_dhcp_client_title": "DHCP-Client",
"network_dhcp_information": "DHCP-Informationen",
"network_dhcp_lease_renew": "DHCP-Lease erneuern",
"network_dhcp_lease_renew_confirm": "Mietvertrag verlängern",
"network_dhcp_lease_renew_confirm_description": "Dadurch wird eine neue IP-Adresse von Ihrem DHCP-Server angefordert. Während dieses Vorgangs kann die Netzwerkverbindung Ihres Geräts vorübergehend unterbrochen werden.",
@ -599,13 +602,21 @@
"network_ipv4_address": "IPv4-Adresse",
"network_ipv4_dns": "IPv4 DNS",
"network_ipv4_gateway": "IPv4-Gateway",
"network_ipv4_invalid": "Ungültige IPv4-Adresse",
"network_ipv4_invalid_cidr": "Ungültige CIDR-Notation für IPv4-Adresse",
"network_ipv4_mode_description": "Konfigurieren des IPv4-Modus",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "Statisch",
"network_ipv4_mode_title": "IPv4-Modus",
"network_ipv4_netmask": "IPv4-Netzmaske",
"network_ipv6_address": "IPv6-Adresse",
"network_ipv6_addresses_header": "IPv6-Adressen",
"network_ipv6_cidr_suggestion": "Bitte verwenden Sie die CIDR-Notation (z. B. 2001:db8::1/64).",
"network_ipv6_dns": "IPv6 DNS",
"network_ipv6_flag_dad_failed": "DAD ist fehlgeschlagen",
"network_ipv6_flag_deprecated": "Veraltet",
"network_ipv6_gateway": "IPv6-Gateway",
"network_ipv6_information": "IPv6-Informationen",
"network_ipv6_invalid": "Ungültige IPv6-Adresse",
"network_ipv6_mode_description": "Konfigurieren des IPv6-Modus",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "Deaktiviert",
@ -614,8 +625,8 @@
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "Statisch",
"network_ipv6_mode_title": "IPv6-Modus",
"network_ipv6_netmask": "IPv6-Netzmaske",
"network_ipv6_no_addresses": "Keine IPv6-Adressen konfiguriert",
"network_ipv6_prefix": "IP-Präfix",
"network_ipv6_prefix_invalid": "Das Präfix muss zwischen 0 und 128 liegen",
"network_ll_dp_all": "Alle",
"network_ll_dp_basic": "Basic",
"network_ll_dp_description": "Steuern Sie, welche TLVs über das Link Layer Discovery Protocol gesendet werden",
@ -631,7 +642,6 @@
"network_mdns_ipv4_only": "Nur IPv4",
"network_mdns_ipv6_only": "Nur IPv6",
"network_mdns_title": "mDNS",
"network_no_dhcp_lease": "Keine DHCP-Lease-Informationen verfügbar",
"network_no_information_description": "Keine Netzwerkkonfiguration verfügbar",
"network_no_information_headline": "Netzwerkinformationen",
"network_pending_dhcp_mode_change_description": "Speichern Sie die Einstellungen, um den DHCP-Modus zu aktivieren und Leasinginformationen anzuzeigen",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "Konfigurationsänderungen",
"network_save_settings_failed": "Netzwerkeinstellungen konnten nicht gespeichert werden: {error}",
"network_save_settings_success": "Netzwerkeinstellungen gespeichert",
"network_settings_invalid_ipv4_cidr": "Ungültige CIDR-Notation für IPv4-Adresse",
"network_settings_add_dns": "DNS-Server hinzufügen",
"network_settings_load_error": "Netzwerkeinstellungen konnten nicht geladen werden: {error}",
"network_static_ipv4_header": "Statische IPv4-Konfiguration",
"network_static_ipv6_header": "Statische IPv6-Konfiguration",
"network_time_sync_description": "Konfigurieren der Zeitsynchronisierungseinstellungen",
"network_time_sync_http_only": "Nur HTTP",
"network_time_sync_ntp_and_http": "NTP und HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "Fehler beim Einfügen des Textes: {error}",
"paste_modal_invalid_chars_intro": "Die folgenden Zeichen werden nicht eingefügt:",
"paste_modal_paste_from_host": "Vom Host einfügen",
"paste_modal_paste_text": "Text einfügen",
"paste_modal_paste_text_description": "Fügen Sie Text von Ihrem Client in den Remote-Host ein",
"paste_modal_sending_using_layout": "Senden von Text mithilfe des Tastaturlayouts: {iso} - {name}",
"paste_text": "Text einfügen",
"paste_text_description": "Fügen Sie Text von Ihrem Client in den Remote-Host ein",
"peer_connection_closed": "Geschlossen",
"peer_connection_closing": "Schließen",
"peer_connection_connected": "Verbunden",

View File

@ -50,7 +50,6 @@
"action_bar_connection_stats": "Connection Stats",
"action_bar_extension": "Extension",
"action_bar_fullscreen": "Fullscreen",
"action_bar_paste_text": "Paste text",
"action_bar_settings": "Settings",
"action_bar_virtual_keyboard": "Virtual Keyboard",
"action_bar_virtual_media": "Virtual Media",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "Confirm your password",
"auth_mode_local_password_confirm_label": "Confirm Password",
"auth_mode_local_password_description": "Secure your device with a password for added protection.",
"auth_mode_local_password_do_not_match": "Passwords do not match",
"auth_mode_local_password_failed_set": "Failed to set password: {error}",
"auth_mode_local_password_note": "This password will be used to secure your device data and protect against unauthorized access.",
"auth_mode_local_password_note_local": "All data remains on your local device.",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "Round-Trip Time",
"connection_stats_round_trip_time_description": "Round-trip time for the active ICE candidate pair between peers.",
"connection_stats_sidebar": "Connection Stats",
"connection_stats_unit_frames_per_second": " fps",
"connection_stats_unit_milliseconds": " ms",
"connection_stats_unit_packets": " packets",
"connection_stats_video": "Video",
"connection_stats_video_description": "The video stream from the JetKVM to the client.",
"continue": "Continue",
@ -207,6 +208,8 @@
"deregister_error": "There was an error {status} deregistering your device. Please try again.",
"deregister_headline": "Deregister {device} from your cloud account",
"detach": "Detach",
"dhcp_empty_lease_description": "We haven't received any DHCP lease information from the device yet.",
"dhcp_empty_lease_headline": "No DHCP Lease information",
"dhcp_lease_boot_file": "Boot File",
"dhcp_lease_boot_next_server": "Boot Next Server",
"dhcp_lease_boot_server_name": "Boot Server Name",
@ -426,7 +429,8 @@
"macro_name_too_long": "Name must be less than 50 characters",
"macro_please_fix_validation_errors": "Please fix the validation errors",
"macro_save": "Save Macro",
"macro_save_error": "An error occurred while saving.",
"macro_save_failed": "An error occurred while saving.",
"macro_save_failed_error": "An error occurred while saving: {error}.",
"macro_step_count": "{steps} / {max} steps",
"macro_step_duration_description": "Time to wait before executing the next step.",
"macro_step_duration_label": "Step Duration",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "Configure which DHCP client to use",
"network_dhcp_client_jetkvm": "JetKVM Internal",
"network_dhcp_client_title": "DHCP Client",
"network_dhcp_information": "DHCP Information",
"network_dhcp_lease_renew": "Renew DHCP Lease",
"network_dhcp_lease_renew_confirm": "Renew Lease",
"network_dhcp_lease_renew_confirm_description": "This will request a new IP address from your DHCP server. Your device may temporarily lose network connectivity during this process.",
@ -599,13 +602,21 @@
"network_ipv4_address": "IPv4 Address",
"network_ipv4_dns": "IPv4 DNS",
"network_ipv4_gateway": "IPv4 Gateway",
"network_ipv4_invalid": "Invalid IPv4 address",
"network_ipv4_invalid_cidr": "Invalid CIDR notation for IPv4 address",
"network_ipv4_mode_description": "Configure the IPv4 mode",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "Static",
"network_ipv4_mode_title": "IPv4 Mode",
"network_ipv4_netmask": "IPv4 Netmask",
"network_ipv6_address": "IPv6 Address",
"network_ipv6_addresses_header": "IPv6 Addresses",
"network_ipv6_cidr_suggestion": "Please use CIDR notation (e.g., 2001:db8::1/64)",
"network_ipv6_dns": "IPv6 DNS",
"network_ipv6_flag_dad_failed": "DAD Failed",
"network_ipv6_flag_deprecated": "Deprecated",
"network_ipv6_gateway": "IPv6 Gateway",
"network_ipv6_information": "IPv6 Information",
"network_ipv6_invalid": "Invalid IPv6 address",
"network_ipv6_mode_description": "Configure the IPv6 mode",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "Disabled",
@ -614,8 +625,8 @@
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "Static",
"network_ipv6_mode_title": "IPv6 Mode",
"network_ipv6_netmask": "IPv6 Netmask",
"network_ipv6_no_addresses": "No IPv6 addresses configured",
"network_ipv6_prefix": "IP Prefix",
"network_ipv6_prefix_invalid": "Prefix must be between 0 and 128",
"network_ll_dp_all": "All",
"network_ll_dp_basic": "Basic",
"network_ll_dp_description": "Control which TLVs will be sent over Link Layer Discovery Protocol",
@ -631,7 +642,6 @@
"network_mdns_ipv4_only": "IPv4 only",
"network_mdns_ipv6_only": "IPv6 only",
"network_mdns_title": "mDNS",
"network_no_dhcp_lease": "No DHCP lease information available",
"network_no_information_description": "No network configuration available",
"network_no_information_headline": "Network Information",
"network_pending_dhcp_mode_change_description": "Save settings to enable DHCP mode and view lease information",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "Configuration changes",
"network_save_settings_failed": "Failed to save network settings: {error}",
"network_save_settings_success": "Network settings saved",
"network_settings_invalid_ipv4_cidr": "Invalid CIDR notation for IPv4 address",
"network_settings_add_dns": "Add DNS Server",
"network_settings_load_error": "Failed to load network settings: {error}",
"network_static_ipv4_header": "Static IPv4 Configuration",
"network_static_ipv6_header": "Static IPv6 Configuration",
"network_time_sync_description": "Configure time synchronization settings",
"network_time_sync_http_only": "HTTP only",
"network_time_sync_ntp_and_http": "NTP and HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "Failed to paste text: {error}",
"paste_modal_invalid_chars_intro": "The following characters won't be pasted:",
"paste_modal_paste_from_host": "Paste from host",
"paste_modal_paste_text": "Paste text",
"paste_modal_paste_text_description": "Paste text from your client to the remote host",
"paste_modal_sending_using_layout": "Sending text using keyboard layout: {iso}-{name}",
"paste_text": "Paste text",
"paste_text_description": "Paste text from your client to the remote host",
"peer_connection_closed": "Closed",
"peer_connection_closing": "Closing",
"peer_connection_connected": "Connected",

View File

@ -50,7 +50,6 @@
"action_bar_connection_stats": "Estadísticas de conexión",
"action_bar_extension": "Extensión",
"action_bar_fullscreen": "Pantalla completa",
"action_bar_paste_text": "Pegar texto",
"action_bar_settings": "Ajustes",
"action_bar_virtual_keyboard": "Teclado virtual",
"action_bar_virtual_media": "Medios virtuales",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "Confirma tu contraseña",
"auth_mode_local_password_confirm_label": "confirmar Contraseña",
"auth_mode_local_password_description": "Proteja su dispositivo con una contraseña para mayor protección.",
"auth_mode_local_password_do_not_match": "Las contraseñas no coinciden",
"auth_mode_local_password_failed_set": "No se pudo establecer la contraseña: {error}",
"auth_mode_local_password_note": "Esta contraseña se utilizará para proteger los datos de su dispositivo y contra accesos no autorizados.",
"auth_mode_local_password_note_local": "Todos los datos permanecen en su dispositivo local.",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "Tiempo de ida y vuelta",
"connection_stats_round_trip_time_description": "Tiempo de ida y vuelta para el par de candidatos ICE activos entre pares.",
"connection_stats_sidebar": "Estadísticas de conexión",
"connection_stats_unit_frames_per_second": " fps",
"connection_stats_unit_milliseconds": " ms",
"connection_stats_unit_packets": " paquetes",
"connection_stats_video": "Video",
"connection_stats_video_description": "La transmisión de vídeo desde JetKVM al cliente.",
"continue": "Continuar",
@ -207,6 +208,8 @@
"deregister_error": "Se produjo un error {status} al cancelar el registro de su dispositivo. Inténtelo de nuevo.",
"deregister_headline": "Anular el registro de {device} en su cuenta en la nube",
"detach": "Despegar",
"dhcp_empty_lease_description": "Aún no hemos recibido ninguna información de concesión de DHCP del dispositivo.",
"dhcp_empty_lease_headline": "No hay información de arrendamiento de DHCP",
"dhcp_lease_boot_file": "Archivo de arranque",
"dhcp_lease_boot_next_server": "Arrancar el siguiente servidor",
"dhcp_lease_boot_server_name": "Nombre del servidor de arranque",
@ -426,7 +429,8 @@
"macro_name_too_long": "El nombre debe tener menos de 50 caracteres.",
"macro_please_fix_validation_errors": "Por favor corrija los errores de validación",
"macro_save": "Guardar macro",
"macro_save_error": "Se produjo un error al guardar.",
"macro_save_failed": "Se produjo un error al guardar.",
"macro_save_failed_error": "Se produjo un error al guardar: {error}.",
"macro_step_count": "{steps} / {max} pasos",
"macro_step_duration_description": "Tiempo de espera antes de ejecutar el siguiente paso.",
"macro_step_duration_label": "Duración del paso",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "Configurar qué cliente DHCP utilizar",
"network_dhcp_client_jetkvm": "JetKVM interno",
"network_dhcp_client_title": "Cliente DHCP",
"network_dhcp_information": "Información de DHCP",
"network_dhcp_lease_renew": "Renovar la concesión de DHCP",
"network_dhcp_lease_renew_confirm": "Renovar el contrato de arrendamiento",
"network_dhcp_lease_renew_confirm_description": "Esto solicitará una nueva dirección IP a su servidor DHCP. Es posible que su dispositivo pierda temporalmente la conexión a la red durante este proceso.",
@ -599,13 +602,21 @@
"network_ipv4_address": "Dirección IPv4",
"network_ipv4_dns": "DNS IPv4",
"network_ipv4_gateway": "Puerta de enlace IPv4",
"network_ipv4_invalid": "Dirección IPv4 no válida",
"network_ipv4_invalid_cidr": "Notación CIDR no válida para la dirección IPv4",
"network_ipv4_mode_description": "Configurar el modo IPv4",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "Estático",
"network_ipv4_mode_title": "Modo IPv4",
"network_ipv4_netmask": "Máscara de red IPv4",
"network_ipv6_address": "Dirección IPv6",
"network_ipv6_addresses_header": "Direcciones IPv6",
"network_ipv6_cidr_suggestion": "Utilice la notación CIDR (por ejemplo, 2001:db8::1/64)",
"network_ipv6_dns": "DNS IPv6",
"network_ipv6_flag_dad_failed": "Papá falló",
"network_ipv6_flag_deprecated": "Obsoleto",
"network_ipv6_gateway": "Puerta de enlace IPv6",
"network_ipv6_information": "Información de IPv6",
"network_ipv6_invalid": "Dirección IPv6 no válida",
"network_ipv6_mode_description": "Configurar el modo IPv6",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "Desactivado",
@ -614,8 +625,8 @@
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "Estático",
"network_ipv6_mode_title": "Modo IPv6",
"network_ipv6_netmask": "Máscara de red IPv6",
"network_ipv6_no_addresses": "No hay direcciones IPv6 configuradas",
"network_ipv6_prefix": "Prefijo IP",
"network_ipv6_prefix_invalid": "El prefijo debe estar entre 0 y 128",
"network_ll_dp_all": "Todo",
"network_ll_dp_basic": "Básico",
"network_ll_dp_description": "Controlar qué TLV se enviarán a través del Protocolo de descubrimiento de capa de enlace",
@ -631,7 +642,6 @@
"network_mdns_ipv4_only": "Sólo IPv4",
"network_mdns_ipv6_only": "Sólo IPv6",
"network_mdns_title": "mDNS",
"network_no_dhcp_lease": "No hay información de arrendamiento de DHCP disponible",
"network_no_information_description": "No hay configuración de red disponible",
"network_no_information_headline": "Información de la red",
"network_pending_dhcp_mode_change_description": "Guardar la configuración para habilitar el modo DHCP y ver la información de arrendamiento",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "Cambios de configuración",
"network_save_settings_failed": "No se pudo guardar la configuración de red: {error}",
"network_save_settings_success": "Configuración de red guardada",
"network_settings_invalid_ipv4_cidr": "Notación CIDR no válida para la dirección IPv4",
"network_settings_add_dns": "Agregar servidor DNS",
"network_settings_load_error": "No se pudo cargar la configuración de red: {error}",
"network_static_ipv4_header": "Configuración estática de IPv4",
"network_static_ipv6_header": "Configuración estática de IPv6",
"network_time_sync_description": "Configurar los ajustes de sincronización horaria",
"network_time_sync_http_only": "Sólo HTTP",
"network_time_sync_ntp_and_http": "NTP y HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "No se pudo pegar el texto: {error}",
"paste_modal_invalid_chars_intro": "Los siguientes caracteres no se pegarán:",
"paste_modal_paste_from_host": "Pegar desde el host",
"paste_modal_paste_text": "Pegar texto",
"paste_modal_paste_text_description": "Pegue el texto de su cliente al host remoto",
"paste_modal_sending_using_layout": "Envío de texto mediante la distribución del teclado: {iso} - {name}",
"paste_text": "Pegar texto",
"paste_text_description": "Pegue el texto de su cliente al host remoto",
"peer_connection_closed": "Cerrado",
"peer_connection_closing": "Cierre",
"peer_connection_connected": "Conectado",

View File

@ -50,7 +50,6 @@
"action_bar_connection_stats": "Statistiques de connexion",
"action_bar_extension": "Extension",
"action_bar_fullscreen": "Plein écran",
"action_bar_paste_text": "Coller du texte",
"action_bar_settings": "Paramètres",
"action_bar_virtual_keyboard": "Clavier virtuel",
"action_bar_virtual_media": "Médias virtuels",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "Confirmez votre mot de passe",
"auth_mode_local_password_confirm_label": "Confirmez le mot de passe",
"auth_mode_local_password_description": "Sécurisez votre appareil avec un mot de passe pour une protection supplémentaire.",
"auth_mode_local_password_do_not_match": "Les mots de passe ne correspondent pas",
"auth_mode_local_password_failed_set": "Échec de la définition du mot de passe : {error}",
"auth_mode_local_password_note": "Ce mot de passe sera utilisé pour sécuriser les données de votre appareil et les protéger contre tout accès non autorisé.",
"auth_mode_local_password_note_local": "Toutes les données restent sur votre appareil local.",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "Temps de trajet aller-retour",
"connection_stats_round_trip_time_description": "Temps de trajet aller-retour pour la paire de candidats ICE actifs entre pairs.",
"connection_stats_sidebar": "Statistiques de connexion",
"connection_stats_unit_frames_per_second": " fps",
"connection_stats_unit_milliseconds": " ms",
"connection_stats_unit_packets": " paquets",
"connection_stats_video": "Vidéo",
"connection_stats_video_description": "Le flux vidéo du JetKVM vers le client.",
"continue": "Continuer",
@ -207,6 +208,8 @@
"deregister_error": "Une erreur {status} s'est produite lors de l'annulation de l'enregistrement de votre appareil. Veuillez réessayer.",
"deregister_headline": "Désinscrivez {device} de votre compte cloud",
"detach": "Détacher",
"dhcp_empty_lease_description": "Nous n'avons pas encore reçu d'informations sur le bail DHCP de l'appareil.",
"dhcp_empty_lease_headline": "Aucune information sur le bail DHCP",
"dhcp_lease_boot_file": "Fichier de démarrage",
"dhcp_lease_boot_next_server": "Démarrer le serveur suivant",
"dhcp_lease_boot_server_name": "Nom du serveur de démarrage",
@ -426,7 +429,8 @@
"macro_name_too_long": "Le nom doit comporter moins de 50 caractères",
"macro_please_fix_validation_errors": "Veuillez corriger les erreurs de validation",
"macro_save": "Enregistrer la macro",
"macro_save_error": "Une erreur s'est produite lors de l'enregistrement.",
"macro_save_failed": "Une erreur s'est produite lors de l'enregistrement.",
"macro_save_failed_error": "Une erreur s'est produite lors de l'enregistrement: {error}.",
"macro_step_count": "{steps} / {max} étapes",
"macro_step_duration_description": "Il est temps dattendre avant dexécuter létape suivante.",
"macro_step_duration_label": "Durée de l'étape",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "Configurer le client DHCP à utiliser",
"network_dhcp_client_jetkvm": "JetKVM interne",
"network_dhcp_client_title": "Client DHCP",
"network_dhcp_information": "Informations DHCP",
"network_dhcp_lease_renew": "Renouveler le bail DHCP",
"network_dhcp_lease_renew_confirm": "Renouveler le bail",
"network_dhcp_lease_renew_confirm_description": "Cette opération demandera une nouvelle adresse IP à votre serveur DHCP. Votre appareil pourrait perdre temporairement sa connectivité réseau pendant cette opération.",
@ -599,13 +602,21 @@
"network_ipv4_address": "Adresse IPv4",
"network_ipv4_dns": "DNS IPv4",
"network_ipv4_gateway": "Passerelle IPv4",
"network_ipv4_invalid": "Adresse IPv4 non valide",
"network_ipv4_invalid_cidr": "Notation CIDR non valide pour l'adresse IPv4",
"network_ipv4_mode_description": "Configurer le mode IPv4",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "Statique",
"network_ipv4_mode_title": "Mode IPv4",
"network_ipv4_netmask": "Masque de réseau IPv4",
"network_ipv6_address": "Adresse IPv6",
"network_ipv6_addresses_header": "Adresses IPv6",
"network_ipv6_cidr_suggestion": "Veuillez utiliser la notation CIDR (par exemple, 2001:db8::1/64)",
"network_ipv6_dns": "DNS IPv6",
"network_ipv6_flag_dad_failed": "Papa a échoué",
"network_ipv6_flag_deprecated": "Obsolète",
"network_ipv6_gateway": "Passerelle IPv6",
"network_ipv6_information": "Informations IPv6",
"network_ipv6_invalid": "Adresse IPv6 non valide",
"network_ipv6_mode_description": "Configurer le mode IPv6",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "Désactivé",
@ -614,8 +625,8 @@
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "Statique",
"network_ipv6_mode_title": "Mode IPv6",
"network_ipv6_netmask": "Masque de réseau IPv6",
"network_ipv6_no_addresses": "Aucune adresse IPv6 configurée",
"network_ipv6_prefix": "Préfixe IP",
"network_ipv6_prefix_invalid": "Le préfixe doit être compris entre 0 et 128",
"network_ll_dp_all": "Tous",
"network_ll_dp_basic": "Basique",
"network_ll_dp_description": "Contrôler les TLV qui seront envoyés via le protocole Link Layer Discovery",
@ -631,7 +642,6 @@
"network_mdns_ipv4_only": "IPv4 uniquement",
"network_mdns_ipv6_only": "IPv6 uniquement",
"network_mdns_title": "mDNS",
"network_no_dhcp_lease": "Aucune information de bail DHCP disponible",
"network_no_information_description": "Aucune configuration réseau disponible",
"network_no_information_headline": "Informations sur le réseau",
"network_pending_dhcp_mode_change_description": "Enregistrer les paramètres pour activer le mode DHCP et afficher les informations de bail",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "Modifications de configuration",
"network_save_settings_failed": "Échec de l'enregistrement des paramètres réseau : {error}",
"network_save_settings_success": "Paramètres réseau enregistrés",
"network_settings_invalid_ipv4_cidr": "Notation CIDR non valide pour l'adresse IPv4",
"network_settings_add_dns": "Ajouter un serveur DNS",
"network_settings_load_error": "Échec du chargement des paramètres réseau : {error}",
"network_static_ipv4_header": "Configuration IPv4 statique",
"network_static_ipv6_header": "Configuration IPv6 statique",
"network_time_sync_description": "Configurer les paramètres de synchronisation de l'heure",
"network_time_sync_http_only": "HTTP uniquement",
"network_time_sync_ntp_and_http": "NTP et HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "Échec du collage du texte : {error}",
"paste_modal_invalid_chars_intro": "Les caractères suivants ne seront pas collés :",
"paste_modal_paste_from_host": "Coller depuis l'hôte",
"paste_modal_paste_text": "Coller du texte",
"paste_modal_paste_text_description": "Collez le texte de votre client sur l'hôte distant",
"paste_modal_sending_using_layout": "Envoi de texte à l'aide de la disposition du clavier : {iso} - {name}",
"paste_text": "Coller du texte",
"paste_text_description": "Collez le texte de votre client sur l'hôte distant",
"peer_connection_closed": "Fermé",
"peer_connection_closing": "Clôture",
"peer_connection_connected": "Connecté",

View File

@ -50,7 +50,6 @@
"action_bar_connection_stats": "Statistiche di connessione",
"action_bar_extension": "Estensione",
"action_bar_fullscreen": "A schermo intero",
"action_bar_paste_text": "Incolla il testo",
"action_bar_settings": "Impostazioni",
"action_bar_virtual_keyboard": "Tastiera virtuale",
"action_bar_virtual_media": "Media virtuali",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "Conferma la tua password",
"auth_mode_local_password_confirm_label": "Conferma password",
"auth_mode_local_password_description": "Per una maggiore protezione, proteggi il tuo dispositivo con una password.",
"auth_mode_local_password_do_not_match": "Le password non corrispondono",
"auth_mode_local_password_failed_set": "Impossibile impostare la password: {error}",
"auth_mode_local_password_note": "Questa password verrà utilizzata per proteggere i dati del tuo dispositivo e proteggerli da accessi non autorizzati.",
"auth_mode_local_password_note_local": "Tutti i dati rimangono sul tuo dispositivo locale.",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "Tempo di andata e ritorno",
"connection_stats_round_trip_time_description": "Tempo di andata e ritorno per la coppia di candidati ICE attivi tra pari.",
"connection_stats_sidebar": "Statistiche di connessione",
"connection_stats_unit_frames_per_second": " fps",
"connection_stats_unit_milliseconds": " ms",
"connection_stats_unit_packets": " pacchetti",
"connection_stats_video": "Video",
"connection_stats_video_description": "Il flusso video dal JetKVM al client.",
"continue": "Continuare",
@ -207,6 +208,8 @@
"deregister_error": "Si è verificato un errore {status} durante l'annullamento della registrazione del dispositivo. Riprova.",
"deregister_headline": "Annulla la registrazione di {device} dal tuo account cloud",
"detach": "Staccare",
"dhcp_empty_lease_description": "Non abbiamo ancora ricevuto alcuna informazione di lease DHCP dal dispositivo.",
"dhcp_empty_lease_headline": "Nessuna informazione sul lease DHCP",
"dhcp_lease_boot_file": "File di avvio",
"dhcp_lease_boot_next_server": "Avvia il server successivo",
"dhcp_lease_boot_server_name": "Nome del server di avvio",
@ -426,7 +429,8 @@
"macro_name_too_long": "Il nome deve contenere meno di 50 caratteri",
"macro_please_fix_validation_errors": "Si prega di correggere gli errori di convalida",
"macro_save": "Salva macro",
"macro_save_error": "Si è verificato un errore durante il salvataggio.",
"macro_save_failed": "Si è verificato un errore durante il salvataggio.",
"macro_save_failed_error": "Si è verificato un errore durante il salvataggio: {error}.",
"macro_step_count": "{steps} / {max} steps",
"macro_step_duration_description": "Tempo di attesa prima di eseguire il passaggio successivo.",
"macro_step_duration_label": "Durata del passo",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "Configurare quale client DHCP utilizzare",
"network_dhcp_client_jetkvm": "JetKVM interno",
"network_dhcp_client_title": "Cliente DHCP",
"network_dhcp_information": "Informazioni DHCP",
"network_dhcp_lease_renew": "Rinnova il contratto di locazione DHCP",
"network_dhcp_lease_renew_confirm": "Rinnovare il contratto di locazione",
"network_dhcp_lease_renew_confirm_description": "Verrà richiesto un nuovo indirizzo IP al server DHCP. Durante questo processo, il dispositivo potrebbe perdere temporaneamente la connettività di rete.",
@ -599,13 +602,21 @@
"network_ipv4_address": "Indirizzo IPv4",
"network_ipv4_dns": "DNS IPv4",
"network_ipv4_gateway": "Gateway IPv4",
"network_ipv4_invalid": "Indirizzo IPv4 non valido",
"network_ipv4_invalid_cidr": "Notazione CIDR non valida per l'indirizzo IPv4",
"network_ipv4_mode_description": "Configurare la modalità IPv4",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "Statico",
"network_ipv4_mode_title": "Modalità IPv4",
"network_ipv4_netmask": "Maschera di rete IPv4",
"network_ipv6_address": "Indirizzo IPv6",
"network_ipv6_addresses_header": "Indirizzi IPv6",
"network_ipv6_cidr_suggestion": "Si prega di utilizzare la notazione CIDR (ad esempio, 2001:db8::1/64)",
"network_ipv6_dns": "DNS IPv6",
"network_ipv6_flag_dad_failed": "DAD fallito",
"network_ipv6_flag_deprecated": "Obsoleto",
"network_ipv6_gateway": "Gateway IPv6",
"network_ipv6_information": "Informazioni IPv6",
"network_ipv6_invalid": "Indirizzo IPv6 non valido",
"network_ipv6_mode_description": "Configurare la modalità IPv6",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "Disabili",
@ -614,8 +625,8 @@
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "Statico",
"network_ipv6_mode_title": "Modalità IPv6",
"network_ipv6_netmask": "Maschera di rete IPv6",
"network_ipv6_no_addresses": "Nessun indirizzo IPv6 configurato",
"network_ipv6_prefix": "Prefisso IP",
"network_ipv6_prefix_invalid": "Il prefisso deve essere compreso tra 0 e 128",
"network_ll_dp_all": "Tutto",
"network_ll_dp_basic": "Di base",
"network_ll_dp_description": "Controlla quali TLV verranno inviati tramite Link Layer Discovery Protocol",
@ -631,7 +642,6 @@
"network_mdns_ipv4_only": "Solo IPv4",
"network_mdns_ipv6_only": "Solo IPv6",
"network_mdns_title": "mDNS",
"network_no_dhcp_lease": "Nessuna informazione disponibile sul lease DHCP",
"network_no_information_description": "Nessuna configurazione di rete disponibile",
"network_no_information_headline": "Informazioni di rete",
"network_pending_dhcp_mode_change_description": "Salva le impostazioni per abilitare la modalità DHCP e visualizzare le informazioni di locazione",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "Modifiche alla configurazione",
"network_save_settings_failed": "Impossibile salvare le impostazioni di rete: {error}",
"network_save_settings_success": "Impostazioni di rete salvate",
"network_settings_invalid_ipv4_cidr": "Notazione CIDR non valida per l'indirizzo IPv4",
"network_settings_add_dns": "Aggiungi server DNS",
"network_settings_load_error": "Impossibile caricare le impostazioni di rete: {error}",
"network_static_ipv4_header": "Configurazione IPv4 statica",
"network_static_ipv6_header": "Configurazione IPv6 statica",
"network_time_sync_description": "Configurare le impostazioni di sincronizzazione dell'ora",
"network_time_sync_http_only": "Solo HTTP",
"network_time_sync_ntp_and_http": "NTP e HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "Impossibile incollare il testo: {error}",
"paste_modal_invalid_chars_intro": "I seguenti caratteri non verranno incollati:",
"paste_modal_paste_from_host": "Incolla dall'host",
"paste_modal_paste_text": "Incolla il testo",
"paste_modal_paste_text_description": "Incolla il testo dal tuo client all'host remoto",
"paste_modal_sending_using_layout": "Invio di testo tramite layout di tastiera: {iso} - {name}",
"paste_text": "Incolla il testo",
"paste_text_description": "Incolla il testo dal tuo client all'host remoto",
"peer_connection_closed": "Chiuso",
"peer_connection_closing": "Chiusura",
"peer_connection_connected": "Collegato",

View File

@ -31,7 +31,7 @@
"access_no_device_id": "Ingen enhets-ID tilgjengelig",
"access_private_key_description": "Av sikkerhetshensyn vil den ikke vises etter lagring.",
"access_private_key_label": "Privat nøkkel",
"access_provider_custom": "Skikk",
"access_provider_custom": "Tilpasset",
"access_provider_jetkvm": "JetKVM Cloud",
"access_remote_description": "Administrer modusen for ekstern tilgang til enheten",
"access_security_encryption": "Ende-til-ende-kryptering ved bruk av WebRTC (DTLS og SRTP)",
@ -42,15 +42,14 @@
"access_title": "Adgang",
"access_tls_certificate_description": "Lim inn TLS-sertifikatet ditt nedenfor. For sertifikatkjeder, inkluder hele kjeden (blad-, mellom- og rotsertifikater).",
"access_tls_certificate_title": "TLS-sertifikat",
"access_tls_custom": "Skikk",
"access_tls_disabled": "Funksjonshemmet",
"access_tls_custom": "Tilpasset",
"access_tls_disabled": "Deaktivert",
"access_tls_self_signed": "Selvsignert",
"access_tls_updated": "TLS-innstillingene er oppdatert",
"access_update_tls_settings": "Oppdater TLS-innstillinger",
"action_bar_connection_stats": "Tilkoblingsstatistikk",
"action_bar_extension": "Forlengelse",
"action_bar_fullscreen": "Fullskjerm",
"action_bar_paste_text": "Lim inn tekst",
"action_bar_settings": "Innstillinger",
"action_bar_virtual_keyboard": "Virtuelt tastatur",
"action_bar_virtual_media": "Virtuelle medier",
@ -116,7 +115,7 @@
"atx_power_control_get_state_error": "Klarte ikke å hente ATX-strømstatus: {error}",
"atx_power_control_hdd_led": "HDD-LED",
"atx_power_control_long_power_button": "Langt trykk",
"atx_power_control_power_button": "Makt",
"atx_power_control_power_button": "Strøm",
"atx_power_control_power_led": "Strøm-LED",
"atx_power_control_reset_button": "Tilbakestill",
"atx_power_control_send_action_error": "Kunne ikke sende ATX-strømhandling {action} : {error}",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "Bekreft passordet ditt",
"auth_mode_local_password_confirm_label": "Bekreft passord",
"auth_mode_local_password_description": "Sikre enheten din med et passord for ekstra beskyttelse.",
"auth_mode_local_password_do_not_match": "Passordene stemmer ikke overens",
"auth_mode_local_password_failed_set": "Klarte ikke å angi passord: {error}",
"auth_mode_local_password_note": "Dette passordet vil bli brukt til å sikre enhetsdataene dine og beskytte mot uautorisert tilgang.",
"auth_mode_local_password_note_local": "Alle dataene forblir på din lokale enhet.",
@ -156,8 +154,8 @@
"auth_signup_create_account_description": "Opprett kontoen din og begynn å administrere enhetene dine med letthet.",
"back": "Tilbake",
"back_to_devices": "Tilbake til Enheter",
"cancel": "Kansellere",
"close": "Lukke",
"cancel": "Avbryt",
"close": "Lukk",
"cloud_kvms": "Cloud KVM-er",
"cloud_kvms_description": "Administrer skybaserte KVM-er og koble til dem sikkert.",
"cloud_kvms_no_devices": "Ingen enheter funnet",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "Tur-retur-tid",
"connection_stats_round_trip_time_description": "Rundturstid for det aktive ICE-kandidatparet mellom jevnaldrende.",
"connection_stats_sidebar": "Tilkoblingsstatistikk",
"connection_stats_unit_frames_per_second": " fps",
"connection_stats_unit_milliseconds": " ms",
"connection_stats_unit_packets": " pakker",
"connection_stats_video": "Video",
"connection_stats_video_description": "Videostrømmen fra JetKVM til klienten.",
"continue": "Fortsette",
@ -188,7 +189,7 @@
"dc_power_control_current": "Nåværende",
"dc_power_control_current_unit": "EN",
"dc_power_control_get_state_error": "Klarte ikke å hente likestrømsstatus: {error}",
"dc_power_control_power": "Makt",
"dc_power_control_power": "Strøm",
"dc_power_control_power_off_button": "Slå av",
"dc_power_control_power_off_state": "Slå av",
"dc_power_control_power_on_button": "Slå på",
@ -207,6 +208,8 @@
"deregister_error": "Det oppsto en feil {status} enheten din skulle avregistreres. Prøv på nytt.",
"deregister_headline": "Avregistrer {device} fra skykontoen din",
"detach": "Løsne",
"dhcp_empty_lease_description": "Vi har ikke mottatt noen DHCP-leaseinformasjon fra enheten ennå.",
"dhcp_empty_lease_headline": "Ingen DHCP-leaseinformasjon",
"dhcp_lease_boot_file": "Oppstartsfil",
"dhcp_lease_boot_next_server": "Start opp neste server",
"dhcp_lease_boot_server_name": "Navn på oppstartsserver",
@ -371,7 +374,7 @@
"local_auth_create_new_password_label": "Nytt passord",
"local_auth_create_new_password_placeholder": "Skriv inn et sterkt passord",
"local_auth_create_not_now_button": "Ikke nå",
"local_auth_create_secure_button": "Sikker enhet",
"local_auth_create_secure_button": "Sikre enheten",
"local_auth_create_title": "Lokal enhetsbeskyttelse",
"local_auth_current_password_label": "Nåværende passord",
"local_auth_disable_local_device_protection_description": "Skriv inn ditt nåværende passord for å deaktivere lokal enhetsbeskyttelse.",
@ -396,7 +399,7 @@
"local_auth_success_password_updated_description": "Du har endret passordet for beskyttelse av den lokale enheten. Husk det nye passordet for fremtidig tilgang.",
"local_auth_success_password_updated_title": "Passord oppdatert",
"local_auth_update_password_button": "Oppdater passord",
"locale_auto": "Bil",
"locale_auto": "Auto",
"locale_change_success": "Språket er endret til {locale}",
"locale_da": "Dansk",
"locale_de": "Tysk",
@ -426,7 +429,8 @@
"macro_name_too_long": "Navnet må være mindre enn 50 tegn",
"macro_please_fix_validation_errors": "Vennligst rett opp valideringsfeilene",
"macro_save": "Lagre makro",
"macro_save_error": "Det oppsto en feil under lagring.",
"macro_save_failed": "Det oppsto en feil under lagring.",
"macro_save_failed_error": "Det oppsto en feil under lagring: {error}.",
"macro_step_count": "{steps} / {max} trinn",
"macro_step_duration_description": "Tid for å vente før man tar neste steg.",
"macro_step_duration_label": "Stegvarighet",
@ -549,9 +553,9 @@
"mouse_hide_cursor_description": "Skjul markøren når du sender musebevegelser",
"mouse_hide_cursor_title": "Skjul markør",
"mouse_jiggler_config_updated": "Jiggler-konfigurasjonen er oppdatert",
"mouse_jiggler_custom": "Skikk",
"mouse_jiggler_custom": "Tilpasset",
"mouse_jiggler_description": "Simuler bevegelsen til en datamus",
"mouse_jiggler_disabled": "Funksjonshemmet",
"mouse_jiggler_disabled": "Deaktivert",
"mouse_jiggler_error_config": "Det oppsto en feil under innstilling av jiggler-konfigurasjonen",
"mouse_jiggler_failed_state": "Klarte ikke å angi jiggler-tilstand: {error}",
"mouse_jiggler_frequent": "Hyppig - 30-tallet",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "Konfigurer hvilken DHCP-klient som skal brukes",
"network_dhcp_client_jetkvm": "JetKVM intern",
"network_dhcp_client_title": "DHCP-klient",
"network_dhcp_information": "DHCP-informasjon",
"network_dhcp_lease_renew": "Forny DHCP-leieavtale",
"network_dhcp_lease_renew_confirm": "Forny leieavtalen",
"network_dhcp_lease_renew_confirm_description": "Dette vil be om en ny IP-adresse fra DHCP-serveren din. Enheten din kan midlertidig miste nettverkstilkoblingen under denne prosessen.",
@ -586,7 +589,7 @@
"network_dhcp_lease_renew_confirm_new_b": "du må kanskje koble til på nytt med den nye adressen",
"network_dhcp_lease_renew_failed": "Kunne ikke fornye leieavtalen: {error}",
"network_dhcp_lease_renew_success": "DHCP-leieavtale fornyet",
"network_domain_custom": "Skikk",
"network_domain_custom": "Tilpasset",
"network_domain_description": "Nettverksdomenesuffiks for enheten",
"network_domain_dhcp_provided": "DHCP levert",
"network_domain_local": ".lokal",
@ -599,39 +602,46 @@
"network_ipv4_address": "IPv4-adresse",
"network_ipv4_dns": "IPv4 DNS",
"network_ipv4_gateway": "IPv4-gateway",
"network_ipv4_invalid": "Ugyldig IPv4-adresse",
"network_ipv4_invalid_cidr": "Ugyldig CIDR-notasjon for IPv4-adresse",
"network_ipv4_mode_description": "Konfigurer IPv4-modusen",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "Statisk",
"network_ipv4_mode_title": "IPv4-modus",
"network_ipv4_netmask": "IPv4-nettmaske",
"network_ipv6_address": "IPv6-adresse",
"network_ipv6_addresses_header": "IPv6-adresser",
"network_ipv6_cidr_suggestion": "Vennligst bruk CIDR-notasjon (f.eks. 2001:db8::1/64)",
"network_ipv6_dns": "IPv6 DNS",
"network_ipv6_flag_dad_failed": "DAD mislyktes",
"network_ipv6_flag_deprecated": "Utdatert",
"network_ipv6_gateway": "IPv6-gateway",
"network_ipv6_information": "IPv6-informasjon",
"network_ipv6_invalid": "Ugyldig IPv6-adresse",
"network_ipv6_mode_description": "Konfigurer IPv6-modusen",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "Funksjonshemmet",
"network_ipv6_mode_disabled": "Deaktivert",
"network_ipv6_mode_link_local": "Kun lenkelokal",
"network_ipv6_mode_slaac": "SLAAC",
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "Statisk",
"network_ipv6_mode_title": "IPv6-modus",
"network_ipv6_netmask": "IPv6-nettmaske",
"network_ipv6_no_addresses": "Ingen IPv6-adresser konfigurert",
"network_ipv6_prefix": "IP-prefiks",
"network_ipv6_prefix_invalid": "Prefikset må være mellom 0 og 128",
"network_ll_dp_all": "Alle",
"network_ll_dp_basic": "Grunnleggende",
"network_ll_dp_description": "Kontroller hvilke TLV-er som skal sendes over Link Layer Discovery Protocol",
"network_ll_dp_disabled": "Funksjonshemmet",
"network_ll_dp_disabled": "Deaktivert",
"network_ll_dp_title": "LLDP",
"network_mac_address_copy_error": "Kunne ikke kopiere MAC-adressen",
"network_mac_address_copy_success": "MAC-adresse { mac } kopiert til utklippstavlen",
"network_mac_address_description": "Maskinvareidentifikator for nettverksgrensesnittet",
"network_mac_address_title": "MAC-adresse",
"network_mdns_auto": "Bil",
"network_mdns_auto": "Auto",
"network_mdns_description": "Kontrollmodus for mDNS (multicast DNS)",
"network_mdns_disabled": "Funksjonshemmet",
"network_mdns_disabled": "Deaktivert",
"network_mdns_ipv4_only": "Kun IPv4",
"network_mdns_ipv6_only": "Kun IPv6",
"network_mdns_title": "mDNS",
"network_no_dhcp_lease": "Ingen DHCP-leaseinformasjon tilgjengelig",
"network_no_information_description": "Ingen nettverkskonfigurasjon tilgjengelig",
"network_no_information_headline": "Nettverksinformasjon",
"network_pending_dhcp_mode_change_description": "Lagre innstillinger for å aktivere DHCP-modus og vise leieavtaleinformasjon",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "Konfigurasjonsendringer",
"network_save_settings_failed": "Kunne ikke lagre nettverksinnstillinger: {error}",
"network_save_settings_success": "Nettverksinnstillinger lagret",
"network_settings_invalid_ipv4_cidr": "Ugyldig CIDR-notasjon for IPv4-adresse",
"network_settings_add_dns": "Legg til DNS-server",
"network_settings_load_error": "Kunne ikke laste inn nettverksinnstillinger: {error}",
"network_static_ipv4_header": "Statisk IPv4-konfigurasjon",
"network_static_ipv6_header": "Statisk IPv6-konfigurasjon",
"network_time_sync_description": "Konfigurer innstillinger for tidssynkronisering",
"network_time_sync_http_only": "Kun HTTP",
"network_time_sync_ntp_and_http": "NTP og HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "Klarte ikke å lime inn tekst: {error}",
"paste_modal_invalid_chars_intro": "Følgende tegn vil ikke bli limt inn:",
"paste_modal_paste_from_host": "Lim inn fra verten",
"paste_modal_paste_text": "Lim inn tekst",
"paste_modal_paste_text_description": "Lim inn tekst fra klienten din til den eksterne verten",
"paste_modal_sending_using_layout": "Sende tekst ved hjelp av tastaturoppsett: {iso} - {name}",
"paste_text": "Lim inn tekst",
"paste_text_description": "Lim inn tekst fra klienten din til den eksterne verten",
"peer_connection_closed": "Lukket",
"peer_connection_closing": "Lukking",
"peer_connection_connected": "Tilkoblet",
@ -743,7 +755,7 @@
"updates_failed_get_device_version": "Klarte ikke å hente enhetsversjon: {error}",
"updating_leave_device_on": "Vennligst ikke slå av enheten din ...",
"usb": "USB",
"usb_config_custom": "Skikk",
"usb_config_custom": "Tilpasset",
"usb_config_default": "JetKVM-standard",
"usb_config_dell": "Dell Multimedia Pro-tastatur",
"usb_config_failed_load": "Klarte ikke å laste inn USB-konfigurasjon: {error}",
@ -767,7 +779,7 @@
"usb_config_vendor_id_placeholder": "Skriv inn leverandør-ID",
"usb_device_classes_description": "USB-enhetsklasser i den sammensatte enheten",
"usb_device_classes_title": "Klasser",
"usb_device_custom": "Skikk",
"usb_device_custom": "Tilpasset",
"usb_device_description": "USB-enheter som skal emuleres på måldatamaskinen",
"usb_device_enable_absolute_mouse_description": "Aktiver absolutt mus (peker)",
"usb_device_enable_absolute_mouse_title": "Aktiver absolutt mus (peker)",
@ -802,7 +814,7 @@
"video_description": "Konfigurer skjerminnstillinger og EDID for optimal kompatibilitet",
"video_edid_acer_b246wl": "Acer B246WL, 1920x1200",
"video_edid_asus_pa248qv": "ASUS PA248QV, 1920x1200",
"video_edid_custom": "Skikk",
"video_edid_custom": "Tilpasset",
"video_edid_dell_d2721h": "DELL D2721H, 1920x1080",
"video_edid_dell_idrac": "DELL IDRAC EDID, 1280x1024",
"video_edid_description": "Juster EDID-innstillingene for skjermen",

View File

@ -31,7 +31,7 @@
"access_no_device_id": "Inget enhets-ID tillgängligt",
"access_private_key_description": "Av säkerhetsskäl kommer den inte att visas efter att den har sparats.",
"access_private_key_label": "Privat nyckel",
"access_provider_custom": "Beställnings",
"access_provider_custom": "Anpassad",
"access_provider_jetkvm": "JetKVM-molnet",
"access_remote_description": "Hantera läget för fjärråtkomst till enheten",
"access_security_encryption": "End-to-end-kryptering med WebRTC (DTLS och SRTP)",
@ -42,15 +42,14 @@
"access_title": "Tillträde",
"access_tls_certificate_description": "Klistra in ditt TLS-certifikat nedan. För certifikatkedjor, inkludera hela kedjan (löv-, mellan- och rotcertifikat).",
"access_tls_certificate_title": "TLS-certifikat",
"access_tls_custom": "Beställnings",
"access_tls_disabled": "Funktionshindrad",
"access_tls_custom": "Anpassad",
"access_tls_disabled": "Inaktiverad",
"access_tls_self_signed": "Självsignerad",
"access_tls_updated": "TLS-inställningarna har uppdaterats",
"access_update_tls_settings": "Uppdatera TLS-inställningar",
"action_bar_connection_stats": "Anslutningsstatistik",
"action_bar_extension": "Förlängning",
"action_bar_fullscreen": "Helskärm",
"action_bar_paste_text": "Klistra in text",
"action_bar_settings": "Inställningar",
"action_bar_virtual_keyboard": "Virtuellt tangentbord",
"action_bar_virtual_media": "Virtuella medier",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "Bekräfta ditt lösenord",
"auth_mode_local_password_confirm_label": "Bekräfta lösenord",
"auth_mode_local_password_description": "Säkra din enhet med ett lösenord för extra skydd.",
"auth_mode_local_password_do_not_match": "Lösenorden matchar inte",
"auth_mode_local_password_failed_set": "Misslyckades med att ange lösenord: {error}",
"auth_mode_local_password_note": "Detta lösenord kommer att användas för att säkra dina enhetsdata och skydda mot obehörig åtkomst.",
"auth_mode_local_password_note_local": "All data finns kvar på din lokala enhet.",
@ -156,8 +154,8 @@
"auth_signup_create_account_description": "Skapa ditt konto och börja enkelt hantera dina enheter.",
"back": "Tillbaka",
"back_to_devices": "Tillbaka till Enheter",
"cancel": "Avboka",
"close": "Nära",
"cancel": "Avbryt",
"close": "Stäng",
"cloud_kvms": "Moln-KVM:er",
"cloud_kvms_description": "Hantera dina moln-KVM:er och anslut till dem säkert.",
"cloud_kvms_no_devices": "Inga enheter hittades",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "Tur- och returtid",
"connection_stats_round_trip_time_description": "Tur- och returtid för det aktiva ICE-kandidatparet mellan peers.",
"connection_stats_sidebar": "Anslutningsstatistik",
"connection_stats_unit_frames_per_second": " fps",
"connection_stats_unit_milliseconds": " ms",
"connection_stats_unit_packets": " paket",
"connection_stats_video": "Video",
"connection_stats_video_description": "Videoströmmen från JetKVM till klienten.",
"continue": "Fortsätta",
@ -207,6 +208,8 @@
"deregister_error": "Det uppstod ett fel {status} enheten avregistrerades. Försök igen.",
"deregister_headline": "Avregistrera {device} från ditt molnkonto",
"detach": "Lösgöra",
"dhcp_empty_lease_description": "Vi har inte mottagit någon DHCP-leaseinformation från enheten ännu.",
"dhcp_empty_lease_headline": "Ingen DHCP-leaseinformation",
"dhcp_lease_boot_file": "Startfil",
"dhcp_lease_boot_next_server": "Starta nästa server",
"dhcp_lease_boot_server_name": "Namn på startserver",
@ -371,7 +374,7 @@
"local_auth_create_new_password_label": "Nytt lösenord",
"local_auth_create_new_password_placeholder": "Ange ett starkt lösenord",
"local_auth_create_not_now_button": "Inte nu",
"local_auth_create_secure_button": "Säker enhet",
"local_auth_create_secure_button": "Säkra enheten",
"local_auth_create_title": "Lokalt enhetsskydd",
"local_auth_current_password_label": "Nuvarande lösenord",
"local_auth_disable_local_device_protection_description": "Ange ditt nuvarande lösenord för att inaktivera lokalt enhetsskydd.",
@ -396,14 +399,14 @@
"local_auth_success_password_updated_description": "Du har ändrat ditt lösenord för lokal enhetsskydd. Se till att komma ihåg ditt nya lösenord för framtida åtkomst.",
"local_auth_success_password_updated_title": "Lösenordet har uppdaterats",
"local_auth_update_password_button": "Uppdatera lösenord",
"locale_auto": "Bil",
"locale_auto": "Auto",
"locale_change_success": "Språket har ändrats till {locale}",
"locale_da": "Danska",
"locale_de": "Deutsch",
"locale_de": "Tyska",
"locale_en": "Engelska",
"locale_es": "Spanska",
"locale_fr": "Franska",
"locale_it": "italiensk",
"locale_it": "Italienska",
"locale_nb": "Norska (bokmål)",
"locale_sv": "Svenska",
"locale_zh": "中文 (简体)",
@ -426,7 +429,8 @@
"macro_name_too_long": "Namnet måste vara kortare än 50 tecken",
"macro_please_fix_validation_errors": "Vänligen åtgärda valideringsfelen",
"macro_save": "Spara makro",
"macro_save_error": "Ett fel uppstod när dokumentet skulle sparas.",
"macro_save_failed": "Ett fel uppstod när dokumentet skulle sparas.",
"macro_save_failed_error": "Ett fel uppstod när dokumentet skulle sparas: {error}.",
"macro_step_count": "{steps} / {max} steg",
"macro_step_duration_description": "Dags att vänta innan nästa steg genomförs.",
"macro_step_duration_label": "Steglängd",
@ -549,9 +553,9 @@
"mouse_hide_cursor_description": "Dölj markören när du skickar musrörelser",
"mouse_hide_cursor_title": "Dölj markören",
"mouse_jiggler_config_updated": "Jiggler-konfigurationen har uppdaterats",
"mouse_jiggler_custom": "Beställnings",
"mouse_jiggler_custom": "Anpassad",
"mouse_jiggler_description": "Simulera rörelsen hos en datormus",
"mouse_jiggler_disabled": "Funktionshindrad",
"mouse_jiggler_disabled": "Inaktiverad",
"mouse_jiggler_error_config": "Det uppstod ett fel vid konfiguration av jiggler",
"mouse_jiggler_failed_state": "Misslyckades med att ställa in jiggler-tillstånd: {error}",
"mouse_jiggler_frequent": "Frekvent - 30-talet",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "Konfigurera vilken DHCP-klient som ska användas",
"network_dhcp_client_jetkvm": "JetKVM Intern",
"network_dhcp_client_title": "DHCP-klient",
"network_dhcp_information": "DHCP-information",
"network_dhcp_lease_renew": "Förnya DHCP-lease",
"network_dhcp_lease_renew_confirm": "Förnya hyresavtalet",
"network_dhcp_lease_renew_confirm_description": "Detta kommer att begära en ny IP-adress från din DHCP-server. Din enhet kan tillfälligt förlora nätverksanslutningen under denna process.",
@ -586,7 +589,7 @@
"network_dhcp_lease_renew_confirm_new_b": "du kan behöva återansluta med den nya adressen",
"network_dhcp_lease_renew_failed": "Misslyckades med att förnya leasingavtalet: {error}",
"network_dhcp_lease_renew_success": "DHCP-lease förnyad",
"network_domain_custom": "Beställnings",
"network_domain_custom": "Anpassad",
"network_domain_description": "Nätverksdomänsuffix för enheten",
"network_domain_dhcp_provided": "DHCP tillhandahålls",
"network_domain_local": ".lokal",
@ -599,39 +602,46 @@
"network_ipv4_address": "IPv4-adress",
"network_ipv4_dns": "IPv4 DNS",
"network_ipv4_gateway": "IPv4-gateway",
"network_ipv4_invalid": "Ogiltig IPv4-adress",
"network_ipv4_invalid_cidr": "Ogiltig CIDR-notation för IPv4-adress",
"network_ipv4_mode_description": "Konfigurera IPv4-läget",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "Statisk",
"network_ipv4_mode_title": "IPv4-läge",
"network_ipv4_netmask": "IPv4-nätmask",
"network_ipv6_address": "IPv6-adress",
"network_ipv6_addresses_header": "IPv6-adresser",
"network_ipv6_cidr_suggestion": "Använd CIDR-notation (t.ex. 2001:db8::1/64)",
"network_ipv6_dns": "IPv6 DNS",
"network_ipv6_flag_dad_failed": "DAD misslyckades",
"network_ipv6_flag_deprecated": "Föråldrad",
"network_ipv6_gateway": "IPv6-gateway",
"network_ipv6_information": "IPv6-information",
"network_ipv6_invalid": "Ogiltig IPv6-adress",
"network_ipv6_mode_description": "Konfigurera IPv6-läget",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "Funktionshindrad",
"network_ipv6_mode_disabled": "Inaktiverad",
"network_ipv6_mode_link_local": "Endast länklokal",
"network_ipv6_mode_slaac": "SLAAC",
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "Statisk",
"network_ipv6_mode_title": "IPv6-läge",
"network_ipv6_netmask": "IPv6-nätmask",
"network_ipv6_no_addresses": "Inga IPv6-adresser konfigurerade",
"network_ipv6_prefix": "IP-prefix",
"network_ipv6_prefix_invalid": "Prefixet måste vara mellan 0 och 128",
"network_ll_dp_all": "Alla",
"network_ll_dp_basic": "Grundläggande",
"network_ll_dp_description": "Kontrollera vilka TLV:er som ska skickas via Link Layer Discovery Protocol",
"network_ll_dp_disabled": "Funktionshindrad",
"network_ll_dp_disabled": "Inaktiverad",
"network_ll_dp_title": "LLDP",
"network_mac_address_copy_error": "Misslyckades med att kopiera MAC-adressen",
"network_mac_address_copy_success": "MAC-adress { mac } kopierad till urklipp",
"network_mac_address_description": "Maskinvaruidentifierare för nätverksgränssnittet",
"network_mac_address_title": "MAC-adress",
"network_mdns_auto": "Bil",
"network_mdns_auto": "Auto",
"network_mdns_description": "Kontroll av mDNS (multicast DNS) driftläge",
"network_mdns_disabled": "Funktionshindrad",
"network_mdns_disabled": "Inaktiverad",
"network_mdns_ipv4_only": "Endast IPv4",
"network_mdns_ipv6_only": "Endast IPv6",
"network_mdns_title": "mDNS",
"network_no_dhcp_lease": "Ingen DHCP-leaseinformation tillgänglig",
"network_no_information_description": "Ingen nätverkskonfiguration tillgänglig",
"network_no_information_headline": "Nätverksinformation",
"network_pending_dhcp_mode_change_description": "Spara inställningar för att aktivera DHCP-läge och visa leasinginformation",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "Konfigurationsändringar",
"network_save_settings_failed": "Misslyckades med att spara nätverksinställningar: {error}",
"network_save_settings_success": "Nätverksinställningar sparade",
"network_settings_invalid_ipv4_cidr": "Ogiltig CIDR-notation för IPv4-adress",
"network_settings_add_dns": "Lägg till DNS-server",
"network_settings_load_error": "Misslyckades med att läsa in nätverksinställningar: {error}",
"network_static_ipv4_header": "Statisk IPv4-konfiguration",
"network_static_ipv6_header": "Statisk IPv6-konfiguration",
"network_time_sync_description": "Konfigurera inställningar för tidssynkronisering",
"network_time_sync_http_only": "Endast HTTP",
"network_time_sync_ntp_and_http": "NTP och HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "Misslyckades med att klistra in text: {error}",
"paste_modal_invalid_chars_intro": "Följande tecken klistras inte in:",
"paste_modal_paste_from_host": "Klistra in från värd",
"paste_modal_paste_text": "Klistra in text",
"paste_modal_paste_text_description": "Klistra in text från din klient till fjärrdatorn",
"paste_modal_sending_using_layout": "Skicka text med tangentbordslayout: {iso} - {name}",
"paste_text": "Klistra in text",
"paste_text_description": "Klistra in text från din klient till fjärrdatorn",
"peer_connection_closed": "Stängd",
"peer_connection_closing": "Stängning",
"peer_connection_connected": "Ansluten",
@ -743,7 +755,7 @@
"updates_failed_get_device_version": "Misslyckades med att hämta enhetsversionen: {error}",
"updating_leave_device_on": "Stäng inte av din enhet…",
"usb": "USB",
"usb_config_custom": "Beställnings",
"usb_config_custom": "Anpassad",
"usb_config_default": "JetKVM-standard",
"usb_config_dell": "Dell Multimedia Pro-tangentbord",
"usb_config_failed_load": "Misslyckades med att ladda USB-konfigurationen: {error}",
@ -767,7 +779,7 @@
"usb_config_vendor_id_placeholder": "Ange leverantörs-ID",
"usb_device_classes_description": "USB-enhetsklasser i den sammansatta enheten",
"usb_device_classes_title": "Klasser",
"usb_device_custom": "Beställnings",
"usb_device_custom": "Anpassad",
"usb_device_description": "USB-enheter att emulera på måldatorn",
"usb_device_enable_absolute_mouse_description": "Aktivera absolut mus (pekare)",
"usb_device_enable_absolute_mouse_title": "Aktivera absolut mus (pekare)",
@ -802,7 +814,7 @@
"video_description": "Konfigurera skärminställningar och EDID för optimal kompatibilitet",
"video_edid_acer_b246wl": "Acer B246WL, 1920x1200",
"video_edid_asus_pa248qv": "ASUS PA248QV, 1920x1200",
"video_edid_custom": "Beställnings",
"video_edid_custom": "Anpassad",
"video_edid_dell_d2721h": "DELL D2721H, 1920x1080",
"video_edid_dell_idrac": "DELL IDRAC EDID, 1280x1024",
"video_edid_description": "Justera EDID-inställningarna för skärmen",

View File

@ -50,7 +50,6 @@
"action_bar_connection_stats": "连接统计",
"action_bar_extension": "扩展",
"action_bar_fullscreen": "全屏",
"action_bar_paste_text": "粘贴文本",
"action_bar_settings": "设置",
"action_bar_virtual_keyboard": "虚拟键盘",
"action_bar_virtual_media": "虚拟媒体",
@ -142,7 +141,6 @@
"auth_mode_local_password_confirm_description": "确认您的密码",
"auth_mode_local_password_confirm_label": "确认密码",
"auth_mode_local_password_description": "使用密码保护您的设备以增强保护。",
"auth_mode_local_password_do_not_match": "密码不匹配",
"auth_mode_local_password_failed_set": "无法设置密码: {error}",
"auth_mode_local_password_note": "此密码将用于保护您的设备数据并防止未经授权的访问。",
"auth_mode_local_password_note_local": "所有数据都保留在您的本地设备上。",
@ -181,6 +179,9 @@
"connection_stats_round_trip_time": "往返时间",
"connection_stats_round_trip_time_description": "对等体之间活跃 ICE 候选对的往返时间。",
"connection_stats_sidebar": "连接统计",
"connection_stats_unit_frames_per_second": " 帧每秒",
"connection_stats_unit_milliseconds": " 毫秒",
"connection_stats_unit_packets": " 数据包",
"connection_stats_video": "视频",
"connection_stats_video_description": "从 JetKVM 到客户端的视频流。",
"continue": "继续",
@ -207,6 +208,8 @@
"deregister_error": "注销您的设备时出现错误{status} 。请重试。",
"deregister_headline": "从您的云帐户中取消注册{device}",
"detach": "分离",
"dhcp_empty_lease_description": "我们尚未收到来自该设备的任何 DHCP 租约信息。",
"dhcp_empty_lease_headline": "无 DHCP 租约信息",
"dhcp_lease_boot_file": "引导文件",
"dhcp_lease_boot_next_server": "启动下一个服务器",
"dhcp_lease_boot_server_name": "启动服务器名称",
@ -426,7 +429,8 @@
"macro_name_too_long": "名称必须少于 50 个字符",
"macro_please_fix_validation_errors": "请修复验证错误",
"macro_save": "保存宏",
"macro_save_error": "保存时发生错误。",
"macro_save_failed": "保存时发生错误。",
"macro_save_failed_error": "保存时发生错误:{error}。",
"macro_step_count": "{steps} / {max}步骤",
"macro_step_duration_description": "执行下一步之前需要等待的时间。",
"macro_step_duration_label": "步骤持续时间",
@ -578,7 +582,6 @@
"network_dhcp_client_description": "配置要使用的 DHCP 客户端",
"network_dhcp_client_jetkvm": "JetKVM 内部",
"network_dhcp_client_title": "DHCP客户端",
"network_dhcp_information": "DHCP 信息",
"network_dhcp_lease_renew": "续订 DHCP 租约",
"network_dhcp_lease_renew_confirm": "续租",
"network_dhcp_lease_renew_confirm_description": "这将从您的 DHCP 服务器请求新的 IP 地址。在此过程中,您的设备可能会暂时失去网络连接。",
@ -599,13 +602,21 @@
"network_ipv4_address": "IPv4 地址",
"network_ipv4_dns": "IPv4 域名服务器",
"network_ipv4_gateway": "IPv4 网关",
"network_ipv4_invalid": "IPv4 地址无效",
"network_ipv4_invalid_cidr": "IPv4 地址的 CIDR 表示法无效",
"network_ipv4_mode_description": "配置 IPv4 模式",
"network_ipv4_mode_dhcp": "DHCP",
"network_ipv4_mode_static": "静止的",
"network_ipv4_mode_title": "IPv4 模式",
"network_ipv4_netmask": "IPv4 网络掩码",
"network_ipv6_address": "IPv6 地址",
"network_ipv6_addresses_header": "IPv6 地址",
"network_ipv6_cidr_suggestion": "请使用 CIDR 表示法例如2001:db8::1/64",
"network_ipv6_dns": "IPv6 域名服务器",
"network_ipv6_flag_dad_failed": "DAD 失败",
"network_ipv6_flag_deprecated": "已弃用",
"network_ipv6_gateway": "IPv6网关",
"network_ipv6_information": "IPv6 信息",
"network_ipv6_invalid": "IPv6 地址无效",
"network_ipv6_mode_description": "配置 IPv6 模式",
"network_ipv6_mode_dhcpv6": "DHCPv6",
"network_ipv6_mode_disabled": "已禁用",
@ -614,8 +625,8 @@
"network_ipv6_mode_slaac_dhcpv6": "SLAAC + DHCPv6",
"network_ipv6_mode_static": "静止的",
"network_ipv6_mode_title": "IPv6模式",
"network_ipv6_netmask": "IPv6 网络掩码",
"network_ipv6_no_addresses": "未配置 IPv6 地址",
"network_ipv6_prefix": "IP 前缀",
"network_ipv6_prefix_invalid": "前缀必须介于 0 到 128 之间",
"network_ll_dp_all": "全部",
"network_ll_dp_basic": "基本的",
"network_ll_dp_description": "控制哪些 TLV 将通过链路层发现协议发送",
@ -631,7 +642,6 @@
"network_mdns_ipv4_only": "仅限 IPv4",
"network_mdns_ipv6_only": "仅限 IPv6",
"network_mdns_title": "移动DNS",
"network_no_dhcp_lease": "没有可用的 DHCP 租约信息",
"network_no_information_description": "没有可用的网络配置",
"network_no_information_headline": "网络信息",
"network_pending_dhcp_mode_change_description": "保存设置以启用 DHCP 模式并查看租约信息",
@ -643,8 +653,10 @@
"network_save_settings_confirm_heading": "配置更改",
"network_save_settings_failed": "无法保存网络设置: {error}",
"network_save_settings_success": "网络设置已保存",
"network_settings_invalid_ipv4_cidr": "IPv4 地址的 CIDR 表示法无效",
"network_settings_add_dns": "添加 DNS 服务器",
"network_settings_load_error": "无法加载网络设置: {error}",
"network_static_ipv4_header": "静态 IPv4 配置",
"network_static_ipv6_header": "静态 IPv6 配置",
"network_time_sync_description": "配置时间同步设置",
"network_time_sync_http_only": "仅 HTTP",
"network_time_sync_ntp_and_http": "NTP 和 HTTP",
@ -670,9 +682,9 @@
"paste_modal_failed_paste": "粘贴文本失败: {error}",
"paste_modal_invalid_chars_intro": "以下字符将不会被粘贴:",
"paste_modal_paste_from_host": "从主机粘贴",
"paste_modal_paste_text": "粘贴文本",
"paste_modal_paste_text_description": "将文本从客户端粘贴到远程主机",
"paste_modal_sending_using_layout": "使用键盘布局发送文本: {iso} - {name}",
"paste_text": "粘贴文本",
"paste_text_description": "将文本从客户端粘贴到远程主机",
"peer_connection_closed": "关闭",
"peer_connection_closing": "结束语",
"peer_connection_connected": "已连接",

45
ui/package-lock.json generated
View File

@ -1,16 +1,17 @@
{
"name": "kvm-ui",
"version": "2025.10.14.2130",
"version": "2025.10.15.1700",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kvm-ui",
"version": "2025.10.14.2130",
"version": "2025.10.15.1700",
"dependencies": {
"@headlessui/react": "^2.2.9",
"@headlessui/tailwindcss": "^0.2.2",
"@heroicons/react": "^2.2.0",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-basic-ssl": "^2.1.0",
"@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0",
@ -68,7 +69,7 @@
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-react-refresh": "^0.4.23",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.4.0",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
@ -126,6 +127,7 @@
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@ -2405,6 +2407,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@ -2414,6 +2417,7 @@
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@ -2431,6 +2435,12 @@
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@types/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"license": "MIT"
},
"node_modules/@types/validator": {
"version": "13.15.3",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz",
@ -2484,6 +2494,7 @@
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/types": "8.46.1",
@ -2790,13 +2801,15 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -3132,6 +3145,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
@ -3371,7 +3385,8 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/cva": {
"version": "1.0.0-beta.4",
@ -3967,6 +3982,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -4028,6 +4044,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@ -4101,6 +4118,7 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@ -5523,6 +5541,7 @@
"integrity": "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=14.0.0"
}
@ -6247,6 +6266,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@ -6292,6 +6312,7 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@ -6448,6 +6469,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -6470,6 +6492,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@ -6531,6 +6554,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@ -6627,7 +6651,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@ -7217,7 +7242,8 @@
"version": "4.1.14",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/tapable": {
"version": "2.3.0",
@ -7304,6 +7330,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -7480,6 +7507,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -7663,6 +7691,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz",
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@ -7774,6 +7803,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -7922,6 +7952,7 @@
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View File

@ -1,7 +1,7 @@
{
"name": "kvm-ui",
"private": true,
"version": "2025.10.14.2130",
"version": "2025.10.15.1700",
"type": "module",
"engines": {
"node": "^22.20.0"
@ -16,14 +16,20 @@
"build:prod": "npm run paraglide && tsc && vite build --mode=cloud-production",
"lint": "npm run paraglide && eslint './src/**/*.{ts,tsx}'",
"lint:fix": "npm run paraglide && eslint './src/**/*.{ts,tsx}' --fix",
"paraglide": "paraglide-js compile --project ./localization/jetKVM.UI.inlang --outdir ./localization/paraglide",
"validate": "inlang validate --project ./localization/jetKVM.UI.inlang",
"machine-translate": "inlang machine translate --project ./localization/jetKVM.UI.inlang"
"i18n": "npm run i18n:resort && npm run i18n:validate && npm run i18n:compile",
"i18n:resort": "python3 tools/resort_messages.py",
"i18n:validate": "inlang validate --project ./localization/jetKVM.UI.inlang",
"i18n:compile": "paraglide-js compile --project ./localization/jetKVM.UI.inlang --outdir ./localization/paraglide",
"i18n:machine-translate": "inlang machine translate --project ./localization/jetKVM.UI.inlang",
"i18n:find-excess": "python3 ./tools/find_excess_messages.py",
"i18n:find-unused": "python3 ./tools/find_unused_messages.py",
"i18n:find-dupes": "python3 ./tools/find_duplicate_translations.py"
},
"dependencies": {
"@headlessui/react": "^2.2.9",
"@headlessui/tailwindcss": "^0.2.2",
"@heroicons/react": "^2.2.0",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-basic-ssl": "^2.1.0",
"@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0",
@ -41,8 +47,8 @@
"react": "^19.2.0",
"react-animate-height": "^3.2.3",
"react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-hook-form": "^7.65.0",
"react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0",
"react-router": "^7.9.4",
"react-simple-keyboard": "^3.8.130",
@ -81,7 +87,7 @@
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-react-refresh": "^0.4.23",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.4.0",
"postcss": "^8.5.6",
"prettier": "^3.6.2",

View File

@ -72,7 +72,7 @@ export default function Actionbar({
<Button
size="XS"
theme="light"
text={m.action_bar_paste_text()}
text={m.paste_text()}
LeadingIcon={MdOutlineContentPasteGo}
onClick={() => {
setDisableVideoFocusTrap(true);

View File

@ -1,12 +1,12 @@
import { LuRefreshCcw } from "react-icons/lu";
import { Button } from "@components/Button";
import EmptyCard from "@components/EmptyCard";
import { GridCard } from "@components/Card";
import { LifeTimeLabel } from "@routes/devices.$id.settings.network";
import { NetworkState } from "@hooks/stores";
import { m } from "@localizations/messages.js";
import EmptyCard from "./EmptyCard";
export default function DhcpLeaseCard({
networkState,
@ -20,8 +20,8 @@ export default function DhcpLeaseCard({
if (isDhcpLeaseEmpty) {
return (
<EmptyCard
headline="No DHCP Lease information"
description="We haven't received any DHCP lease information from the device yet."
headline={m.dhcp_empty_lease_headline()}
description={m.dhcp_empty_lease_description()}
/>
);
}
@ -41,7 +41,7 @@ export default function DhcpLeaseCard({
theme="light"
type="button"
className="text-red-500"
text="Renew DHCP Lease"
text={m.dhcp_lease_renew()}
LeadingIcon={LuRefreshCcw}
onClick={() => setShowRenewLeaseConfirm(true)}
/>
@ -54,7 +54,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ip_address()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.ip}
</span>
@ -65,7 +65,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.subnet_mask()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.netmask}
</span>
@ -76,7 +76,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dns_servers()}
</span>
</span>&nbsp;
<span className="text-right text-sm font-medium">
{networkState?.dhcp_lease?.dns_servers.map(dns => (
<div key={dns}>{dns}</div>
@ -89,7 +89,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_broadcast()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.broadcast}
</span>
@ -100,7 +100,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_domain()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.domain}
</span>
@ -112,7 +112,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between gap-x-8 border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<div className="w-full grow text-sm text-slate-600 dark:text-slate-400">
{m.ntp_servers()}
</div>
</div>&nbsp;
<div className="shrink text-right text-sm font-medium">
{networkState?.dhcp_lease?.ntp_servers.map(server => (
<div key={server}>{server}</div>
@ -125,7 +125,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_hostname()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.hostname}
</span>
@ -139,7 +139,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between pt-2">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_gateway()}
</span>
</span>&nbsp;
<span className="text-right text-sm font-medium">
{networkState?.dhcp_lease?.routers.map(router => (
<div key={router}>{router}</div>
@ -152,7 +152,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_server()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.server_id}
</span>
@ -163,7 +163,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_lease_expires()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
<LifeTimeLabel
lifetime={`${networkState?.dhcp_lease?.lease_expiry}`}
@ -175,8 +175,8 @@ export default function DhcpLeaseCard({
{networkState?.dhcp_lease?.broadcast && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
Broadcast
</span>
{m.dhcp_lease_broadcast()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.broadcast}
</span>
@ -185,18 +185,22 @@ export default function DhcpLeaseCard({
{networkState?.dhcp_lease?.mtu && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">MTU</span>
<span className="text-sm font-medium">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_maximum_transfer_unit()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.mtu}
</span>
</div>
)}
{networkState?.dhcp_lease?.ttl && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">TTL</span>
<span className="text-sm font-medium">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_time_to_live()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.ttl}
</span>
</div>
)}
@ -205,7 +209,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_boot_next_server()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_next_server}
</span>
@ -216,7 +220,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_boot_server_name()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_server_name}
</span>
@ -227,7 +231,7 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_boot_file()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_file}
</span>
@ -236,8 +240,12 @@ export default function DhcpLeaseCard({
{networkState?.dhcp_lease?.dhcp_client && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">DHCP Client</span>
<span className="text-sm font-medium">{networkState?.dhcp_lease?.dhcp_client}</span>
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.network_dhcp_client_title()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.dhcp_client}
</span>
</div>
)}
</div>

View File

@ -33,7 +33,7 @@ export default function Ipv6NetworkCard({
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_link_local()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.ipv6_link_local}
</span>
@ -42,7 +42,7 @@ export default function Ipv6NetworkCard({
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_gateway()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.ipv6_gateway}
</span>
@ -52,7 +52,9 @@ export default function Ipv6NetworkCard({
<div className="space-y-3 pt-2">
{networkState?.ipv6_addresses && networkState?.ipv6_addresses.length > 0 && (
<div className="space-y-3">
<h4 className="text-sm font-semibold">IPv6 Addresses</h4>
<h4 className="text-sm font-semibold">
{m.network_ipv6_addresses_header()}
</h4>
{networkState.ipv6_addresses.map(addr => (
<div
key={addr.address}
@ -62,12 +64,12 @@ export default function Ipv6NetworkCard({
<div className="col-span-2 flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_address_label()}
</span>
</span>&nbsp;
<span className="text-sm font-medium flex">
<span className="flex-1">{addr.address}</span>
<span className="flex-1">{addr.address}</span>&nbsp;
<span className="text-sm font-medium flex gap-x-1">
{addr.flag_deprecated ? <FlagLabel flag="Deprecated" /> : null}
{addr.flag_dad_failed ? <FlagLabel flag="DAD Failed" /> : null}
{addr.flag_deprecated ? <FlagLabel flag={m.network_ipv6_flag_deprecated()} /> : null}
{addr.flag_dad_failed ? <FlagLabel flag={m.network_ipv6_flag_dad_failed()} /> : null}
</span>
</span>
</div>
@ -76,7 +78,7 @@ export default function Ipv6NetworkCard({
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_valid_lifetime()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{addr.valid_lifetime === "" ? (
<span className="text-slate-400 dark:text-slate-600">
@ -93,7 +95,7 @@ export default function Ipv6NetworkCard({
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_preferred_lifetime()}
</span>
</span>&nbsp;
<span className="text-sm font-medium">
{addr.preferred_lifetime === "" ? (
<span className="text-slate-400 dark:text-slate-600">

View File

@ -39,7 +39,7 @@ export function MacroForm({
onSubmit,
onCancel,
isSubmitting = false,
}: MacroFormProps) {
}: Readonly<MacroFormProps>) {
const [macro, setMacro] = useState<Partial<KeySequence>>(initialData);
const [keyQueries, setKeyQueries] = useState<Record<number, string>>({});
const [errors, setErrors] = useState<ValidationErrors>({});
@ -61,11 +61,11 @@ export function MacroForm({
newErrors.name = m.macro_name_too_long();
}
if (!macro.steps?.length) {
newErrors.steps = { 0: { keys: m.macro_at_least_one_step_required() } };
} else {
const hasKeyOrModifier = macro.steps.some(
step => (step.keys?.length || 0) > 0 || (step.modifiers?.length || 0) > 0,
const steps = (macro.steps || []);
if (steps.length) {
const hasKeyOrModifier = steps.some(
step => step.keys.length > 0 || step.modifiers.length > 0,
);
if (!hasKeyOrModifier) {
@ -73,6 +73,8 @@ export function MacroForm({
0: { keys: m.macro_at_least_one_step_keys_or_modifiers() },
};
}
} else {
newErrors.steps = { 0: { keys: m.macro_at_least_one_step_required() } };
}
setErrors(newErrors);
@ -89,9 +91,9 @@ export function MacroForm({
await onSubmit(macro);
} catch (error) {
if (error instanceof Error) {
showTemporaryError(error.message);
showTemporaryError(m.macro_save_failed_error(error.message || m.unknown_error));
} else {
showTemporaryError(m.macro_save_error());
showTemporaryError(m.macro_save_failed());
}
}
};
@ -104,14 +106,14 @@ export function MacroForm({
if (!newSteps[stepIndex]) return;
if (option.keys) {
// they gave us a full set of keys (e.g. from deleting one)
newSteps[stepIndex].keys = option.keys;
} else if (option.value) {
// they gave us a single key to add
if (!newSteps[stepIndex].keys) {
newSteps[stepIndex].keys = [];
}
const keysArray = Array.isArray(newSteps[stepIndex].keys)
? newSteps[stepIndex].keys
: [];
const keysArray = newSteps[stepIndex].keys;
if (keysArray.length >= MAX_KEYS_PER_STEP) {
showTemporaryError(m.macro_max_steps_error({ max: MAX_KEYS_PER_STEP }));
return;
@ -172,7 +174,6 @@ export function MacroForm({
const isMaxStepsReached = (macro.steps?.length || 0) >= MAX_STEPS_PER_MACRO;
return (
<>
<div className="space-y-4">
<Fieldset>
<InputFieldWithLabel
@ -204,16 +205,16 @@ export function MacroForm({
{m.macro_step_count({ steps: macro.steps?.length || 0, max: MAX_STEPS_PER_MACRO })}
</span>
</div>
{errors.steps && errors.steps[0]?.keys && (
{errors.steps?.[0]?.keys && (
<div className="mt-2">
<FieldError error={errors.steps[0].keys} />
<FieldError error={errors.steps?.[0]?.keys} />
</div>
)}
<Fieldset>
<div className="mt-2 space-y-4">
{(macro.steps || []).map((step, stepIndex) => (
<MacroStepCard
key={stepIndex}
key={`step-{stepIndex}`}
step={step}
stepIndex={stepIndex}
onDelete={
@ -285,6 +286,5 @@ export function MacroForm({
</div>
</div>
</div>
</>
);
}

View File

@ -12,7 +12,7 @@ import { keys, modifiers } from "@/keyboardMappings";
import { m } from "@localizations/messages.js";
// Filter out modifier keys since they're handled in the modifiers section
const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta'];
const modifierKeyPrefixes = ["Alt", "Control", "Shift", "Meta"];
const modifierOptions = Object.keys(modifiers).map(modifier => ({
value: modifier,
@ -20,10 +20,10 @@ const modifierOptions = Object.keys(modifiers).map(modifier => ({
}));
const groupedModifiers: Record<string, typeof modifierOptions> = {
Control: modifierOptions.filter(mod => mod.value.startsWith('Control')),
Shift: modifierOptions.filter(mod => mod.value.startsWith('Shift')),
Alt: modifierOptions.filter(mod => mod.value.startsWith('Alt')),
Meta: modifierOptions.filter(mod => mod.value.startsWith('Meta')),
Control: modifierOptions.filter(mod => mod.value.startsWith("Control")),
Shift: modifierOptions.filter(mod => mod.value.startsWith("Shift")),
Alt: modifierOptions.filter(mod => mod.value.startsWith("Alt")),
Meta: modifierOptions.filter(mod => mod.value.startsWith("Meta")),
};
// not going to localize these since they're short time intervals
@ -64,13 +64,17 @@ interface MacroStepCardProps {
onModifierChange: (modifiers: string[]) => void;
onDelayChange: (delay: number) => void;
isLastStep: boolean;
keyboard: KeyboardLayout
keyboard: KeyboardLayout;
}
const ensureArray = <T,>(arr: T[] | null | undefined): T[] => {
return Array.isArray(arr) ? arr : [];
};
const keyDisplay = (keyDisplayMap: Record<string, string>, key: string): string => {
return keyDisplayMap[key] || key
};
export function MacroStepCard({
step,
stepIndex,
@ -83,28 +87,42 @@ export function MacroStepCard({
onModifierChange,
onDelayChange,
isLastStep,
keyboard
}: MacroStepCardProps) {
keyboard,
}: Readonly<MacroStepCardProps>) {
const { keyDisplayMap } = keyboard;
const keyOptions = useMemo(() =>
const keyOptions = useMemo(
() =>
Object.keys(keys)
.filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix)))
.map(key => ({
value: key,
label: keyDisplayMap[key] || key,
label: keyDisplay(keyDisplayMap, key),
})),
[keyDisplayMap]
[keyDisplayMap],
);
const handleModifierToggle = (optionValue: string) => {
const modifiersArray = ensureArray(step.modifiers);
const isSelected = modifiersArray.includes(optionValue);
const newModifiers = isSelected
? modifiersArray.filter(m => m !== optionValue)
: [...modifiersArray, optionValue];
onModifierChange(newModifiers);
};
const filteredKeys = useMemo(() => {
const selectedKeys = ensureArray(step.keys);
const availableKeys = keyOptions.filter(option => !selectedKeys.includes(option.value));
const availableKeys = keyOptions.filter(
option => !selectedKeys.includes(option.value),
);
if (keyQuery === '') {
if (keyQuery === "") {
return availableKeys;
} else {
return availableKeys.filter(option => option.label.toLowerCase().includes(keyQuery.toLowerCase()));
return availableKeys.filter(option =>
option.label.toLowerCase().includes(keyQuery.toLowerCase()),
);
}
}, [keyOptions, keyQuery, step.keys]);
@ -147,13 +165,19 @@ export function MacroStepCard({
</div>
</div>
<div className="space-y-4 mt-2">
<div className="w-full flex flex-col gap-2">
<FieldLabel label={m.macro_step_modifiers_label()} description={m.macro_step_modifiers_description()} />
<div className="mt-2 space-y-4">
<div className="flex w-full flex-col gap-2">
<FieldLabel
label={m.macro_step_modifiers_label()}
description={m.macro_step_modifiers_description()}
/>
<div className="inline-flex flex-wrap gap-3">
{Object.entries(groupedModifiers).map(([group, mods]) => (
<div key={group} className="relative min-w-[120px] rounded-md border border-slate-200 dark:border-slate-700 p-2">
<span className="absolute -top-2.5 left-2 px-1 text-xs font-medium bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400">
<div
key={group}
className="relative min-w-[120px] rounded-md border border-slate-200 p-2 dark:border-slate-700"
>
<span className="absolute -top-2.5 left-2 bg-white px-1 text-xs font-medium text-slate-500 dark:bg-slate-800 dark:text-slate-400">
{group}
</span>
<div className="flex flex-wrap gap-4 pt-1">
@ -161,16 +185,13 @@ export function MacroStepCard({
<Button
key={option.value}
size="XS"
theme={ensureArray(step.modifiers).includes(option.value) ? "primary" : "light"}
text={option.label.split(' ')[1] || option.label}
onClick={() => {
const modifiersArray = ensureArray(step.modifiers);
const isSelected = modifiersArray.includes(option.value);
const newModifiers = isSelected
? modifiersArray.filter(m => m !== option.value)
: [...modifiersArray, option.value];
onModifierChange(newModifiers);
}}
theme={
ensureArray(step.modifiers).includes(option.value)
? "primary"
: "light"
}
text={option.label.split(" ")[1] || option.label}
onClick={() => handleModifierToggle(option.value)}
/>
))}
</div>
@ -179,27 +200,30 @@ export function MacroStepCard({
</div>
</div>
<div className="w-full flex flex-col gap-1">
<div className="flex w-full flex-col gap-1">
<div className="flex items-center gap-1">
<FieldLabel label={m.macro_step_keys_label()} description={m.macro_step_keys_description({ max: MAX_KEYS_PER_STEP })} />
<FieldLabel
label={m.macro_step_keys_label()}
description={m.macro_step_keys_description({ max: MAX_KEYS_PER_STEP })}
/>
</div>
{ensureArray(step.keys) && step.keys.length > 0 && (
{step.keys?.length > 0 && (
<div className="flex flex-wrap gap-1 pb-2">
{step.keys.map((key, keyIndex) => (
<span
key={keyIndex}
className="inline-flex items-center py-0.5 rounded-md bg-blue-100 px-1 text-xs font-medium text-blue-800 dark:bg-blue-900/40 dark:text-blue-200"
key={`key-{keyIndex}`}
className="inline-flex items-center rounded-md bg-blue-100 px-1 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900/40 dark:text-blue-200"
>
<span className="px-1">
{keyDisplayMap[key] || key}
</span>
<span className="px-1">{keyDisplay(keyDisplayMap, key)}</span>
<Button
size="XS"
className=""
theme="blank"
onClick={() => {
const newKeys = ensureArray(step.keys).filter((_, i) => i !== keyIndex);
const newKeys = step.keys.filter(
(_, i) => i !== keyIndex,
);
onKeySelect({ value: null, keys: newKeys });
}}
LeadingIcon={LuX}
@ -210,10 +234,10 @@ export function MacroStepCard({
)}
<div className="relative w-full">
<Combobox
onChange={(option) => {
onChange={option => {
const selectedOption = option as ComboboxOption | null;
onKeySelect({ value: selectedOption?.value ?? null });
onKeyQueryChange('');
onKeyQueryChange("");
}}
displayValue={() => keyQuery}
onInputChange={onKeyQueryChange}
@ -222,22 +246,29 @@ export function MacroStepCard({
size="SM"
immediate
disabled={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP}
placeholder={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP ? m.macro_step_max_keys_reached() : m.macro_step_search_for_key()}
placeholder={
ensureArray(step.keys).length >= MAX_KEYS_PER_STEP
? m.macro_step_max_keys_reached()
: m.macro_step_search_for_key()
}
emptyMessage={m.macro_step_no_matching_keys_found()}
/>
</div>
</div>
<div className="w-full flex flex-col gap-1">
<div className="flex w-full flex-col gap-1">
<div className="flex items-center gap-1">
<FieldLabel label={m.macro_step_duration_label()} description={m.macro_step_duration_description()} />
<FieldLabel
label={m.macro_step_duration_label()}
description={m.macro_step_duration_description()}
/>
</div>
<div className="flex items-center gap-3">
<SelectMenuBasic
size="SM"
fullWidth
value={step.delay.toString()}
onChange={(e) => onDelayChange(parseInt(e.target.value, 10))}
onChange={e => onDelayChange(Number.parseInt(e.target.value, 10))}
options={PRESET_DELAYS}
/>
</div>

View File

@ -1,14 +1,15 @@
import { LuPlus, LuX } from "react-icons/lu";
import { useFieldArray, useFormContext } from "react-hook-form";
import { useEffect } from "react";
import validator from "validator";
import { LuPlus, LuX } from "react-icons/lu";
import { useFieldArray, useFormContext } from "react-hook-form";
import { cx } from "cva";
import { GridCard } from "@/components/Card";
import { Button } from "@/components/Button";
import { InputFieldWithLabel } from "@/components/InputField";
import { NetworkSettings } from "@/hooks/stores";
import { NetworkSettings } from "@hooks/stores";
import { Button } from "@components/Button";
import { GridCard } from "@components/Card";
import { InputFieldWithLabel } from "@components/InputField";
import { netMaskFromCidr4 } from "@/utils/ip";
import { m } from "@localizations/messages.js";
export default function StaticIpv4Card() {
const formMethods = useFormContext<NetworkSettings>();
@ -24,6 +25,7 @@ export default function StaticIpv4Card() {
const ipv4StaticAddress = watch("ipv4_static.address");
const hideSubnetMask = ipv4StaticAddress?.includes("/");
useEffect(() => {
const parts = ipv4StaticAddress?.split("/", 2);
if (parts?.length !== 2) return;
@ -35,13 +37,13 @@ export default function StaticIpv4Card() {
setValue("ipv4_static.netmask", mask);
}, [ipv4StaticAddress, setValue]);
const validate = (value: string) => {
if (!validator.isIP(value)) return "Invalid IP address";
const ipv4Validation = (value: string) => {
if (!validator.isIP(value, 4)) return m.network_ipv4_invalid()
return true;
};
const validateIsIPOrCIDR4 = (value: string) => {
if (!validator.isIP(value, 4) && !validator.isIPRange(value, 4)) return "Invalid IP address or CIDR notation";
if (!validator.isIP(value) && !validator.isIPRange(value, 4)) return m.network_ipv4_invalid_cidr();
return true;
};
@ -50,12 +52,12 @@ export default function StaticIpv4Card() {
<div className="animate-fadeIn p-4 text-black opacity-0 animation-duration-500 dark:text-white">
<div className="space-y-4">
<h3 className="text-base font-bold text-slate-900 dark:text-white">
Static IPv4 Configuration
{m.network_static_ipv4_header()}
</h3>
<div className={cx("grid grid-cols-1 gap-4", hideSubnetMask ? "md:grid-cols-1" : "md:grid-cols-2")}>
<InputFieldWithLabel
label="IP Address"
label={m.network_ipv4_address()}
type="text"
size="SM"
placeholder="192.168.1.100"
@ -67,21 +69,21 @@ export default function StaticIpv4Card() {
/>
{!hideSubnetMask && <InputFieldWithLabel
label="Subnet Mask"
label={m.network_ipv4_netmask()}
type="text"
size="SM"
placeholder="255.255.255.0"
{...register("ipv4_static.netmask", { validate: (value: string | undefined) => validate(value ?? "") })}
{...register("ipv4_static.netmask", { validate: (value: string | undefined) => ipv4Validation(value ?? "") })}
error={formState.errors.ipv4_static?.netmask?.message}
/>}
</div>
<InputFieldWithLabel
label="Gateway"
label={m.network_ipv4_gateway()}
type="text"
size="SM"
placeholder="192.168.1.1"
{...register("ipv4_static.gateway", { validate: (value: string | undefined) => validate(value ?? "") })}
{...register("ipv4_static.gateway", { validate: (value: string | undefined) => ipv4Validation(value ?? "") })}
error={formState.errors.ipv4_static?.gateway?.message}
/>
@ -93,13 +95,13 @@ export default function StaticIpv4Card() {
<div className="flex items-start gap-x-2">
<div className="flex-1">
<InputFieldWithLabel
label={index === 0 ? "DNS Server" : null}
label={index === 0 ? m.network_ipv4_dns() : null}
type="text"
size="SM"
placeholder="1.1.1.1"
{...register(
`ipv4_static.dns.${index}`,
{ validate: (value: string | undefined) => validate(value ?? "") }
{ validate: (value: string | undefined) => ipv4Validation(value ?? "") }
)}
error={formState.errors.ipv4_static?.dns?.[index]?.message}
/>
@ -127,7 +129,7 @@ export default function StaticIpv4Card() {
onClick={() => append("", { shouldFocus: true })}
LeadingIcon={LuPlus}
type="button"
text="Add DNS Server"
text={m.network_settings_add_dns()}
disabled={dns?.[0] === ""}
/>
</div>

View File

@ -1,12 +1,13 @@
import { useEffect } from "react";
import validator from "validator";
import { LuPlus, LuX } from "react-icons/lu";
import { useFieldArray, useFormContext } from "react-hook-form";
import validator from "validator";
import { useEffect } from "react";
import { GridCard } from "@/components/Card";
import { Button } from "@/components/Button";
import { InputFieldWithLabel } from "@/components/InputField";
import { NetworkSettings } from "@/hooks/stores";
import { NetworkSettings } from "@hooks/stores";
import { Button } from "@components/Button";
import { GridCard } from "@components/Card";
import { InputFieldWithLabel } from "@components/InputField";
import { m } from "@localizations/messages.js";
export default function StaticIpv6Card() {
const formMethods = useFormContext<NetworkSettings>();
@ -25,20 +26,20 @@ export default function StaticIpv6Card() {
// Check if it's a valid IPv6 address with CIDR notation
const parts = value.split("/");
if (parts.length !== 2) return "Please use CIDR notation (e.g., 2001:db8::1/64)";
if (parts.length !== 2) return m.network_ipv6_cidr_suggestion();
const [address, prefix] = parts;
if (!validator.isIP(address, 6)) return "Invalid IPv6 address";
if (!validator.isIP(address, 6)) return m.network_ipv6_invalid();
const prefixNum = parseInt(prefix);
if (isNaN(prefixNum) || prefixNum < 0 || prefixNum > 128) {
return "Prefix must be between 0 and 128";
return m.network_ipv6_prefix_invalid();
}
return true;
};
const ipv6Validation = (value: string) => {
if (!validator.isIP(value, 6)) return "Invalid IPv6 address";
if (!validator.isIP(value, 6)) return m.network_ipv6_invalid()
return true;
};
@ -47,11 +48,11 @@ export default function StaticIpv6Card() {
<div className="animate-fadeIn p-4 text-black opacity-0 animation-duration-500 dark:text-white">
<div className="space-y-4">
<h3 className="text-base font-bold text-slate-900 dark:text-white">
Static IPv6 Configuration
{m.network_static_ipv6_header()}
</h3>
<InputFieldWithLabel
label="IP Prefix"
label={m.network_ipv6_prefix()}
type="text"
size="SM"
placeholder="2001:db8::1/64"
@ -60,7 +61,7 @@ export default function StaticIpv6Card() {
/>
<InputFieldWithLabel
label="Gateway"
label={m.network_ipv6_gateway()}
type="text"
size="SM"
placeholder="2001:db8::1"
@ -76,7 +77,7 @@ export default function StaticIpv6Card() {
<div className="flex items-start gap-x-2">
<div className="flex-1">
<InputFieldWithLabel
label={index === 0 ? "DNS Server" : null}
label={index === 0 ? m.network_ipv6_dns() : null}
type="text"
size="SM"
placeholder="2001:4860:4860::8888"
@ -107,7 +108,7 @@ export default function StaticIpv6Card() {
onClick={() => append("", { shouldFocus: true })}
LeadingIcon={LuPlus}
type="button"
text="Add DNS Server"
text={m.network_settings_add_dns()}
disabled={dns?.[0] === ""}
/>
</div>

View File

@ -123,8 +123,8 @@ export default function PasteModal() {
<div className="h-full space-y-4">
<div className="space-y-4">
<SettingsPageHeader
title={m.paste_modal_paste_text()}
description={m.paste_modal_paste_text_description()}
title={m.paste_text()}
description={m.paste_text_description()}
/>
<div

View File

@ -116,7 +116,7 @@ export default function ConnectionStatsSidebar() {
metric: x.metric != null ? Math.round(x.metric * 1000) : null,
})}
domain={[0, 600]}
unit=" ms"
unit={m.connection_stats_units_milliseconds()}
/>
</div>
@ -140,7 +140,7 @@ export default function ConnectionStatsSidebar() {
metric: x.metric != null ? Math.round(x.metric * 1000) : null,
})}
domain={[0, 10]}
unit=" ms"
unit={m.connection_stats_units_milliseconds()}
/>
{/* Playback Delay */}
@ -162,7 +162,7 @@ export default function ConnectionStatsSidebar() {
)
}
domain={[0, 30]}
unit=" ms"
unit={m.connection_stats_unit_milliseconds()}
/>
{/* Packets Lost */}
@ -172,7 +172,7 @@ export default function ConnectionStatsSidebar() {
stream={inboundVideoRtpStats}
metric="packetsLost"
domain={[0, 100]}
unit=" packets"
unit={m.connection_stats_unit_packets()}
/>
{/* Frames Per Second */}
@ -182,7 +182,7 @@ export default function ConnectionStatsSidebar() {
stream={inboundVideoRtpStats}
metric="framesPerSecond"
domain={[0, 80]}
unit=" fps"
unit={m.connection_stats_unit_frames_per_second()}
/>
</div>
</div>

View File

@ -11,11 +11,13 @@ import { useJsonRpc } from "@hooks/useJsonRpc";
import AutoHeight from "@components/AutoHeight";
import { Button } from "@components/Button";
import { ConfirmDialog } from "@components/ConfirmDialog";
import DhcpLeaseCard from "@components/DhcpLeaseCard";
import EmptyCard from "@components/EmptyCard";
import { GridCard } from "@components/Card";
import InputField, { InputFieldWithLabel } from "@components/InputField";
import Ipv6NetworkCard from "@components/Ipv6NetworkCard";
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
import { SettingsItem } from "@components/SettingsItem";
import { SettingsPageHeader } from "@/components/SettingsPageheader";
import StaticIpv4Card from "@components/StaticIpv4Card";
import StaticIpv6Card from "@components/StaticIpv6Card";
@ -24,11 +26,11 @@ import { netMaskFromCidr4 } from "@/utils/ip";
import { getNetworkSettings, getNetworkState } from "@/utils/jsonrpc";
import notifications from "@/notifications";
import { m } from "@localizations/messages";
import { SettingsItem } from "@components/SettingsItem";
import DhcpLeaseCard from "@components/DhcpLeaseCard";
dayjs.extend(relativeTime);
const isLLDPAvailable = false; // LLDP is not supported yet
const resolveOnRtcReady = () => {
return new Promise(resolve => {
// Check if RTC is already connected
@ -160,7 +162,7 @@ export default function SettingsNetworkRoute() {
const parts = settings.ipv4_static.address.split("/");
const cidrNotation = parseInt(parts[1]);
if (isNaN(cidrNotation) || cidrNotation < 0 || cidrNotation > 32) {
return notifications.error(m.network_settings_invalid_ipv4_cidr());
return notifications.error(m.network_ipv4_invalid_cidr());
}
settings.ipv4_static.netmask = netMaskFromCidr4(cidrNotation);
settings.ipv4_static.address = parts[0];
@ -251,6 +253,30 @@ export default function SettingsNetworkRoute() {
});
}
if (dirty.ipv6_static?.prefix) {
changes.push({
label: m.network_ipv6_prefix(),
from: initialSettingsRef.current?.ipv6_static?.prefix as string,
to: data.ipv6_static?.prefix as string,
});
}
if (dirty.ipv4_static?.gateway) {
changes.push({
label: m.network_ipv6_gateway(),
from: initialSettingsRef.current?.ipv6_static?.gateway as string,
to: data.ipv6_static?.gateway as string,
});
}
if (dirty.ipv6_static?.dns) {
changes.push({
label: m.network_ipv6_dns(),
from: initialSettingsRef.current?.ipv6_static?.dns.join(", ").toString() ?? "",
to: data.ipv4_static?.dns.join(", ").toString() ?? "",
});
}
// If no critical fields are changed, save immediately
if (changes.length === 0) return onSubmit(settings);
@ -516,9 +542,7 @@ export default function SettingsNetworkRoute() {
</AutoHeight>
</div>
{ // eslint-disable-next-line no-constant-condition
false ? /* LLDP is not supported yet */
{ isLLDPAvailable &&
(
<div className="hidden space-y-4">
<SettingsItem
@ -536,7 +560,7 @@ export default function SettingsNetworkRoute() {
/>
</SettingsItem>
</div>
) : null
)
}
<div className="animate-fadeInStill animation-duration-300">
@ -598,7 +622,7 @@ export default function SettingsNetworkRoute() {
<path strokeLinecap="round" strokeLinejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
<code className="rounded border border-slate-800/20 bg-slate-50 px-1.5 py-1 text-xs text-black font-mono dark:border-slate-300/20 dark:bg-slate-800 dark:text-slate-100">
{c.to}
{c.to || "—"}
</code>
</div>
</div>
@ -611,7 +635,7 @@ export default function SettingsNetworkRoute() {
<ConfirmDialog
open={showRenewLeaseConfirm}
title={m.network_dhcp_lease_renew()}
title={m.dhcp_lease_renew()}
variant="warning"
confirmText={m.network_dhcp_lease_renew_confirm()}
description={

View File

@ -31,7 +31,7 @@ const action: ActionFunction = async ({ request }: ActionFunctionArgs) => {
const confirmPassword = formData.get("confirmPassword");
if (password !== confirmPassword) {
return { error: m.auth_mode_local_password_do_not_match() };
return { error: m.local_auth_error_passwords_not_match() };
}
try {

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3
import argparse
import json
import os
import re
from datetime import datetime
from pathlib import Path
@ -26,12 +25,30 @@ def normalize(s, ignore_case=False, trim=False, collapse_ws=False):
return s
def main():
p = argparse.ArgumentParser(description="Find identical translation targets with different keys in en.json")
p.add_argument("--en", default="../ui/localization/messages/en.json", help="path to en.json")
p.add_argument("--out", default="../reports/duplicate_translations.json", help="output report path (JSON)")
p.add_argument("--ignore-case", action="store_true", help="ignore case when comparing values")
p.add_argument("--trim", action="store_true", help="trim surrounding whitespace before comparing")
p.add_argument("--collapse-ws", action="store_true", help="collapse internal whitespace before comparing")
p = argparse.ArgumentParser(
description="Find identical translation targets with different keys in en.json"
)
p.add_argument(
"--en", default="./localization/messages/en.json", help="path to en.json"
)
p.add_argument(
"--out",
default="./reports/duplicate_translation_targets.json",
help="output report path (JSON)",
)
p.add_argument(
"--ignore-case", default=True, action="store_true", help="ignore case when comparing values"
)
p.add_argument(
"--trim",
default=True, action="store_true",
help="trim surrounding whitespace before comparing",
)
p.add_argument(
"--collapse-ws",
default=True, action="store_true",
help="collapse internal whitespace before comparing",
)
args = p.parse_args()
en_path = Path(args.en)
@ -48,7 +65,12 @@ def main():
groups = {}
original_values = {}
for key, val in entries:
norm = normalize(val, ignore_case=args.ignore_case, trim=args.trim, collapse_ws=args.collapse_ws)
norm = normalize(
val,
ignore_case=args.ignore_case,
trim=args.trim,
collapse_ws=args.collapse_ws,
)
groups.setdefault(norm, []).append(key)
# keep the first seen original for reporting
original_values.setdefault(norm, val)
@ -56,19 +78,23 @@ def main():
duplicates = []
for norm, keys in groups.items():
if len(keys) > 1:
duplicates.append({
duplicates.append(
{
"normalized_value": norm,
"original_value": original_values.get(norm),
"keys": sorted(keys),
"count": len(keys)
})
"count": len(keys),
}
)
report = {
"generated_at": datetime.utcnow().isoformat() + "Z",
"en_json": str(en_path),
"total_string_keys": total_keys,
"duplicate_groups": sorted(duplicates, key=lambda d: (-d["count"], d["normalized_value"])),
"duplicate_count": len(duplicates)
"duplicate_groups": sorted(
duplicates, key=lambda d: (-d["count"], d["normalized_value"])
),
"duplicate_count": len(duplicates),
}
out_path = Path(args.out)
@ -76,7 +102,9 @@ def main():
with out_path.open("w", encoding="utf-8") as f:
json.dump(report, f, indent=2, ensure_ascii=False)
print(f"Wrote {out_path} — total keys: {total_keys}, duplicate groups: {len(duplicates)}")
print(
f"Wrote {out_path} — total keys: {total_keys}, duplicate groups: {len(duplicates)}"
)
if __name__ == "__main__":
main()
sys.exit(main(sys.argv[1:]))

View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Find excess translation keys that exist in other language files but are not in en.json
Usage:
python3 tools/find_excess_messages.py
Optional: set `--json` to print machine-readable JSON output.
Optional: set `--path` to point to the directory containing messages JSON files.
Optional: set `--out` to specify a file to write the JSON report to.
"""
import argparse
import json
import sys
from pathlib import Path
from json import JSONDecodeError
def load_json(path: Path):
try:
return json.loads(path.read_text(encoding='utf-8'))
except (JSONDecodeError, OSError) as e:
print(f"Failed to read {path}: {e}")
return {}
def main(argv):
p = argparse.ArgumentParser(
description="Sort translations keys in message *.json files"
)
p.add_argument("--path", default="./localization/messages/", help="path to messages *.json")
p.add_argument("--json", default=False, action="store_true", help="output excess keys as JSON")
p.add_argument("--out", default="./reports/excess_messages.json", help="output report file")
args = p.parse_args()
messages_path = Path(args.path)
if not messages_path.is_dir():
print(f"message path is not a directory: {messages_path}")
raise SystemExit(2)
files = list(messages_path.glob("*.json"))
if len(files) == 0:
print(f"no message files (*.json) found in: {messages_path}")
raise SystemExit(3)
en_path = messages_path / 'en.json'
en = load_json(en_path) if en_path.exists() else {}
en_keys = set(en.keys())
extras = {} # key -> list of files where present
for f in files:
if f.name == 'en.json':
continue
data = load_json(f)
for k in data.keys():
if k not in en_keys:
extras.setdefault(k, []).append(f.name)
if '--json' in argv:
print(json.dumps(extras, ensure_ascii=False, indent=2))
return 0
if not extras:
print('No excess message keys found in other languages.')
return 0
print(f"Message keys present in other languages but not in en.json ({len(extras)}):\n")
for key, files in sorted(extras.items()):
print(f"{key}: {', '.join(files)}")
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

@ -14,7 +14,9 @@ def flatten(d, prefix=""):
else:
yield key
def gather_files(src_dir, exts=(".ts", ".tsx", ".js", ".jsx", ".html", ".vue", ".json")):
def gather_files(
src_dir, exts=(".ts", ".tsx", ".js", ".jsx")
):
for root, _, files in os.walk(src_dir):
parts = root.split(os.sep)
if "node_modules" in parts or ".git" in parts:
@ -25,29 +27,38 @@ def gather_files(src_dir, exts=(".ts", ".tsx", ".js", ".jsx", ".html", ".vue", "
def find_usages(keys, files):
usages = {k: [] for k in keys}
print(f"Compiling {len(keys)} patterns for keys ...")
# Precompile patterns for speed
patterns = {k: re.compile(r"\bm\." + re.escape(k) + r"\s*\(") for k in keys}
print(f"Scanning files...")
for file in files:
try:
text = file.read_text(encoding="utf-8")
except Exception:
except (OSError, UnicodeDecodeError):
# skip files that can't be read due to I/O or decoding errors
continue
lines = text.splitlines()
for i, line in enumerate(lines, start=1):
for k, pat in patterns.items():
if pat.search(line):
usages[k].append({
"file": str(file),
"line": i,
"text": line.strip()
})
usages[k].append(
{"file": str(file), "line": i, "text": line.strip()}
)
return usages
def main():
p = argparse.ArgumentParser(description="Generate JSON report of localization key usage (m.key_name_here()).")
p.add_argument("--en", default="../ui/localization/messages/en.json", help="path to en.json")
p.add_argument("--src", default="../ui", help="root source directory to scan")
p.add_argument("--out", default="../reports/localization_report.json", help="output report file")
p = argparse.ArgumentParser(
description="Generate JSON report of localization key usage (m.key_name_here())."
)
p.add_argument(
"--en", default="./localization/messages/en.json", help="path to en.json"
)
p.add_argument("--src", default="./", help="root source directory to scan")
p.add_argument(
"--out", default="./reports/localization_key_usage.json", help="output report file"
)
args = p.parse_args()
en_path = Path(args.en)
@ -55,34 +66,40 @@ def main():
print(f"en.json not found: {en_path}", flush=True)
raise SystemExit(2)
print(f"Reading english from {en_path}")
with en_path.open(encoding="utf-8") as f:
payload = json.load(f)
keys = sorted(list(flatten(payload)))
keys.pop(0) if keys and keys[0] == "$schema" else None # remove $schema if present
print(f"Found {len(keys)} localization keys")
print(f"Gathering source files in {args.src} ...")
files = list(gather_files(args.src))
print(f"Scanning {len(files)} source files ...")
usages = find_usages(keys, files)
print(f"Generating report for {len(usages)} usages ...")
report = {
"generated_at": datetime.utcnow().isoformat() + "Z",
"en_json": str(en_path),
"src_root": args.src,
"total_keys": len(keys),
"keys": {}
"keys": {},
}
unused_count = 0
for k in keys:
occ = usages.get(k, [])
used = bool(occ)
if not used:
unused_count += 1
report["keys"][k] = {
"used": used,
"occurrences": occ
}
report["keys"][k] = {"used": used, "occurrences": occ}
unused_keys = [k for k, v in report["keys"].items() if not v["used"]]
unused_count = len(unused_keys)
print(f"Found {unused_count} unused keys")
report["unused_count"] = unused_count
report["unused_keys"] = [k for k, v in report["keys"].items() if not v["used"]]
report["unused_keys"] = unused_keys
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
@ -92,5 +109,6 @@ def main():
print(f"Report written to {out_path}")
print(f"Total keys: {report['total_keys']}, Unused: {report['unused_count']}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
import argparse
import json
from pathlib import Path
def main():
p = argparse.ArgumentParser(
description="Sort translations keys in message *.json files"
)
p.add_argument(
"--path", default="./localization/messages/", help="path to messages *.json"
)
args = p.parse_args()
messages_path = Path(args.path)
if not messages_path.is_dir():
print(f"message path is not a directory: {messages_path}")
raise SystemExit(2)
files = list(messages_path.glob("*.json"))
if len(files) == 0:
print(f"no message files (*.json) found in: {messages_path}")
raise SystemExit(3)
for f in files:
print(f"Processing {f.name} ...")
data = json.loads(f.read_text(encoding="utf-8"))
# Keep $schema first if present
schema = None
if "$schema" in data:
schema = data.pop("$schema")
sorted_items = dict(sorted(data.items()))
if schema is not None:
out = {"$schema": schema}
out.update(sorted_items)
else:
out = sorted_items
f.write_text(
json.dumps(out, ensure_ascii=False, indent=4) + "\n", encoding="utf-8"
)
print(f"Processed {len(files)} files in {messages_path}")
if __name__ == "__main__":
main()