summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-16 00:21:07 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-16 00:21:07 +0200
commit3b2d03a919e56d14f8097844b97b844ddf526cd6 (patch)
tree31b222fedf6d608809eb47c45c5f431d7001d87a /packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
parent75065cc1ed5d9cd733871d478dc4f915663d795c (diff)
downloadmeshbay-3b2d03a919e56d14f8097844b97b844ddf526cd6.tar.gz
feat(hub): translate the web client into nine more languages
French, Spanish, Brazilian Portuguese, Simplified Chinese, Japanese, German, Italian, Dutch and Polish, all in the formal register. `hub`, `node` and `GEK` stay in English: they name the CLI, node.toml and the docs, and translating them would cut the interface off from everything an operator reads and types. Catalogues move out of i18n.js into locales/, one file per language, fetched with a dynamic import. A visitor downloads their language plus English as a fallback — about 36 KB rather than the ~180 KB that ten inlined catalogues would have cost everyone. i18n.js keeps only the loader, so the first render now waits for initLocale(). Three things the old code got wrong, none of them visible until there was a second language: - Resolution trimmed a tag to its base before matching, so a browser reporting pt-BR looked for a `pt` catalogue that does not exist and fell back to English. Matching is now exact first, then by base language. - Counted strings were single strings, so Polish could not express 1 plik / 2 pliki / 5 plików at all. t() selects through Intl.PluralRules; en.js gains the same treatment, which incidentally fixes "1 files". - Interpolation used String.replace, which reads `$&` in the replacement. A file named rap$&sody.mp3 rendered corrupted in its own delete dialog. Five strings were still hardcoded in app.js — the group name placeholder and the four visibility/join-policy descriptions — and are now keyed. test_locales.py holds the nine translations to the shape of en.js: same keys, a counted string stays counted everywhere, every plural entry covers each category Intl actually produces for that language, and the {placeholders} survive translation. Verified failing first, against a catalogue with a key removed, a placeholder dropped and the Polish `few` form deleted. The language menu also grew from one entry to ten, which overran a short viewport inside a dropdown that clipped instead of scrolling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/locales/it.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js404
1 files changed, 404 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
new file mode 100644
index 0000000..e3c92ef
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -0,0 +1,404 @@
+/**
+ * Italian — translated from locales/en.js.
+ *
+ * Register: formal (forma di cortesia, "Lei").
+ * `hub`, `node` and `GEK` are left in English: they name the CLI
+ * (`meshbay-node`), node.toml and the docs, which are English throughout.
+ * Grammatical gender chosen here: l'hub (m.), il node (m.), la GEK → "la chiave".
+ */
+
+export default {
+ // Nav
+ 'nav.toggle_menu': 'Mostra il menu',
+ 'nav.light_mode': 'Tema chiaro',
+ 'nav.dark_mode': 'Tema scuro',
+ 'nav.logout': 'Esci',
+ 'nav.login': 'Accedi',
+
+ // Sidebar
+ 'sidebar.my_groups': 'I miei gruppi',
+ 'sidebar.no_groups': 'Ancora nessun gruppo',
+ 'sidebar.discover': 'Esplora',
+ 'sidebar.public_groups': 'Gruppi pubblici',
+
+ // Login
+ 'login.title': 'Accedi',
+ 'login.username': 'Nome utente',
+ 'login.password': 'Password',
+ 'login.submit': 'Accedi',
+ 'login.loading': 'Accesso in corso...',
+ 'login.no_account': 'Non ha ancora un account?',
+ 'login.register_link': 'Crea un account',
+
+ // Register
+ 'register.title': 'Crea un account',
+ 'register.username': 'Nome utente',
+ 'register.email': 'Indirizzo e-mail',
+ 'register.password': 'Passphrase (almeno 12 caratteri)',
+ 'register.confirm': 'Conferma la password',
+ 'register.submit': "Crea l'account",
+ 'register.loading': "Creazione dell'account...",
+ 'register.has_account': 'Ha già un account?',
+ 'register.login_link': 'Accedi',
+ 'register.success_title': 'Account creato',
+ 'register.success_msg': 'Ora può accedere con le sue credenziali.',
+ 'register.go_login': "Vai all'accesso",
+ 'register.err_mismatch': 'Le password non coincidono',
+ 'register.err_min_len': {
+ one: 'Usi almeno {n} carattere',
+ other: 'Usi almeno {n} caratteri',
+ },
+ 'register.err_too_weak': 'Troppo facile da indovinare. La passphrase è ciò che '
+ + 'protegge le sue chiavi là dove sono conservate — qualche parola senza alcun '
+ + 'legame tra loro funziona bene.',
+ 'register.strength': 'Robustezza: circa {bits} bit. Protegge la copia delle sue '
+ + 'chiavi conservata sui node a cui aderisce, quindi vale la pena sceglierla con cura.',
+
+ // Home
+ 'home.welcome': 'Benvenuto in MeshBay',
+ 'home.no_groups': 'Non fa ancora parte di nessun gruppo.',
+ 'home.browse_prefix': 'Dia un’occhiata ai ',
+ 'home.browse_link': 'gruppi pubblici',
+ 'home.browse_suffix': " oppure chieda a un amministratore di gruppo di invitarla.",
+ 'home.my_groups': 'I miei gruppi',
+
+ // Explore
+ 'explore.title': 'Gruppi pubblici',
+ 'explore.search': 'Cerca gruppi...',
+ 'explore.loading': 'Caricamento...',
+ 'explore.empty': 'Nessun gruppo pubblico disponibile.',
+ 'explore.create_group': 'Crea un gruppo',
+ 'explore.join': 'Partecipa',
+ 'explore.member': 'Iscritto',
+
+ // Group page
+ 'group.default_name': 'Gruppo',
+ 'group.tab_files': 'File',
+ 'group.tab_chat': 'Chat',
+ 'group.tab_members': 'Membri',
+ 'group.filter': 'Filtra i file...',
+ 'group.col_name': 'Nome',
+ 'group.col_size': 'Dimensione',
+ 'group.col_type': 'Tipo',
+ 'group.col_date': 'Data',
+ 'group.empty_filter': 'Nessun file corrisponde al filtro',
+ 'group.empty_dir': 'Questa cartella è vuota',
+ 'group.download': 'Scarica',
+ 'group.play': 'Riproduci',
+ 'group.offline_title': 'Al momento nessun node è online per questo gruppo.',
+ 'group.offline_hint': 'I file compariranno quando si collegherà un node che ospita '
+ + 'questo gruppo.',
+ 'group.upload': 'Carica',
+ 'group.mkdir': 'Nuova cartella',
+ 'group.mkdir_prompt': 'Nome della nuova cartella:',
+ 'group.view': 'Visualizza',
+ 'group.delete': 'Elimina',
+ 'group.delete_confirm': 'Eliminare {name}?',
+ 'group.delete_group': 'Elimina il gruppo',
+ 'group.delete_group_confirm': 'Eliminare definitivamente «{name}» e rimuovere tutti '
+ + 'i membri? I file presenti sul node vengono conservati.',
+ 'group.err_transport': 'Modulo di trasporto non caricato',
+
+ // Status
+ 'status.idle': 'Inattivo',
+ 'status.discovering': 'Ricerca dei node...',
+ 'status.connecting': 'Connessione tramite WebRTC...',
+ 'status.fetching': "Recupero dell'indice...",
+ 'status.files': {
+ one: '{n} file',
+ other: '{n} file',
+ },
+ 'status.offline': 'Nessun node online',
+ 'status.error': 'Connessione non riuscita',
+
+ // Chat
+ 'chat.empty': 'Ancora nessun messaggio. Inizi la conversazione!',
+ 'chat.placeholder': 'Scriva un messaggio...',
+ 'chat.send': 'Invia',
+ 'chat.attach': 'Allega un file',
+
+ // Video player
+ 'video.loading': 'Caricamento di {name}...',
+ 'video.buffering': 'Buffering in corso...',
+ 'video.close': 'Chiudi (Esc)',
+ 'preview.pdf_fallback': 'Questo browser non mostrerà il PDF nella pagina. Lo scarichi '
+ + 'invece — in ogni caso è stato decifrato qui.',
+ 'group.upload_indexing': 'indicizzazione…',
+ 'video.err_transport': 'Trasporto non connesso',
+ 'video.err_mse': 'Codec non supportato per lo streaming: {codec}',
+
+ // Settings
+ 'settings.title': 'Impostazioni',
+ 'settings.coming_soon': 'Prossimamente.',
+ 'settings.downloads': 'Download',
+ 'settings.dl_auto': 'Salva automaticamente',
+ 'settings.dl_auto_hint': 'I file finiscono direttamente nella cartella che sceglie '
+ + 'qui sotto, senza alcuna finestra. Scaricare venti file ne deposita venti.',
+ 'settings.dl_ask': 'Chiedi ogni volta',
+ 'settings.dl_ask_hint': 'Una finestra «Salva con nome» per ciascun file — una per '
+ + 'file, anche quando scarica una selezione.',
+ 'settings.dl_folder': 'Cartella: {name}',
+ 'settings.dl_no_folder': 'Nessuna cartella scelta — i download finiscono dove li '
+ + 'colloca il browser',
+ 'settings.dl_choose': 'Scegli una cartella',
+ 'settings.dl_change': 'Cambia',
+ 'settings.dl_forget': 'Dimentica',
+ 'settings.dl_path_note': 'A una pagina web non si può indicare un percorso, quindi '
+ + 'qui non c’è nulla da digitare: è il browser a concedere l’accesso alla cartella '
+ + 'che indica, e MeshBay scrive soltanto al suo interno. Potrebbe esserle chiesta '
+ + 'una conferma una volta per sessione.',
+ 'settings.dl_unsupported': 'Questo browser non sa scrivere in una cartella a sua '
+ + 'scelta (manca la File System Access API), perciò i download finiscono nella sua '
+ + 'cartella di download. Chrome ed Edge permettono di sceglierne una.',
+ 'settings.profile': 'Profilo',
+ 'settings.username': 'Nome utente',
+ 'settings.node_pins': 'Identità dei node',
+ 'settings.node_pins_hint': "La chiave d'identità di ogni node viene memorizzata alla "
+ + 'prima connessione. Se cambia, la connessione viene rifiutata — cosa che ci si '
+ + 'aspetta solo quando un operatore reinstalla un node. Verifichi con lui prima di '
+ + 'cancellarla.',
+ 'settings.node_pins_count': {
+ one: '{n} fissata',
+ other: '{n} fissate',
+ },
+ 'settings.node_pins_clear': 'Cancella le identità fissate',
+ 'settings.danger': "Elimina l'account",
+ 'settings.delete_hint': "Rimuove il suo account, le sue adesioni ai gruppi e le sue "
+ + "notifiche dall'hub, e libera il suo nome utente. Non arriva a ciò che si trova "
+ + 'sui node: i file che ha caricato restano dove il loro operatore li conserva, e '
+ + 'ogni node mantiene l’identità che ha fissato per lei finché il suo operatore non '
+ + 'la rimuove. I registri di connessione sono conservati per un anno, come impone '
+ + 'la legge.',
+ 'settings.delete_account': 'Elimina il mio account',
+ 'settings.delete_confirm': 'Inserisca la sua passphrase per confermare. '
+ + "L'operazione non può essere annullata.",
+ 'settings.delete_confirm_btn': 'Elimina definitivamente',
+ 'settings.cancel': 'Annulla',
+ 'settings.appearance': 'Aspetto',
+ 'settings.theme': 'Tema',
+ 'settings.theme_light': 'Chiaro',
+ 'settings.theme_dark': 'Scuro',
+ 'settings.theme_system': 'Sistema',
+ 'settings.language': 'Lingua',
+ 'settings.about': 'Informazioni',
+ 'settings.version': 'Versione',
+ 'settings.protocol': 'Protocollo',
+ 'settings.role': 'Ruolo',
+ 'settings.groups': 'Notifiche dei gruppi',
+ 'settings.notifications': 'Notifiche',
+ 'settings.node_key': 'Collega un node',
+ 'settings.node_key_desc': 'Se gestisce un node MeshBay, incolli qui la sua chiave '
+ + 'pubblica Ed25519 per collegarlo al suo account. Trova questa chiave nel '
+ + 'pannello di amministrazione del suo node.',
+ 'settings.node_key_placeholder': 'Incolli la chiave pubblica Ed25519 del node (base64)',
+ 'settings.node_key_submit': 'Collega il node',
+ 'settings.node_key_success': 'Node collegato correttamente',
+ 'settings.node_key_current': 'Chiave del node collegato',
+
+ // Sidebar
+ 'sidebar.create_group': 'Crea un gruppo',
+ 'sidebar.settings': 'Impostazioni',
+ 'sidebar.admin': 'Amministrazione',
+
+ // User menu
+ 'usermenu.language': 'Lingua',
+ 'usermenu.settings': 'Impostazioni',
+ 'usermenu.theme_light': 'Tema chiaro',
+ 'usermenu.theme_dark': 'Tema scuro',
+ 'usermenu.logout': 'Esci',
+
+ // Admin panel
+ 'admin.title': 'Amministrazione',
+ 'admin.tab_stats': 'Statistiche',
+ 'admin.tab_users': 'Utenti',
+ 'admin.tab_groups': 'Gruppi',
+ 'admin.tab_nodes': 'Node',
+ 'admin.tab_logs': 'Registri',
+ 'admin.col_observed_ip': 'Visto da',
+ 'admin.col_hint': 'Indirizzo dichiarato',
+ 'admin.col_last_seen': 'Ultimo annuncio',
+ 'admin.node_online': 'online',
+ 'admin.node_offline': 'offline',
+ 'admin.no_nodes': 'Nessun node registrato',
+ 'admin.nodes_hint': '«Visto da» è l’indirizzo dal quale l’annuncio è effettivamente '
+ + 'arrivato, in una richiesta firmata con la chiave del node — è quello su cui '
+ + 'basarsi per rispondere a una richiesta formale. L’indirizzo dichiarato è quello '
+ + 'che il node crede essere il proprio, appreso da un server STUN e inviato a noi; '
+ + 'serve a raggiungere il node e non prova nulla.',
+ 'admin.tab_blocklist': 'Elenco dei blocchi',
+
+ // Admin stats
+ 'admin.stat_users': 'Utenti',
+ 'admin.stat_groups': 'Gruppi',
+ 'admin.stat_nodes': 'Node',
+ 'admin.stat_online': 'Node online',
+
+ // Admin users
+ 'admin.users_search': 'Cerca utenti...',
+ 'admin.col_username': 'Nome utente',
+ 'admin.col_role': 'Ruolo',
+ 'admin.col_status': 'Stato',
+ 'admin.col_created': 'Creazione',
+ 'admin.col_actions': 'Azioni',
+ 'admin.col_email': 'E-mail',
+ 'admin.col_groups': 'Gruppi',
+ 'admin.no_users': 'Nessun utente trovato',
+ 'admin.btn_suspend': 'Sospendi',
+ 'admin.btn_unsuspend': 'Riattiva',
+ 'admin.btn_details': 'Dettagli',
+ 'admin.btn_delete': 'Elimina',
+ 'admin.delete_confirm': 'Eliminare l’account «{user}»? L’operazione non può essere '
+ + 'annullata. I file che ha caricato restano sui node che li ospitano, e ogni node '
+ + 'mantiene l’identità che ha fissato finché il suo operatore non la rimuove. La '
+ + 'sospensione è l’opzione reversibile.',
+ 'admin.user_detail': "Dettagli dell'utente",
+ 'admin.btn_close': 'Chiudi',
+ 'admin.self_note': '(lei)',
+
+ // Admin groups
+ 'admin.col_name': 'Nome',
+ 'admin.col_visibility': 'Visibilità',
+ 'admin.col_members': 'Membri',
+ 'admin.no_groups': 'Nessun gruppo trovato',
+
+ // Admin logs
+ 'admin.col_time': 'Ora',
+ 'admin.col_event': 'Evento',
+ 'admin.col_user': 'Utente',
+ 'admin.col_ip': 'IP',
+ 'admin.col_detail': 'Dettaglio',
+ 'admin.filter_all': 'Tutti gli eventi',
+ 'admin.no_logs': 'Nessun registro trovato',
+ 'admin.btn_load_more': 'Carica altri',
+
+ // Admin blocklist
+ 'admin.col_hash': 'Hash',
+ 'admin.col_reason': 'Motivo',
+ 'admin.col_date': 'Data',
+ 'admin.col_added_by': 'Aggiunto da',
+ 'admin.no_blocked': 'Nessun hash bloccato',
+ 'admin.add_hash': "Aggiungi un hash all'elenco dei blocchi",
+ 'admin.hash_placeholder': 'hash blake3 (64 caratteri esadecimali)',
+ 'admin.reason_placeholder': 'Motivo',
+ 'admin.btn_block': 'Blocca',
+ 'admin.btn_unblock': 'Sblocca',
+
+ // Notifications
+ // Create group
+ 'create_group.title': 'Crea un gruppo',
+ 'create_group.name': 'Nome del gruppo',
+ 'create_group.name_placeholder': 'es. Foto di famiglia, File del progetto...',
+ 'create_group.visibility': 'Visibilità',
+ 'create_group.private': 'Privato',
+ 'create_group.private_desc': 'Solo i membri invitati vedono i file e vi accedono',
+ 'create_group.public': 'Pubblico',
+ 'create_group.public_desc': 'Chiunque può scoprire e sfogliare questo gruppo',
+ 'create_group.join_policy': 'Modalità di adesione',
+ 'create_group.invite': 'Solo su invito',
+ 'create_group.invite_desc': 'I membri devono essere invitati da un amministratore',
+ 'create_group.open': 'Aperto (chiunque può partecipare)',
+ 'create_group.open_desc': 'Chiunque può partecipare senza approvazione',
+ 'create_group.description': 'Descrizione',
+ 'create_group.description_hint': 'Di cosa si occupa questo gruppo? (facoltativo)',
+ 'create_group.submit': 'Crea',
+ 'create_group.hint': 'Un gruppo ha bisogno di un node che ospiti i file. Può creare '
+ + 'il gruppo ora e collegare un node più avanti.',
+ 'create_group.creating': 'Creazione in corso...',
+
+ // Members
+ 'members.col_role': 'Ruolo',
+ 'members.group_role': 'Ruolo nel gruppo',
+ 'members.admin': 'Amministratore',
+ 'members.group_admin': 'Amministratore del gruppo',
+ 'members.owner': 'Proprietario',
+ 'members.member': 'Membro',
+ 'members.invite_title': 'Invita un membro',
+ 'members.username_placeholder': 'Nome utente',
+ 'members.invite_btn': 'Invita',
+ 'members.invite_needs_pairing': 'Gli inviti sono firmati con la chiave che questo '
+ + 'node ha fissato per il suo browser: lo associ qui sotto prima di invitare '
+ + 'chiunque.',
+ 'members.invite_ask_operator': 'Solo l’operatore del node che ospita questo gruppo '
+ + 'può invitare, da un browser associato a esso.',
+ 'members.remove': 'Rimuovi',
+ 'members.remove_confirm': 'Rimuovere {user} da questo gruppo? Il suo account, gli '
+ + 'altri gruppi e i file che ha caricato non vengono toccati.',
+ 'members.remove_hint': 'Rimuovere qualcuno gli toglie l’adesione e impedisce al node '
+ + 'di consegnargli la chiave del gruppo. Non elimina il suo account e non recupera '
+ + 'la chiave che già possiede — la ruoti sul node (meshbay-node gek-init) se la '
+ + 'cosa è importante.',
+ 'members.pair_title': 'Associ questo browser al suo node',
+ 'members.pair_hint': 'Il suo node accetta le azioni da operatore — inviti, '
+ + 'eliminazione di file — solo da un browser a cui è stato associato. Esegua '
+ + '`meshbay-node operator pair` sul node e digiti qui il codice. Il codice non '
+ + 'passa mai per l’hub.',
+ 'members.pair_btn': 'Associa',
+ 'members.pair_success': 'Questo browser è ora associato al node.',
+ 'members.invite_code_ready': 'Codice di invito per {user} — glielo mandi con il '
+ + 'mezzo che usate di solito. Funziona una volta sola e non passa mai per l’hub.',
+ 'members.invite_code_hint': 'Lo inserirà la prima volta che aprirà questo gruppo. '
+ + 'Non è necessario che lei sia online in quel momento.',
+ 'transfers.title': 'Trasferimenti',
+ 'transfers.clear': 'Rimuovi i completati',
+ 'transfers.cancel': 'Annulla',
+ 'transfers.open': 'Apri',
+ 'transfers.done': 'Completato',
+ 'transfers.cancelled': 'Annullato',
+ 'transfers.failed': 'Non riuscito',
+ 'group.select': 'Seleziona',
+ 'group.select_done': 'Fine',
+ 'group.actions': 'Azioni ({n})',
+ 'group.download_n': 'Scarica ({n})',
+ 'group.download_zip_n': 'Scarica le cartelle in zip ({n})',
+ 'group.delete_n': 'Elimina ({n})',
+ 'group.delete_n_confirm': {
+ one: 'Eliminare {n} elemento? {names}',
+ other: 'Eliminare {n} elementi? {names}',
+ },
+ 'group.download_zip': {
+ one: 'Scarica in zip ({n} file)',
+ other: 'Scarica in zip ({n} file)',
+ },
+ 'group.zip_empty': 'Quella cartella non contiene nulla da scaricare.',
+ 'group.zip_no_stream': 'Questo browser non sa scrivere un download direttamente su '
+ + 'disco, quindi {name} ({size}) deve prima essere costruito in memoria. Con un '
+ + 'archivio grande l’operazione può fallire. Continuare?',
+ 'group.rmdir': 'Elimina la cartella',
+ 'group.rmdir_confirm': 'Eliminare la cartella «{name}»? Deve essere vuota.',
+ 'group.rmdir_not_empty': 'Quella cartella non è vuota. Elimini prima ciò che '
+ + 'contiene — qui nulla cancella file che lei non vede.',
+ 'group.desc_edit': 'Modifica la descrizione',
+ 'group.desc_add': 'Aggiungi una descrizione',
+ 'group.desc_save': 'Salva',
+ 'group.desc_cancel': 'Annulla',
+ 'group.desc_placeholder': 'A cosa serve questo gruppo — i membri lo vedono sulla '
+ + 'loro pagina iniziale.',
+ 'group.join_code_title': 'Questo node deve riconoscerla',
+ 'group.join_code_hint': 'Chieda il codice monouso a chi l’ha invitata e lo inserisca '
+ + 'qui. Dopodiché questo browser è riconosciuto e non le verrà più chiesto.',
+ 'group.join_code_btn': 'Partecipa',
+
+ 'notif.purge': 'Cancella tutto',
+ 'notif.title': 'Notifiche',
+ 'notif.empty': 'Nessuna notifica',
+ 'notif.mark_all_read': 'Segna tutto come letto',
+
+ // Sidebar
+ 'sidebar.search': 'Cerca file',
+
+ // Status
+
+ // Search
+ 'search.title': 'Cerca file',
+ 'search.placeholder': 'Cerca in tutti i gruppi...',
+ 'search.no_results': 'Nessun file corrisponde alla sua ricerca.',
+ 'search.synced': 'sincronizzato {ago}',
+ 'search.cache_note': 'La ricerca si basa su com’era ogni gruppo l’ultima volta che '
+ + 'lo ha aperto. Apra un gruppo per aggiornarne qui i file.',
+ 'search.col_group': 'Gruppo',
+ 'search.result_count': {
+ one: '{n} file trovato',
+ other: '{n} file trovati',
+ },
+ 'search.hint': 'Cerca i nomi dei file in tutti gli indici di gruppo in cache.',
+};