From 5ae52082640fbe7338e522e9febe5c1f1fc39943 Mon Sep 17 00:00:00 2001 From: astropuzzo Date: Tue, 28 Jul 2026 09:41:55 +0200 Subject: [PATCH] feat(ui): complete accessibility and i18n --- .github/workflows/cross-platform.yml | 3 + README.md | 12 ++ assets/i18n/en.json | 144 ++++++++++++++++++ docs/CHANGELOG.md | 9 ++ docs/CROSS_PLATFORM_PARITY.md | 4 +- docs/PIANO_READY_TO_SELL.md | 65 ++++++++ dotnet/AstroForge.App/App.xaml | 5 + dotnet/AstroForge.App/MainWindow.xaml | 53 +++---- dotnet/AstroForge.App/MainWindow.xaml.cs | 60 +++++++- .../Services/WpfLocalizationAdapter.cs | 30 ++++ dotnet/AstroForge.CrossPlatform/App.axaml | 4 + .../AvaloniaLocalizationAdapter.cs | 33 +++- .../AstroForge.CrossPlatform/MainWindow.axaml | 26 ++-- .../MainWindow.axaml.cs | 57 ++++++- qa-gate.ps1 | 6 +- scripts/check-ui-contract.ps1 | 61 ++++++++ version.json | 2 +- 17 files changed, 528 insertions(+), 46 deletions(-) create mode 100644 docs/PIANO_READY_TO_SELL.md create mode 100644 scripts/check-ui-contract.ps1 diff --git a/.github/workflows/cross-platform.yml b/.github/workflows/cross-platform.yml index 9d6349d..d0b8137 100644 --- a/.github/workflows/cross-platform.yml +++ b/.github/workflows/cross-platform.yml @@ -39,6 +39,9 @@ jobs: - name: Parity contract shell: pwsh run: ./scripts/verify-cross-platform-parity.ps1 -SkipBuild + - name: UI accessibility and localization contract + shell: pwsh + run: ./scripts/check-ui-contract.ps1 - name: Publish self-contained app shell: bash run: | diff --git a/README.md b/README.md index 0a68914..86d6b01 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,12 @@ AstroProject Forge reads FITS and XISF metadata, reconstructs observing nights a 5. Check the suggested WBPP Grouping Keywords. 6. Export the project and load it in PixInsight WeightedBatchPreprocessing. +### Keyboard + +`Ctrl+O` opens a project, `Ctrl+S` saves it, `Ctrl+Shift+S` saves a copy, +`Ctrl+Enter` analyzes it, `Ctrl+,` opens the menu and `F1` opens the guide. +Use `Alt+1` through `Alt+8` to move directly between workspaces. + The suggested Master Library layout starts with the camera: ```text @@ -76,6 +82,12 @@ AstroProject Forge legge i metadati FITS e XISF, ricostruisce le notti osservati 5. Controlla le Grouping Keywords WBPP suggerite. 6. Esporta il progetto e caricalo in PixInsight WeightedBatchPreprocessing. +### Tastiera + +`Ctrl+O` apre un progetto, `Ctrl+S` lo salva, `Ctrl+Shift+S` salva una copia, +`Ctrl+Invio` avvia l'analisi, `Ctrl+,` apre il menu e `F1` apre la guida. +Usa `Alt+1`–`Alt+8` per passare direttamente da un'area di lavoro all'altra. + ## Downloads | Platform | Packages | diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 5bd7cc8..999c286 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -294,4 +294,148 @@ ,"Margine spazio esportazione (%)": "Export free-space margin (%)" ,"Forza una nuova lettura alla prossima analisi": "Force a fresh read during the next analysis" ,"Rimuove i dati dall'app senza cancellare i file": "Remove data from the app without deleting files" + ," da risolvere": " to review" + ,"+ File": "+ File" + ,"Aggiorna": "Refresh" + ,"Aggiungi Dark e Bias": "Add Dark and Bias frames" + ,"Aggiungi una o più librerie di calibrazione. Questa funzione non richiede un progetto.": "Add one or more calibration libraries. This feature does not require a project." + ,"Analisi qualità": "Quality analysis" + ,"Anteprima non generata": "Preview not generated" + ,"Assegna il Flat Set alla selezione": "Assign the Flat Set to the selection" + ,"ASTROPROJECT FORGE": "ASTROPROJECT FORGE" + ,"AstroProject Forge": "AstroProject Forge" + ,"Avvia l’analisi per costruire la mappa del progetto.": "Run the analysis to build the project map." + ,"Azzera filtri": "Clear filters" + ,"Bin": "Bin" + ,"Calibrazioni assegnate": "Assigned calibrations" + ,"Cambio notte": "Night boundary" + ,"CAMERA": "CAMERA" + ,"Cerca file, filtro, sessione…": "Search file, filter or session…" + ,"Classificazione": "Classification" + ,"Clicca l’intestazione per ordinare · Ctrl/Shift per selezionare · doppio click per mostrare l’originale": "Select a column header to sort · use Ctrl/Shift to select · double-click to reveal the original" + ,"Codice": "Code" + ,"Collega alla selezione": "Link to selection" + ,"Configura librerie di calibrazione, notte astronomica e sorgenti FITS/XISF.": "Configure calibration libraries, astronomical nights and FITS/XISF sources." + ,"Configurazione completata": "Setup complete" + ,"Confronta i candidati campo per campo": "Compare candidates field by field" + ,"Controlla metadati e destinazioni prima della copia": "Check metadata and destinations before copying" + ,"Controlla metadati e destinazioni senza copiare file": "Check metadata and destinations without copying files" + ,"Controlla nome e destinazione, poi costruisci l’albero esatto del progetto.": "Check the name and destination, then build the exact project tree." + ,"CONTROLLI AUTOMATICI": "AUTOMATIC CHECKS" + ,"Copertura": "Coverage" + ,"Copia i file nella struttura di progetto": "Copy files into the project structure" + ,"Copia, imprimi e organizza": "Copy, stamp and organize" + ,"Copyright © 2026 Gianmarco Spagnoli": "Copyright © 2026 Gianmarco Spagnoli" + ,"Creato interamente tramite vibe coding assistito da AI. L’autore non ha una formazione da sviluppatore: segnala gli errori dal menu Aiuto e verifica sempre le calibrazioni.": "Created entirely through AI-assisted vibe coding. The author has no formal software-development background: report errors from the Help menu and always verify calibration assignments." + ,"DATI": "DATA" + ,"Destinazione nuova libreria": "New library destination" + ,"Destinazione nuova Libreria Master": "New Master Library destination" + ,"Dettaglio delle notti": "Night details" + ,"ECC.": "ECC." + ,"Eccezione": "Exception" + ,"Esp.": "Exp." + ,"Esposizione": "Exposure" + ,"Eventi applicazione": "Application events" + ,"Evento": "Event" + ,"EXP s": "EXP s" + ,"FILE": "FILE" + ,"FITS e XISF": "FITS and XISF" + ,"Flat Set": "Flat Set" + ,"FWHM": "FWHM" + ,"GAIN": "GAIN" + ,"Gain e Offset predefiniti vengono usati soltanto quando mancano nei metadati.": "Default Gain and Offset are used only when metadata is missing." + ,"Gain predefinito": "Default Gain" + ,"GitHub": "GitHub" + ,"Header originali": "Original headers" + ,"i": "i" + ,"I log non includono immagini, coordinate o percorsi completi.": "Logs do not include images, coordinates or full paths." + ,"I valori predefiniti vengono usati solo quando il metadato non è disponibile.": "Default values are used only when metadata is unavailable." + ,"ID": "ID" + ,"Importa": "Import" + ,"Imposta i valori mancanti": "Set missing values" + ,"Inizia": "Start" + ,"Integrazione totale": "Total integration" + ,"Intervallo": "Date range" + ,"KEYWORD": "KEYWORD" + ,"Le librerie Dark/Bias si configurano nel pannello Sorgenti.": "Configure Dark/Bias libraries in the Sources panel." + ,"Le librerie possono essere modificate in seguito.": "Libraries can be changed later." + ,"Le modifiche valgono solo nel progetto; i sorgenti non vengono riscritti.": "Changes apply only to the project; source files are never rewritten." + ,"Log locali con percorsi e nomi sensibili rimossi.": "Local logs with sensitive paths and names removed." + ,"Marca file, notti o sessioni dello stesso filtro, quindi scegli il Flat Set.": "Mark files, nights or sessions using the same filter, then choose the Flat Set." + ,"Margine %": "Margin %" + ,"MASTER": "MASTER" + ,"Menu": "Menu" + ,"metadati e calibrazioni": "metadata and calibrations" + ,"Modifica la ricerca o disattiva il filtro.": "Change the search or disable the filter." + ,"N.I.N.A., ASIAIR, SGP, Voyager, SharpCap o altri software": "N.I.N.A., ASIAIR, SGP, Voyager, SharpCap or other software" + ,"Nessun risultato": "No results" + ,"OFFSET": "OFFSET" + ,"Offset predefinito": "Default Offset" + ,"Passo 1 · Librerie": "Step 1 · Libraries" + ,"Passo 2 · Acquisizione": "Step 2 · Acquisition" + ,"POST": "POST" + ,"PRE": "PRE" + ,"PREFLIGHT / STATO": "PREFLIGHT / STATUS" + ,"progetto WBPP": "WBPP project" + ,"Pulisci filtri": "Clear filters" + ,"Puoi aggiungere più librerie e ordinarle per priorità.": "You can add multiple libraries and order them by priority." + ,"Readout": "Readout" + ,"Rimuove soltanto le copie create dall’ultima operazione": "Remove only copies created by the latest operation" + ,"Ripristina": "Restore" + ,"Riserva GiB": "Reserve GiB" + ,"Rumore": "Noise" + ,"Salta configurazione": "Skip setup" + ,"Scegli cartelle": "Choose folders" + ,"Scegli destinazione": "Choose destination" + ,"Scegli file": "Choose files" + ,"Score": "Score" + ,"Segnalazioni della selezione": "Selected-item issues" + ,"Sessione": "Session" + ,"Setpoint °C": "Setpoint °C" + ,"Sfoglia": "Browse" + ,"SHA-256": "SHA-256" + ,"SNR": "SNR" + ,"Solo da rivedere": "Review only" + ,"Solo questo Light": "This Light only" + ,"SORGENTI FITS / XISF": "FITS / XISF SOURCES" + ,"SPAZIO": "SPACE" + ,"Sposta i file segnalati in Excluded/Quality senza eliminare gli originali": "Place flagged files in Excluded/Quality without deleting the originals" + ,"Stato": "Status" + ,"Stelle": "Stars" + ,"STIMA": "ESTIMATE" + ,"Stima MiB/s": "Estimated MiB/s" + ,"Stretch": "Stretch" + ,"Struttura, spazio disponibile e integrità dei file.": "Structure, available space and file integrity." + ,"TEMP °C": "TEMP °C" + ,"Temp.": "Temp." + ,"Temperatura (°C)": "Temperature (°C)" + ,"Temperatura setpoint °C": "Temperature setpoint °C" + ,"TIPO": "TYPE" + ,"Tutta la notte": "Entire night" + ,"Uno score oltre la soglia è sospetto; diventa escluso solo dopo conferma.": "A score above the threshold is suspicious; it is excluded only after confirmation." + ,"Verifica i Master e crea una libreria ordinata. Funziona senza un progetto aperto.": "Verify Master frames and create an organized library. Works without an open project." + ,"Verranno eseguiti automaticamente quando avvii l’esportazione.": "They run automatically when you start the export." + ,"WBPP": "WBPP" + ,"Apri progetto · Ctrl+O": "Open project · Ctrl+O" + ,"Salva progetto · Ctrl+S · Salva con nome Ctrl+Shift+S": "Save project · Ctrl+S · Save as Ctrl+Shift+S" + ,"Menu · Ctrl+,": "Menu · Ctrl+," + ,"Analizza progetto · Ctrl+Invio": "Analyze project · Ctrl+Enter" + ,"Camera": "Camera" + ,"da risolvere": "to review" + ,"DESTINAZIONE": "DESTINATION" + ,"Ecc.": "Ecc." + ,"File": "File" + ,"Gain": "Gain" + ,"Ingrandisci anteprima": "Zoom in" + ,"Master": "Master" + ,"Offset": "Offset" + ,"Per filtro": "By filter" + ,"Riduci anteprima": "Zoom out" + ,"RUMORE": "NOISE" + ,"SCORE": "SCORE" + ,"STATO": "STATUS" + ,"STELLE": "STARS" + ,"Temperatura": "Temperature" + ,"Tipo": "Type" + ,"VERIFICA": "CHECK" } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1de21ff..cd8cafa 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,14 @@ # AstroProject Forge — note di rilascio +## 1.1.0 — 28 luglio 2026 + +- Completata la traduzione inglese delle interfacce Windows, Linux e macOS. +- Aggiunti nomi e descrizioni accessibili per screen reader e un focus tastiera più visibile. +- Nuove scorciatoie per apertura, salvataggio, analisi, menu, guida e cambio area di lavoro. +- `Ctrl+S` salva direttamente il progetto aperto; `Ctrl+Shift+S` apre **Salva con nome**. +- La QA ora blocca stringhe UI non tradotte e regressioni dei contratti di accessibilità. +- Ripristinato il piano ready-to-sell come roadmap verificabile e aggiornata. + ## 1.0.3 — 28 luglio 2026 - Le nuove installazioni Windows usano `C:\Program Files\AstroProject Forge`. diff --git a/docs/CROSS_PLATFORM_PARITY.md b/docs/CROSS_PLATFORM_PARITY.md index ee3b3e6..18b54a7 100644 --- a/docs/CROSS_PLATFORM_PARITY.md +++ b/docs/CROSS_PLATFORM_PARITY.md @@ -34,8 +34,8 @@ still requires an interaction test on the named OS; `blocked` prevents publicati | First-run onboarding linked to real source/library state | pass | implemented | native QA | native QA | P0 | | Signed platform-aware application updates | Windows only | pending redesign | blocked | blocked | commercial | | Responsive panels at 980–2560 px and HiDPI | pass | implemented | native QA | native QA | P0 | -| Keyboard navigation and screen-reader names | partial | partial | blocked | blocked | P0 | -| English UI/localization | partial | partial | blocked | blocked | P0 | +| Keyboard navigation and screen-reader names | pass | implemented | native QA | native QA | P0 | +| English UI/localization | pass | implemented | native QA | native QA | P0 | | Linux self-contained x64/ARM64 package | n/a | implemented | native QA | n/a | P0 | | macOS Intel/Apple Silicon `.app` bundle | n/a | implemented | n/a | native QA | P0 | | macOS signing, hardened runtime and notarization | n/a | prepared only | n/a | blocked | commercial | diff --git a/docs/PIANO_READY_TO_SELL.md b/docs/PIANO_READY_TO_SELL.md new file mode 100644 index 0000000..7a88e34 --- /dev/null +++ b/docs/PIANO_READY_TO_SELL.md @@ -0,0 +1,65 @@ +# Piano ready to sell + +Aggiornato al 28 luglio 2026. Questo documento raccoglie soltanto lavoro concreto e +verificabile. Le idee già realizzate non restano mischiate alle attività aperte. + +## Stato del prodotto + +AstroProject Forge dispone già del flusso completo: import FITS/XISF generico, +ricostruzione delle notti oltre la mezzanotte, sessioni di configurazione, associazione +Flat/Dark/Bias, librerie multiple per camera, Quality Lab, progetto WBPP, esportazione +verificata, salvataggio `.astroforge`, installer e aggiornamento automatico Windows. +La stessa logica Core è usata dall'interfaccia Avalonia per Linux e macOS. + +## Milestone corrente — affidabilità dell'interfaccia + +- [x] Traduzione inglese completa delle stringhe statiche WPF e Avalonia. +- [x] Controllo automatico che blocca nuove stringhe prive di traduzione. +- [x] Scorciatoie coerenti: apri, salva, salva con nome, analizza, menu, guida e workspace. +- [x] Salvataggio diretto del progetto già aperto; `Salva con nome` resta esplicito. +- [x] Nomi e descrizioni per screen reader sui controlli principali. +- [x] Focus tastiera ad alto contrasto. +- [ ] Test manuale completo con Narrator su Windows. +- [ ] Test manuale tastiera, VoiceOver e Orca sui pacchetti nativi. + +## P0 — prima di una vendita commerciale + +- [ ] Validare su dati reali la matrice di associazione WBPP: mono, OSC, binning, + esposizioni miste, Dark Flat e sensori senza Bias. +- [ ] Aggiungere test end-to-end con fixture FITS/XISF pubblicabili e risultati attesi. +- [ ] Collaudare installer e aggiornamento Windows su account standard, percorso + personalizzato, spazio insufficiente, rete interrotta e rollback. +- [ ] Eseguire la matrice HiDPI 100/150/200% e finestre 980–2560 px su tutti i sistemi. +- [ ] Completare una beta hardware con almeno tre camere e più software di acquisizione. +- [ ] Definire licenza commerciale, privacy, termini di supporto e politica di rimborso. +- [ ] Ottenere firma Windows e Apple notarization quando il budget lo consente. + +## P1 — upgrade ad alto valore + +- [ ] Dashboard copertura Master: combinazioni camera/gain/offset/temperatura/esposizione + mancanti, obsolete o duplicate. +- [ ] Profili strumento: camera, ruota filtri, treno ottico e intervalli di validità + dichiarati dall'utente. +- [ ] Timeline degli eventi ottici: rotazione camera, pulizia, cambio filtro o adattatore; + ogni evento propone automaticamente un nuovo Flat Set. +- [ ] Aggiornamento incrementale: importare una nuova notte in un progetto esistente senza + ricalcolare file invariati. +- [ ] Obiettivi di integrazione per filtro con avanzamento, deficit e previsione notti. +- [ ] Cache Quality Lab invalidata da hash, per non rimisurare frame invariati. +- [ ] Regole qualità personalizzabili per serie e preset per focale/campionamento. +- [ ] Confronto visuale sincronizzato di due frame con stesso zoom e stretch. + +## P2 — maturità operativa + +- [ ] Telemetria solo opt-in, anonima e documentata per crash e prestazioni. +- [ ] Import/export di profili e impostazioni. +- [ ] Migrazione versionata dei progetti con backup automatico. +- [ ] Guida contestuale collegata allo stato reale del progetto. +- [ ] Localizzazione aggiuntiva tramite file di risorse validati dalla CI. +- [ ] Benchmark pubblici su librerie da 1.000, 10.000 e 50.000 frame. + +## Regola di rilascio + +Una voce è completata soltanto quando possiede codice, test automatico dove possibile e +una verifica sul sistema operativo reale. La compilazione non equivale a parità funzionale: +la matrice aggiornata è in [CROSS_PLATFORM_PARITY.md](CROSS_PLATFORM_PARITY.md). diff --git a/dotnet/AstroForge.App/App.xaml b/dotnet/AstroForge.App/App.xaml index 6aee1f8..b5da6d7 100644 --- a/dotnet/AstroForge.App/App.xaml +++ b/dotnet/AstroForge.App/App.xaml @@ -78,6 +78,7 @@ + @@ -117,6 +118,9 @@ + + + + + @@ -26,6 +28,8 @@ + + diff --git a/dotnet/AstroForge.CrossPlatform/AvaloniaLocalizationAdapter.cs b/dotnet/AstroForge.CrossPlatform/AvaloniaLocalizationAdapter.cs index 40dfb5a..db97eb0 100644 --- a/dotnet/AstroForge.CrossPlatform/AvaloniaLocalizationAdapter.cs +++ b/dotnet/AstroForge.CrossPlatform/AvaloniaLocalizationAdapter.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.VisualTree; @@ -25,10 +26,40 @@ private static void Translate(Visual value, string language) if (value is HeaderedContentControl headered && headered.Header is string header) headered.SetCurrentValue(HeaderedContentControl.HeaderProperty, UiLocalization.Translate(header, language)); if (value is Control control && ToolTip.GetTip(control) is string tip) - ToolTip.SetTip(control, UiLocalization.Translate(tip, language)); + { + var translatedTip = UiLocalization.Translate(tip, language); + ToolTip.SetTip(control, translatedTip); + if (string.IsNullOrWhiteSpace(AutomationProperties.GetHelpText(control))) + AutomationProperties.SetHelpText(control, translatedTip); + } + if (value is Control accessibleControl) ApplyAccessibleName(accessibleControl, language); if (value is DataGrid grid) foreach (var column in grid.Columns) if (column.Header is string columnHeader) column.Header = UiLocalization.Translate(columnHeader, language); } + private static void ApplyAccessibleName(Control control, string language) + { + var existing = AutomationProperties.GetName(control); + if (!string.IsNullOrWhiteSpace(existing)) + { + AutomationProperties.SetName(control, UiLocalization.Translate(existing, language)); + return; + } + + var candidate = control switch + { + TextBox textBox when !string.IsNullOrWhiteSpace(textBox.PlaceholderText) => textBox.PlaceholderText, + ContentControl content when content.Content is string text => text, + HeaderedContentControl headered when headered.Header is string header => header, + _ when ToolTip.GetTip(control) is string tip => tip, + _ => null + }; + if (IsMeaningful(candidate)) + AutomationProperties.SetName(control, UiLocalization.Translate(candidate!, language)); + } + + private static bool IsMeaningful(string? value) => + !string.IsNullOrWhiteSpace(value) && value.Count(char.IsLetterOrDigit) >= 2; + } diff --git a/dotnet/AstroForge.CrossPlatform/MainWindow.axaml b/dotnet/AstroForge.CrossPlatform/MainWindow.axaml index 9977872..043e19d 100644 --- a/dotnet/AstroForge.CrossPlatform/MainWindow.axaml +++ b/dotnet/AstroForge.CrossPlatform/MainWindow.axaml @@ -16,10 +16,10 @@