feat(i18n): add ES, FR, PT, DE languages and fix live switching

Add Spanish, French, Portuguese, German translations to all UI strings.

Fix language switching in Avalonia compiled bindings: instead of firing
PropertyChanged("Item[]") on a singleton (which Avalonia ignores when the
object reference is unchanged), Loc.SwitchTo() now creates a new Loc
instance for the selected language and replaces both Loc.Instance and the
ViewModel's _loc field. OnPropertyChanged("Loc") then forces Avalonia to
re-evaluate all {Binding Loc[key]} bindings with the new instance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 18:41:31 +02:00
parent 75583f7a69
commit 71a604c8a5
3 changed files with 205 additions and 120 deletions
+178 -117
View File
@@ -1,166 +1,227 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
namespace PalladiumWallet.App.Localization; namespace PalladiumWallet.App.Localization;
/// <summary> /// <summary>
/// Localizzazione UI (blueprint §14): dizionario chiave → [it, en], con /// Localizzazione UI: dizionario chiave → traduzioni per lingua, con
/// indicizzatore bindabile da XAML ({Binding Loc[chiave]}). Al cambio lingua /// indicizzatore bindabile da XAML ({Binding Loc[chiave]}). Al cambio lingua
/// notifica "Item[]" e tutte le binding si aggiornano. /// il ViewModel sostituisce l'istanza così Avalonia rivaluta tutte le binding.
/// </summary> /// </summary>
public sealed class Loc : INotifyPropertyChanged public sealed class Loc
{ {
public static Loc Instance { get; } = new(); public static Loc Instance { get; private set; } = new();
public static readonly string[] Languages = ["it", "en"]; public static readonly string[] Languages = ["it", "en", "es", "fr", "pt", "de"];
public static readonly string[] LanguageNames = ["Italiano", "English"]; public static readonly string[] LanguageNames = ["Italiano", "English", "Español", "Français", "Português", "Deutsch"];
public string Language { get; private set; } = "it"; public string Language { get; private set; } = "en";
public event PropertyChangedEventHandler? PropertyChanged; private Loc() { }
private Loc(string language) { Language = language; }
public void SetLanguage(string language) /// <summary>
/// Crea una nuova istanza con la lingua specificata e aggiorna il singleton
/// usato da <see cref="Tr"/>. Il ViewModel assegna questa istanza alla
/// propria property Loc così Avalonia vede un riferimento diverso e
/// rivaluta tutte le binding {Binding Loc[chiave]}.
/// </summary>
internal static Loc SwitchTo(string language)
{ {
if (Language == language || System.Array.IndexOf(Languages, language) < 0) if (System.Array.IndexOf(Languages, language) < 0) language = "it";
return; var loc = new Loc(language);
Language = language; Instance = loc;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]")); return loc;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Language)));
} }
public string this[string key] => public string this[string key] =>
Strings.TryGetValue(key, out var values) Strings.TryGetValue(key, out var values)
? values[Language == "en" ? 1 : 0] ? values[System.Math.Max(0, System.Array.IndexOf(Languages, Language))]
: key; : key;
public static string Tr(string key) => Instance[key]; public static string Tr(string key) => Instance[key];
private static readonly Dictionary<string, string[]> Strings = new() private static readonly Dictionary<string, string[]> Strings = new()
{ {
// Menu // Menu it en es fr pt de
["menu.file"] = ["_File", "_File"], ["menu.file"] = ["_File", "_File", "_Archivo", "_Fichier", "_Arquivo", "_Datei"],
["menu.file.new"] = ["Nuovo / ripristina wallet…", "New / restore wallet…"], ["menu.file.new"] = ["Nuovo / ripristina wallet…", "New / restore wallet…", "Nuevo / restaurar wallet…", "Nouveau / restaurer le wallet…", "Novo / restaurar carteira…", "Neu / Wallet wiederherstellen…"],
["menu.file.open"] = ["Apri wallet da file…", "Open wallet from file…"], ["menu.file.open"] = ["Apri wallet da file…", "Open wallet from file…", "Abrir wallet desde archivo…", "Ouvrir le wallet depuis un fichier…", "Abrir carteira de arquivo…", "Wallet aus Datei öffnen…"],
["menu.file.close"] = ["Chiudi wallet", "Close wallet"], ["menu.file.close"] = ["Chiudi wallet", "Close wallet", "Cerrar wallet", "Fermer le wallet", "Fechar carteira", "Wallet schließen"],
["menu.net"] = ["_Rete", "_Network"], ["menu.net"] = ["_Rete", "_Network", "_Red", "_Réseau", "_Rede", "_Netzwerk"],
["menu.net.discover"] = ["Cerca altri server (peer)", "Discover servers (peers)"], ["menu.net.discover"] = ["Cerca altri server (peer)", "Discover servers (peers)", "Buscar otros servidores (peers)", "Rechercher d'autres serveurs (pairs)", "Procurar outros servidores (peers)", "Weitere Server suchen (Peers)"],
["menu.net.resetcerts"] = ["Reset certificati SSL", "Reset SSL certificates"], ["menu.net.resetcerts"] = ["Reset certificati SSL", "Reset SSL certificates", "Restablecer certificados SSL", "Réinitialiser les certificats SSL", "Redefinir certificados SSL", "SSL-Zertifikate zurücksetzen"],
["menu.settings"] = ["_Impostazioni", "_Settings"], ["menu.settings"] = ["_Impostazioni", "_Settings", "_Configuración", "_Paramètres", "_Configurações", "_Einstellungen"],
["settings.unit.short"] = ["Unità", "Unit"], ["settings.unit.short"] = ["Unità", "Unit", "Unidad", "Unité", "Unidade", "Einheit"],
// Wizard // Wizard
["wiz.net"] = ["Rete:", "Network:"], ["wiz.net"] = ["Rete:", "Network:", "Red:", "Réseau :", "Rede:", "Netzwerk:"],
["wiz.open.btn"] = ["Apri il wallet esistente", "Open existing wallet"], ["wiz.open.btn"] = ["Apri il wallet esistente", "Open existing wallet", "Abrir wallet existente", "Ouvrir le wallet existant", "Abrir carteira existente", "Vorhandenes Wallet öffnen"],
["wiz.new.btn"] = ["Crea un nuovo wallet", "Create a new wallet"], ["wiz.new.btn"] = ["Crea un nuovo wallet", "Create a new wallet", "Crear nuevo wallet", "Créer un nouveau wallet", "Criar nova carteira", "Neues Wallet erstellen"],
["wiz.restore.btn"] = ["Ripristina da seed", "Restore from seed"], ["wiz.restore.btn"] = ["Ripristina da seed", "Restore from seed", "Restaurar desde semilla", "Restaurer depuis la graine", "Restaurar da semente", "Aus Seed wiederherstellen"],
["wiz.open.title"] = ["Apri il wallet", "Open the wallet"], ["wiz.open.title"] = ["Apri il wallet", "Open the wallet", "Abrir el wallet", "Ouvrir le wallet", "Abrir a carteira", "Wallet öffnen"],
["wiz.open.placeholder"] = ["Password del file (vuoto se non impostata)", "File password (empty if not set)"], ["wiz.open.placeholder"] = ["Password del file (vuoto se non impostata)", "File password (empty if not set)", "Contraseña del archivo (vacío si no establecida)", "Mot de passe du fichier (vide si non défini)", "Senha do arquivo (vazio se não definida)", "Dateipasswort (leer lassen, wenn nicht gesetzt)"],
["wiz.open.ok"] = ["Apri", "Open"], ["wiz.open.ok"] = ["Apri", "Open", "Abrir", "Ouvrir", "Abrir", "Öffnen"],
["wiz.seed.title"] = ["Il tuo seed (12 parole)", "Your seed (12 words)"], ["wiz.seed.title"] = ["Il tuo seed (12 parole)", "Your seed (12 words)", "Tu semilla (12 palabras)", "Votre graine (12 mots)", "Sua semente (12 palavras)", "Ihr Seed (12 Wörter)"],
["wiz.seed.warning"] = [ ["wiz.seed.warning"] = [
"Scrivi le parole su carta, nell'ordine. Chi le possiede controlla i fondi; se le perdi, i fondi sono irrecuperabili.", "Scrivi le parole su carta, nell'ordine. Chi le possiede controlla i fondi; se le perdi, i fondi sono irrecuperabili.",
"Write the words on paper, in order. Whoever holds them controls the funds; if you lose them, funds are unrecoverable."], "Write the words on paper, in order. Whoever holds them controls the funds; if you lose them, funds are unrecoverable.",
["wiz.seed.next"] = ["Le ho scritte — Avanti", "I wrote them down — Next"], "Escribe las palabras en papel, en orden. Quien las posea controla los fondos; si las pierdes, los fondos son irrecuperables.",
["wiz.confirm.title"] = ["Conferma il seed", "Confirm the seed"], "Écrivez les mots sur papier, dans l'ordre. Celui qui les possède contrôle les fonds ; si vous les perdez, les fonds sont irrécupérables.",
["wiz.confirm.placeholder"] = ["Reinserisci le 12 parole separate da spazi", "Re-enter the 12 words separated by spaces"], "Escreva as palavras no papel, em ordem. Quem as possuir controla os fundos; se as perder, os fundos são irrecuperáveis.",
["wiz.words.title"] = ["Ripristina da seed", "Restore from seed"], "Schreiben Sie die Wörter auf Papier, in der richtigen Reihenfolge. Wer sie besitzt, kontrolliert die Gelder; wenn Sie sie verlieren, sind die Gelder unwiederbringlich verloren."],
["wiz.words.placeholder"] = ["Mnemonica BIP39 (12 o 24 parole separate da spazi)", "BIP39 mnemonic (12 or 24 words separated by spaces)"], ["wiz.seed.next"] = ["Le ho scritte — Avanti", "I wrote them down — Next", "Las anoté — Siguiente", "Je les ai notés — Suivant", "Eu as anotei — Próximo", "Ich habe sie notiert — Weiter"],
["wiz.passphrase.title"] = ["Passphrase opzionale", "Optional passphrase"], ["wiz.confirm.title"] = ["Conferma il seed", "Confirm the seed", "Confirmar la semilla", "Confirmer la graine", "Confirmar a semente", "Seed bestätigen"],
["wiz.passphrase.placeholder"] = ["Lascia vuoto per non usarla", "Leave empty to skip"], ["wiz.confirm.placeholder"] = ["Reinserisci le 12 parole separate da spazi", "Re-enter the 12 words separated by spaces", "Reingresa las 12 palabras separadas por espacios", "Ressaisissez les 12 mots séparés par des espaces", "Reinsira as 12 palavras separadas por espaços", "12 Wörter durch Leerzeichen getrennt erneut eingeben"],
["wiz.password.title"] = ["Password del file wallet", "Wallet file password"], ["wiz.words.title"] = ["Ripristina da seed", "Restore from seed", "Restaurar desde semilla", "Restaurer depuis la graine", "Restaurar da semente", "Aus Seed wiederherstellen"],
["wiz.password.placeholder"] = ["Consigliata (vuoto = file in chiaro su disco)", "Recommended (empty = plaintext file on disk)"], ["wiz.words.placeholder"] = ["Mnemonica BIP39 (12 o 24 parole separate da spazi)", "BIP39 mnemonic (12 or 24 words separated by spaces)", "Mnemónico BIP39 (12 o 24 palabras separadas por espacios)", "Mnémonique BIP39 (12 ou 24 mots séparés par des espaces)", "Mnemônico BIP39 (12 ou 24 palavras separadas por espaços)", "BIP39-Mnemonic (12 oder 24 durch Leerzeichen getrennte Wörter)"],
["wiz.password.create"] = ["Crea il wallet", "Create wallet"], ["wiz.passphrase.title"] = ["Passphrase opzionale", "Optional passphrase", "Frase de contraseña opcional", "Phrase de passe optionnelle", "Frase-senha opcional", "Optionale Passphrase"],
["wiz.back"] = ["Indietro", "Back"], ["wiz.passphrase.placeholder"] = ["Lascia vuoto per non usarla", "Leave empty to skip", "Deja vacío para omitir", "Laisser vide pour ignorer", "Deixe vazio para ignorar", "Leer lassen zum Überspringen"],
["wiz.next"] = ["Avanti", "Next"], ["wiz.password.title"] = ["Password del file wallet", "Wallet file password", "Contraseña del archivo wallet", "Mot de passe du fichier wallet", "Senha do arquivo da carteira", "Wallet-Dateipasswort"],
["wiz.password.placeholder"] = ["Consigliata (vuoto = file in chiaro su disco)", "Recommended (empty = plaintext file on disk)", "Recomendada (vacío = archivo en texto claro en disco)", "Recommandé (vide = fichier en texte clair sur disque)", "Recomendada (vazio = arquivo em texto simples no disco)", "Empfohlen (leer = Klartextdatei auf Disk)"],
["wiz.password.create"] = ["Crea il wallet", "Create wallet", "Crear wallet", "Créer le wallet", "Criar carteira", "Wallet erstellen"],
["wiz.back"] = ["Indietro", "Back", "Atrás", "Retour", "Voltar", "Zurück"],
["wiz.next"] = ["Avanti", "Next", "Siguiente", "Suivant", "Próximo", "Weiter"],
// Pannello wallet // Pannello wallet
["wallet.close"] = ["Chiudi wallet", "Close wallet"], ["wallet.close"] = ["Chiudi wallet", "Close wallet", "Cerrar wallet", "Fermer le wallet", "Fechar carteira", "Wallet schließen"],
["wallet.server"] = ["Server:", "Server:"], ["wallet.server"] = ["Server:", "Server:", "Servidor:", "Serveur :", "Servidor:", "Server:"],
["wallet.connect"] = ["Connetti e sincronizza", "Connect and sync"], ["wallet.connect"] = ["Connetti e sincronizza", "Connect and sync", "Conectar y sincronizar", "Connecter et synchroniser", "Conectar e sincronizar", "Verbinden und synchronisieren"],
["wallet.manual"] = ["oppure host:porta manuale", "or manual host:port"], ["wallet.manual"] = ["oppure host:porta manuale", "or manual host:port", "o host:puerto manual", "ou hôte:port manuel", "ou host:porta manual", "oder manuell host:port"],
["wallet.discover"] = ["Cerca altri server", "Discover servers"], ["wallet.discover"] = ["Cerca altri server", "Discover servers", "Buscar otros servidores", "Rechercher des serveurs", "Procurar servidores", "Server suchen"],
["wallet.resetcert"] = ["Reset cert.", "Reset certs"], ["wallet.resetcert"] = ["Reset cert.", "Reset certs", "Restablecer cert.", "Réinit. cert.", "Redefinir cert.", "Zert. zurücksetzen"],
["tab.receive"] = ["Ricevi", "Receive"], ["tab.receive"] = ["Ricevi", "Receive", "Recibir", "Recevoir", "Receber", "Empfangen"],
["tab.history"] = ["Storico", "History"], ["tab.history"] = ["Storico", "History", "Historial", "Historique", "Histórico", "Verlauf"],
["tab.addresses"] = ["Indirizzi", "Addresses"], ["tab.addresses"] = ["Indirizzi", "Addresses", "Direcciones", "Adresses", "Endereços", "Adressen"],
["tab.send"] = ["Invia", "Send"], ["tab.send"] = ["Invia", "Send", "Enviar", "Envoyer", "Enviar", "Senden"],
["receive.next"] = ["Prossimo indirizzo non usato:", "Next unused address:"], ["receive.next"] = ["Prossimo indirizzo non usato:", "Next unused address:", "Próxima dirección no usada:", "Prochaine adresse non utilisée :", "Próximo endereço não usado:", "Nächste ungenutzte Adresse:"],
["receive.hint"] = [ ["receive.hint"] = [
"Ogni pagamento ricevuto qui comparirà nello storico alla prossima sincronizzazione.", "Ogni pagamento ricevuto qui comparirà nello storico alla prossima sincronizzazione.",
"Payments received here will appear in the history at the next synchronization."], "Payments received here will appear in the history at the next synchronization.",
["addr.type"] = ["Tipo", "Type"], "Los pagos recibidos aquí aparecerán en el historial en la próxima sincronización.",
["addr.index"] = ["Indice", "Index"], "Les paiements reçus ici apparaîtront dans l'historique à la prochaine synchronisation.",
["addr.address"] = ["Indirizzo", "Address"], "Os pagamentos recebidos aqui aparecerão no histórico na próxima sincronização.",
["addr.balance"] = ["Saldo", "Balance"], "Hier empfangene Zahlungen erscheinen beim nächsten Synchronisieren im Verlauf."],
["addr.receive"] = ["ricezione", "receive"], ["addr.type"] = ["Tipo", "Type", "Tipo", "Type", "Tipo", "Typ"],
["addr.change"] = ["change", "change"], ["addr.index"] = ["Indice", "Index", "Índice", "Index", "Índice", "Index"],
["send.to"] = ["Indirizzo destinatario", "Recipient address"], ["addr.address"] = ["Indirizzo", "Address", "Dirección", "Adresse", "Endereço", "Adresse"],
["send.amount"] = ["Importo", "Amount"], ["addr.balance"] = ["Saldo", "Balance", "Saldo", "Solde", "Saldo", "Saldo"],
["send.all"] = ["Invia tutto", "Send all"], ["addr.receive"] = ["ricezione", "receive", "recepción", "réception", "recebimento", "Empfang"],
["send.feerate"] = ["fee sat/vB:", "fee sat/vB:"], ["addr.change"] = ["change", "change", "cambio", "monnaie", "troco", "Wechselgeld"],
["send.prepare"] = ["Prepara transazione", "Prepare transaction"], ["send.to"] = ["Indirizzo destinatario", "Recipient address", "Dirección destinataria", "Adresse du destinataire", "Endereço do destinatário", "Empfängeradresse"],
["send.confirm"] = ["CONFERMA E TRASMETTI", "CONFIRM AND BROADCAST"], ["send.amount"] = ["Importo", "Amount", "Importe", "Montant", "Valor", "Betrag"],
["send.all"] = ["Invia tutto", "Send all", "Enviar todo", "Tout envoyer", "Enviar tudo", "Alles senden"],
["send.feerate"] = ["fee sat/vB:", "fee sat/vB:", "tarifa sat/vB:", "frais sat/vB :", "taxa sat/vB:", "Gebühr sat/vB:"],
["send.prepare"] = ["Prepara transazione", "Prepare transaction", "Preparar transacción", "Préparer la transaction", "Preparar transação", "Transaktion vorbereiten"],
["send.confirm"] = ["CONFERMA E TRASMETTI", "CONFIRM AND BROADCAST", "CONFIRMAR Y TRANSMITIR", "CONFIRMER ET DIFFUSER", "CONFIRMAR E TRANSMITIR", "BESTÄTIGEN UND SENDEN"],
// Stato connessione // Stato connessione
["conn.none"] = ["non connesso", "not connected"], ["conn.none"] = ["non connesso", "not connected", "no conectado", "non connecté", "não conectado", "nicht verbunden"],
["conn.disconnected"] = ["disconnesso", "disconnected"], ["conn.disconnected"] = ["disconnesso", "disconnected", "desconectado", "déconnecté", "desconectado", "getrennt"],
["conn.reconnecting"] = ["riconnessione…", "reconnecting…"], ["conn.reconnecting"] = ["riconnessione…", "reconnecting…", "reconectando…", "reconnexion…", "reconectando…", "Verbindung wird wiederhergestellt…"],
["conn.error"] = ["errore di connessione", "connection error"], ["conn.error"] = ["errore di connessione", "connection error", "error de conexión", "erreur de connexion", "erro de conexão", "Verbindungsfehler"],
["conn.certchanged"] = ["certificato cambiato", "certificate changed"], ["conn.certchanged"] = ["certificato cambiato", "certificate changed", "certificado cambiado", "certificat modifié", "certificado alterado", "Zertifikat geändert"],
["conn.connectedto"] = ["connesso a", "connected to"], ["conn.connectedto"] = ["connesso a", "connected to", "conectado a", "connecté à", "conectado a", "verbunden mit"],
["conn.connectingto"] = ["connessione a", "connecting to"], ["conn.connectingto"] = ["connessione a", "connecting to", "conectando a", "connexion à", "conectando a", "Verbindung zu"],
// Messaggi di stato principali // Messaggi di stato principali
["msg.welcome.existing"] = [ ["msg.welcome.existing"] = [
"Trovato un wallet esistente su questa rete: aprilo, oppure creane un altro.", "Trovato un wallet esistente su questa rete: aprilo, oppure creane un altro.",
"Found an existing wallet on this network: open it, or create another one."], "Found an existing wallet on this network: open it, or create another one.",
["msg.welcome.new"] = [ "Se encontró un wallet existente en esta red: ábrelo o crea otro.",
"Un wallet existant a été trouvé sur ce réseau : ouvrez-le ou créez-en un autre.",
"Uma carteira existente foi encontrada nesta rede: abra-a ou crie outra.",
"Ein vorhandenes Wallet wurde in diesem Netzwerk gefunden: öffnen Sie es oder erstellen Sie ein neues."],
["msg.welcome.new"] = [
"Benvenuto: crea un nuovo wallet o ripristina da seed.", "Benvenuto: crea un nuovo wallet o ripristina da seed.",
"Welcome: create a new wallet or restore from seed."], "Welcome: create a new wallet or restore from seed.",
["msg.open.password"] = [ "Bienvenido: crea un nuevo wallet o restaura desde semilla.",
"Bienvenue : créez un nouveau wallet ou restaurez depuis une graine.",
"Bem-vindo: crie uma nova carteira ou restaure da semente.",
"Willkommen: erstellen Sie ein neues Wallet oder stellen Sie es aus einem Seed wieder her."],
["msg.open.password"] = [
"Inserisci la password del file (lascia vuoto se non impostata).", "Inserisci la password del file (lascia vuoto se non impostata).",
"Enter the file password (leave empty if not set)."], "Enter the file password (leave empty if not set).",
["msg.seed.write"] = [ "Ingresa la contraseña del archivo (deja vacío si no establecida).",
"Entrez le mot de passe du fichier (laisser vide si non défini).",
"Digite a senha do arquivo (deixe vazio se não definida).",
"Geben Sie das Dateipasswort ein (leer lassen, wenn nicht gesetzt)."],
["msg.seed.write"] = [
"Scrivi le 12 parole SU CARTA, nell'ordine. Sono l'unico backup del wallet.", "Scrivi le 12 parole SU CARTA, nell'ordine. Sono l'unico backup del wallet.",
"Write the 12 words ON PAPER, in order. They are the only backup of the wallet."], "Write the 12 words ON PAPER, in order. They are the only backup of the wallet.",
["msg.seed.retype"] = [ "Escribe las 12 palabras EN PAPEL, en orden. Son la única copia de seguridad del wallet.",
"Écrivez les 12 mots SUR PAPIER, dans l'ordre. C'est la seule sauvegarde du wallet.",
"Escreva as 12 palavras NO PAPEL, em ordem. São o único backup da carteira.",
"Schreiben Sie die 12 Wörter AUF PAPIER, in der richtigen Reihenfolge. Sie sind die einzige Sicherung des Wallets."],
["msg.seed.retype"] = [
"Reinserisci le 12 parole per confermare di averle scritte.", "Reinserisci le 12 parole per confermare di averle scritte.",
"Re-enter the 12 words to confirm you wrote them down."], "Re-enter the 12 words to confirm you wrote them down.",
["msg.seed.mismatch"] = [ "Reingresa las 12 palabras para confirmar que las has anotado.",
"Ressaisissez les 12 mots pour confirmer que vous les avez notés.",
"Reinsira as 12 palavras para confirmar que as anotou.",
"Geben Sie die 12 Wörter erneut ein, um zu bestätigen, dass Sie sie notiert haben."],
["msg.seed.mismatch"] = [
"Le parole non corrispondono: ricontrolla quello che hai scritto su carta.", "Le parole non corrispondono: ricontrolla quello che hai scritto su carta.",
"The words do not match: check what you wrote on paper."], "The words do not match: check what you wrote on paper.",
["msg.words.enter"] = [ "Las palabras no coinciden: revisa lo que escribiste en papel.",
"Les mots ne correspondent pas : vérifiez ce que vous avez écrit sur papier.",
"As palavras não correspondem: verifique o que escreveu no papel.",
"Die Wörter stimmen nicht überein: überprüfen Sie, was Sie auf Papier geschrieben haben."],
["msg.words.enter"] = [
"Inserisci la mnemonica BIP39 (12 o 24 parole separate da spazi).", "Inserisci la mnemonica BIP39 (12 o 24 parole separate da spazi).",
"Enter the BIP39 mnemonic (12 or 24 words separated by spaces)."], "Enter the BIP39 mnemonic (12 or 24 words separated by spaces).",
["msg.words.invalid"] = [ "Ingresa el mnemónico BIP39 (12 o 24 palabras separadas por espacios).",
"Entrez le mnémonique BIP39 (12 ou 24 mots séparés par des espaces).",
"Insira o mnemônico BIP39 (12 ou 24 palavras separadas por espaços).",
"Geben Sie die BIP39-Mnemonic ein (12 oder 24 durch Leerzeichen getrennte Wörter)."],
["msg.words.invalid"] = [
"Mnemonica non valida (parole o checksum errati): ricontrolla.", "Mnemonica non valida (parole o checksum errati): ricontrolla.",
"Invalid mnemonic (wrong words or checksum): check again."], "Invalid mnemonic (wrong words or checksum): check again.",
["msg.passphrase.info"] = [ "Mnemónico no válido (palabras o checksum incorrectos): verifica de nuevo.",
"Mnémonique invalide (mots ou checksum incorrects) : vérifiez à nouveau.",
"Mnemônico inválido (palavras ou checksum incorretos): verifique novamente.",
"Ungültige Mnemonic (falsche Wörter oder Prüfsumme): bitte erneut prüfen."],
["msg.passphrase.info"] = [
"Passphrase BIP39 opzionale: cambia completamente il wallet. Se la usi, annotala A PARTE dal seed; se la perdi i fondi sono irrecuperabili. Lascia vuoto per non usarla.", "Passphrase BIP39 opzionale: cambia completamente il wallet. Se la usi, annotala A PARTE dal seed; se la perdi i fondi sono irrecuperabili. Lascia vuoto per non usarla.",
"Optional BIP39 passphrase: it derives a completely different wallet. If you use it, note it SEPARATELY from the seed; if lost, funds are unrecoverable. Leave empty to skip."], "Optional BIP39 passphrase: it derives a completely different wallet. If you use it, note it SEPARATELY from the seed; if lost, funds are unrecoverable. Leave empty to skip.",
["msg.password.info"] = [ "Frase de contraseña BIP39 opcional: deriva un wallet completamente diferente. Si la usas, anótala SEPARADA de la semilla; si la pierdes, los fondos son irrecuperables. Deja vacío para omitir.",
"Phrase de passe BIP39 optionnelle : elle dérive un wallet complètement différent. Si vous l'utilisez, notez-la SÉPARÉMENT de la graine ; si vous la perdez, les fonds sont irrécupérables. Laisser vide pour ignorer.",
"Frase-senha BIP39 opcional: deriva uma carteira completamente diferente. Se a usar, anote-a SEPARADAMENTE da semente; se a perder, os fundos são irrecuperáveis. Deixe vazio para ignorar.",
"Optionale BIP39-Passphrase: leitet ein völlig anderes Wallet ab. Falls verwendet, GETRENNT vom Seed notieren; falls verloren, sind die Gelder unwiederbringlich. Leer lassen zum Überspringen."],
["msg.password.info"] = [
"Password di cifratura del file wallet su disco (consigliata). Non sostituisce il seed: serve solo a proteggere il file.", "Password di cifratura del file wallet su disco (consigliata). Non sostituisce il seed: serve solo a proteggere il file.",
"Encryption password for the wallet file on disk (recommended). It does not replace the seed: it only protects the file."], "Encryption password for the wallet file on disk (recommended). It does not replace the seed: it only protects the file.",
["msg.wrongpassword"] = ["Password errata.", "Wrong password."], "Contraseña de cifrado para el archivo wallet en disco (recomendada). No reemplaza la semilla: solo protege el archivo.",
["msg.opened"] = ["Wallet aperto: connessione al server…", "Wallet opened: connecting to server"], "Mot de passe de chiffrement pour le fichier wallet sur disque (recommandé). Ne remplace pas la graine : protège uniquement le fichier.",
["msg.synced"] = ["Sincronizzato", "Synchronized"], "Senha de criptografia para o arquivo da carteira no disco (recomendada). Não substitui a semente: apenas protege o arquivo.",
["msg.synced.detail"] = [ "Verschlüsselungspasswort für die Wallet-Datei auf der Festplatte (empfohlen). Ersetzt nicht den Seed: schützt nur die Datei."],
["msg.wrongpassword"] = ["Password errata.", "Wrong password.", "Contraseña incorrecta.", "Mot de passe incorrect.", "Senha incorreta.", "Falsches Passwort."],
["msg.opened"] = ["Wallet aperto: connessione al server…", "Wallet opened: connecting to server…", "Wallet abierto: conectando al servidor…", "Wallet ouvert : connexion au serveur…", "Carteira aberta: conectando ao servidor…", "Wallet geöffnet: Verbindung zum Server…"],
["msg.synced"] = ["Sincronizzato", "Synchronized", "Sincronizado", "Synchronisé", "Sincronizado", "Synchronisiert"],
["msg.synced.detail"] = [
"transazioni verificate SPV. Aggiornamento in tempo reale attivo.", "transazioni verificate SPV. Aggiornamento in tempo reale attivo.",
"SPV-verified transactions. Real-time updates active."], "SPV-verified transactions. Real-time updates active.",
["msg.height"] = ["altezza", "height"], "transacciones verificadas SPV. Actualizaciones en tiempo real activas.",
["msg.pending"] = ["in attesa di conferma", "pending confirmation"], "transactions vérifiées SPV. Mises à jour en temps réel actives.",
["msg.notspendable"] = ["non ancora spendibile", "not yet spendable"], "transações verificadas SPV. Atualizações em tempo real ativas.",
["msg.settings.saved"] = ["Impostazioni salvate.", "Settings saved."], "SPV-verifizierte Transaktionen. Echtzeit-Updates aktiv."],
["msg.certreset"] = [ ["msg.height"] = ["altezza", "height", "altura", "hauteur", "altura", "Höhe"],
["msg.pending"] = ["in attesa di conferma", "pending confirmation", "pendiente de confirmación", "en attente de confirmation", "aguardando confirmação", "ausstehende Bestätigung"],
["msg.notspendable"] = ["non ancora spendibile", "not yet spendable", "aún no gastable", "pas encore dépensable", "ainda não gastável", "noch nicht verwendbar"],
["msg.settings.saved"] = ["Impostazioni salvate.", "Settings saved.", "Configuración guardada.", "Paramètres enregistrés.", "Configurações salvas.", "Einstellungen gespeichert."],
["msg.certreset"] = [
"Certificati SSL azzerati: riprova la connessione.", "Certificati SSL azzerati: riprova la connessione.",
"SSL certificates cleared: retry the connection."], "SSL certificates cleared: retry the connection.",
["msg.error"] = ["Errore", "Error"], "Certificados SSL restablecidos: reintenta la conexión.",
"Certificats SSL réinitialisés : réessayez la connexion.",
"Certificados SSL redefinidos: tente novamente a conexão.",
"SSL-Zertifikate zurückgesetzt: Verbindung erneut versuchen."],
["msg.error"] = ["Errore", "Error", "Error", "Erreur", "Erro", "Fehler"],
// Finestra impostazioni // Finestra impostazioni
["settings.title"] = ["Impostazioni", "Settings"], ["settings.title"] = ["Impostazioni", "Settings", "Configuración", "Paramètres", "Configurações", "Einstellungen"],
["settings.language"] = ["Lingua", "Language"], ["settings.language"] = ["Lingua", "Language", "Idioma", "Langue", "Idioma", "Sprache"],
["settings.unit"] = ["Unità degli importi", "Amount unit"], ["settings.unit"] = ["Unità degli importi", "Amount unit", "Unidad de importes", "Unité des montants", "Unidade dos valores", "Betrageinheit"],
["settings.ok"] = ["Salva", "Save"], ["settings.ok"] = ["Salva", "Save", "Guardar", "Enregistrer", "Salvar", "Speichern"],
["settings.cancel"] = ["Annulla", "Cancel"], ["settings.cancel"] = ["Annulla", "Cancel", "Cancelar", "Annuler", "Cancelar", "Abbrechen"],
}; };
} }
+15 -3
View File
@@ -46,8 +46,10 @@ public partial class MainWindowViewModel : ViewModelBase
/// <summary>Configurazione globale (§8): lingua e unità.</summary> /// <summary>Configurazione globale (§8): lingua e unità.</summary>
private AppConfig _config = AppConfig.Load(); private AppConfig _config = AppConfig.Load();
/// <summary>Stringhe localizzate, bindabili da XAML come Loc[chiave].</summary> /// <summary>Istanza Loc corrente: viene rimpiazzata ad ogni cambio lingua
public Loc Loc => Loc.Instance; /// così Avalonia vede un riferimento diverso e rivaluta le binding {Binding Loc[chiave]}.</summary>
private Loc _loc = Loc.Instance;
public Loc Loc => _loc;
/// <summary>Unità corrente per il campo importo del pannello Invia.</summary> /// <summary>Unità corrente per il campo importo del pannello Invia.</summary>
public string UnitLabel => _config.Unit; public string UnitLabel => _config.Unit;
@@ -57,6 +59,10 @@ public partial class MainWindowViewModel : ViewModelBase
// Spunte del menu Impostazioni (ToggleType Radio). // Spunte del menu Impostazioni (ToggleType Radio).
public bool IsLangIt => _config.Language == "it"; public bool IsLangIt => _config.Language == "it";
public bool IsLangEn => _config.Language == "en"; public bool IsLangEn => _config.Language == "en";
public bool IsLangEs => _config.Language == "es";
public bool IsLangFr => _config.Language == "fr";
public bool IsLangPt => _config.Language == "pt";
public bool IsLangDe => _config.Language == "de";
public bool IsUnitPlm => _config.Unit == "PLM"; public bool IsUnitPlm => _config.Unit == "PLM";
public bool IsUnitMilli => _config.Unit == "mPLM"; public bool IsUnitMilli => _config.Unit == "mPLM";
public bool IsUnitMicro => _config.Unit == "µPLM"; public bool IsUnitMicro => _config.Unit == "µPLM";
@@ -81,10 +87,15 @@ public partial class MainWindowViewModel : ViewModelBase
{ {
_config = config; _config = config;
_config.Save(); _config.Save();
Loc.SetLanguage(config.Language); _loc = Loc.SwitchTo(config.Language);
OnPropertyChanged(nameof(Loc));
OnPropertyChanged(nameof(UnitLabel)); OnPropertyChanged(nameof(UnitLabel));
OnPropertyChanged(nameof(IsLangIt)); OnPropertyChanged(nameof(IsLangIt));
OnPropertyChanged(nameof(IsLangEn)); OnPropertyChanged(nameof(IsLangEn));
OnPropertyChanged(nameof(IsLangEs));
OnPropertyChanged(nameof(IsLangFr));
OnPropertyChanged(nameof(IsLangPt));
OnPropertyChanged(nameof(IsLangDe));
OnPropertyChanged(nameof(IsUnitPlm)); OnPropertyChanged(nameof(IsUnitPlm));
OnPropertyChanged(nameof(IsUnitMilli)); OnPropertyChanged(nameof(IsUnitMilli));
OnPropertyChanged(nameof(IsUnitMicro)); OnPropertyChanged(nameof(IsUnitMicro));
@@ -227,6 +238,7 @@ public partial class MainWindowViewModel : ViewModelBase
public MainWindowViewModel() public MainWindowViewModel()
{ {
_loc = Loc.SwitchTo(_config.Language);
RefreshSetupState(); RefreshSetupState();
// Aggiornamenti continui (§9): ping periodico per tenere viva la // Aggiornamenti continui (§9): ping periodico per tenere viva la
// connessione e accorgersi subito della caduta; se cade, riconnette // connessione e accorgersi subito della caduta; se cade, riconnette
+12
View File
@@ -37,6 +37,18 @@
<MenuItem Header="English" ToggleType="Radio" <MenuItem Header="English" ToggleType="Radio"
IsChecked="{Binding IsLangEn, Mode=OneWay}" IsChecked="{Binding IsLangEn, Mode=OneWay}"
Command="{Binding SetLanguageCommand}" CommandParameter="en"/> Command="{Binding SetLanguageCommand}" CommandParameter="en"/>
<MenuItem Header="Español" ToggleType="Radio"
IsChecked="{Binding IsLangEs, Mode=OneWay}"
Command="{Binding SetLanguageCommand}" CommandParameter="es"/>
<MenuItem Header="Français" ToggleType="Radio"
IsChecked="{Binding IsLangFr, Mode=OneWay}"
Command="{Binding SetLanguageCommand}" CommandParameter="fr"/>
<MenuItem Header="Português" ToggleType="Radio"
IsChecked="{Binding IsLangPt, Mode=OneWay}"
Command="{Binding SetLanguageCommand}" CommandParameter="pt"/>
<MenuItem Header="Deutsch" ToggleType="Radio"
IsChecked="{Binding IsLangDe, Mode=OneWay}"
Command="{Binding SetLanguageCommand}" CommandParameter="de"/>
</MenuItem> </MenuItem>
<MenuItem Header="{Binding Loc[settings.unit.short]}"> <MenuItem Header="{Binding Loc[settings.unit.short]}">
<MenuItem Header="PLM" ToggleType="Radio" <MenuItem Header="PLM" ToggleType="Radio"