diff --git a/electron/locale/catalogs.ts b/electron/locale/catalogs.ts index 3effbb5..7830640 100644 --- a/electron/locale/catalogs.ts +++ b/electron/locale/catalogs.ts @@ -6,6 +6,7 @@ import { fr } from "./fr"; import { ja } from "./ja"; import { ko } from "./ko"; import { pt } from "./pt"; +import { it } from "./it"; import { zhCN } from "./zh-CN"; import { zhTW } from "./zh-TW"; @@ -21,4 +22,5 @@ export const desktopCatalogs: Record = { fr, de, pt, + it, }; diff --git a/electron/locale/it.ts b/electron/locale/it.ts new file mode 100644 index 0000000..4a4c956 --- /dev/null +++ b/electron/locale/it.ts @@ -0,0 +1,43 @@ +import type { DesktopMessageKey } from "./en"; + +export const it: Record = { + file: "File", + openProject: "Apri progetto…", + reopenLast: "Riapri ultimo progetto", + recentProjects: "Progetti recenti", + noRecent: "Nessun progetto recente", + clearRecent: "Cancella progetti recenti", + edit: "Modifica", + undo: "Annulla", + redo: "Ripeti", + cut: "Taglia", + copy: "Copia", + paste: "Incolla", + pasteMatch: "Incolla e adatta lo stile", + delete: "Elimina", + selectAll: "Seleziona tutto", + view: "Visualizza", + reload: "Ricarica", + forceReload: "Forza ricarica", + devTools: "Attiva/disattiva Strumenti sviluppatore", + resetZoom: "Dimensioni reali", + zoomIn: "Aumenta zoom", + zoomOut: "Riduci zoom", + fullscreen: "Attiva/disattiva schermo intero", + window: "Finestra", + minimize: "Minimizza", + zoom: "Zoom", + front: "Porta tutto in primo piano", + close: "Chiudi finestra", + quit: "Esci da Rescript", + about: "Informazioni su Rescript", + services: "Servizi", + hide: "Nascondi Rescript", + hideOthers: "Nascondi altri", + unhide: "Mostra tutto", + restart: "Riavvia", + later: "Più tardi", + updateTitle: "Aggiornamento disponibile", + updateMessage: "Rescript {version} è pronto per essere installato.", + updateDetail: "Riavvia ora per applicare l'aggiornamento.", +}; diff --git a/lib/i18n/catalogs.ts b/lib/i18n/catalogs.ts index ad39d4f..7682697 100644 --- a/lib/i18n/catalogs.ts +++ b/lib/i18n/catalogs.ts @@ -5,6 +5,7 @@ import { fr } from "./messages/fr"; import { ja } from "./messages/ja"; import { ko } from "./messages/ko"; import { pt } from "./messages/pt"; +import { it } from "./messages/it"; import { zhCN } from "./messages/zh-CN"; import { zhTW } from "./messages/zh-TW"; import type { UiLocale } from "./locales"; @@ -21,4 +22,5 @@ export const catalogs: Record = { fr, de, pt, + it, }; diff --git a/lib/i18n/locales.ts b/lib/i18n/locales.ts index a022791..df4adba 100644 --- a/lib/i18n/locales.ts +++ b/lib/i18n/locales.ts @@ -15,6 +15,7 @@ export const UI_LOCALES = [ "fr", "de", "pt", + "it", ] as const; export type UiLocale = (typeof UI_LOCALES)[number]; @@ -99,6 +100,12 @@ export const UI_LOCALE_META: Record = { nsis: "pt_BR", match: prefix("pt"), }, + it: { + htmlLang: "it", + nativeLabel: "Italiano", + nsis: "it_IT", + match: prefix("it"), + }, }; function isTraditionalChinese(tag: string): boolean { @@ -119,6 +126,7 @@ const MATCH_ORDER: readonly UiLocale[] = [ "fr", "de", "pt", + "it", "en", ]; diff --git a/lib/i18n/messages/it.ts b/lib/i18n/messages/it.ts new file mode 100644 index 0000000..422a230 --- /dev/null +++ b/lib/i18n/messages/it.ts @@ -0,0 +1,272 @@ +import type { MessageKey } from "./en"; + +/** Italian UI catalog. Every key in {@link en} must be present. */ +export const it: Record = { + "app.title": "Rescript — monta i video come modifichi il testo", + "common.cancel": "Annulla", + "common.close": "Chiudi", + "common.delete": "Elimina", + "common.download": "Scarica", + "common.import": "Importa", + "common.loading": "Caricamento", + "common.remove": "Rimuovi", + "common.restore": "Ripristina", + "common.retry": "Riprova", + "common.searchOrCreate": "Cerca o crea…", + "common.settings": "Impostazioni", + "common.system": "Sistema", + "common.tools": "Strumenti", + "settings.appearance": "Aspetto", + "settings.light": "Chiaro", + "settings.dark": "Scuro", + "settings.interfaceLanguage": "Lingua dell'interfaccia", + "settings.privacy": "Privacy", + "settings.helpImprove": "Aiuta a migliorare l'app", + "settings.telemetryHelp": + "Invia statistiche anonime sull'uso delle funzioni e segnalazioni di arresti anomali.", + "settings.support": "Supporto / feedback", + "settings.reportIssue": "Segnala un problema", + "settings.homepage": "Pagina iniziale", + "settings.github": "GitHub", + "settings.followX": "Segui su X", + "model.transcriptSource": "Origine della trascrizione", + "model.language": "Lingua", + "model.transcriptLanguage": "Lingua della trascrizione", + "model.importTranscript": "Importa trascrizione", + "upload.recentProjects": "Progetti recenti", + "upload.removeRecent": "Rimuovi dai recenti", + "upload.dropPrefix": "Rilascia qui un file video o audio, oppure", + "upload.browse": "sfoglia", + "upload.chooseTranscriptFirst": + "Scegli una trascrizione nel menu sopra, poi rilascia il file multimediale", + "upload.willUseTranscript": "Verrà usato {name} · MP4, WebM, MOV, MP3, WAV, …", + "upload.mediaFormats": "MP4, WebM, MOV, MP3, WAV, M4A, …", + "upload.gettingReady": "Preparazione in corso", + "upload.gettingReadyHelp": "Configurazione del motore multimediale, succede solo una volta.", + "upload.unsupported": "Questo browser non può eseguire l'editor", + "upload.unsupportedHelp": + "L'editing richiede SharedArrayBuffer, che necessita di una pagina isolata cross-origin. Prova una versione recente di Chrome, Edge, Safari o Firefox via HTTPS.", + "upload.unsupportedSimd": "Questo dispositivo non può eseguire l'editor", + "upload.unsupportedSimdHelp": + "L'editing richiede WebAssembly SIMD, che questo processore non dichiara di supportare. Su un PC Windows su Arm, aggiorna all'ultima versione di Rescript: gli installer attuali includono una build nativa Arm64 che non viene eseguita in emulazione.", + "upload.transcribeTitle": "Trascrivi", + "upload.transcribeText": "Whisper in locale, oppure importa SRT / VTT.", + "upload.editTitle": "Modifica", + "upload.editText": "Seleziona le parole e premi Canc per modificare.", + "upload.exportTitle": "Esporta", + "upload.exportText": "Esegui il rendering del montaggio finale in MP4 o M4A.", + "editor.chooseTranscript": "Scegli prima un file di trascrizione dal menu di origine.", + "editor.chooseMedia": "Scegli un file video o audio.", + "editor.undo": "Annulla (⌘Z)", + "editor.redo": "Ripeti (⇧⌘Z)", + "editor.export": "Esporta", + "topbar.startOver": "Ricomincia", + "transcript.replace": "Sostituisci la trascrizione da SRT, VTT o JSON", + "transcript.header": "Trascrizione", + "transcript.wordDeleted": "{count} parola", + "transcript.wordsDeleted": "{count} parole", + "transcript.replaceConfirm": "Sostituire la trascrizione attuale con questo file?", + "transcript.invalidFile": "Scegli un file di trascrizione SRT, VTT o JSON.", + "transcript.noSpeech": + "Nessun parlato rilevato. Assicurati che questo file abbia l'audio, oppure importa una trascrizione.", + "transcript.cut": "Taglia", + "transcript.follow": "Segui il cursore", + "transcript.hideDeleted": "Nascondi le parole eliminate", + "transcript.showDeleted": "Mostra le parole eliminate", + "transcript.correct": "Correggi", + "transcript.scrollWithPlayhead": "Scorri insieme al cursore", + "transcript.joinSplit": "Clip divisa — clicca per unire queste clip", + "transcript.joinClips": "Unisci le clip", + "transcript.hesitation": + "Esitazione rilevata (non trascritta) — tagliala con Rimuovi parole di riempimento", + "transcript.textLabel": "Testo della trascrizione", + "transcript.textHint": + "Le freccette spostano tra le parole e muovono il cursore. Maiusc con le freccette seleziona. Backspace o Canc tagliano le parole, Invio le corregge, Spazio riproduce o metti in pausa, e Annulla ripristina i tagli.", + "transcript.textHesitation": "[esitazione]", + "transcript.textCut": "Tagliato: {text}", + "transcript.textNothingToCut": "Niente da tagliare", + "transcript.textUndo": "Annulla", + "transcript.textRedo": "Ripeti", + "tools.bulk": "Pulizie di massa della trascrizione", + "tools.removeFillers": "Rimuovi parole di riempimento", + "tools.removeFillersTitle": + 'Taglia le parole di riempimento ("ehm", "uhm", "...", …) dal video', + "tools.restoreFillers": "Ripristina parole di riempimento", + "tools.restoreFillersTitle": "Riporta indietro ogni parola di riempimento tagliata", + "tools.removeSilences": "Rimuovi i silenzi", + "tools.removeSilencesTitle": "Taglia pause e silenzi (≥{seconds}s) dal video", + "tools.restoreSilences": "Ripristina i silenzi", + "tools.restoreSilencesTitle": "Riporta indietro ogni pausa tagliata", + "timeline.removed": "{trimmed} rimossi — durata originale {duration}", + "timeline.back5": "Indietro di 5 s", + "timeline.playPause": "Riproduci / pausa (spazio)", + "timeline.forward5": "Avanti di 5 s", + "timeline.split": "Dividi", + "timeline.splitTitle": "Dividi la clip al cursore (S)", + "timeline.splitDisabled": "Sposta il cursore su una regione conservata per dividere", + "timeline.delete": "Elimina", + "timeline.deleteTitle": "Elimina la clip selezionata (Canc)", + "timeline.deleteDisabled": "Seleziona una clip sulla timeline per eliminarla", + "timeline.restore": "Ripristina", + "timeline.restoreTitle": "Ripristina il taglio selezionato (Canc)", + "timeline.restoreDisabled": + "Seleziona un taglio o una sezione di silenzio sulla timeline per ripristinarla", + "timeline.zoomOut": "Riduci zoom", + "timeline.fit": "Adatta alla finestra", + "timeline.zoomIn": "Aumenta zoom — trascina i bordi delle parole per perfezionare i tempi", + "timeline.joinClips": "Unisci queste clip (rimuovi la divisione)", + "timeline.trimStart": "Rifila l'inizio della clip", + "timeline.trimEnd": "Rifila la fine della clip", + "timeline.hesitationAdjust": + "… esitazione rilevata — trascina i bordi per sistemare, oppure Rimuovi parole di riempimento per tagliare", + "timeline.hesitationCut": "… esitazione rilevata — tagliala con Rimuovi parole di riempimento", + "timeline.dragTiming": "{word} — trascina i bordi per sistemare i tempi", + "timeline.scrollZoom": "Scorri per aumentare/ridurre lo zoom", + "speaker.moveStart": "Trascina per spostare dove inizia questo parlante", + "speaker.moveLabel": "Sposta l'etichetta del parlante", + "speaker.options": "Opzioni del parlante", + "speaker.change": "Cambia parlante", + "speaker.rename": "Rinomina parlante", + "speaker.renameAction": "Rinomina", + "speaker.replace": "Sostituisci nel progetto con…", + "speaker.removeProject": "Rimuovi dal progetto", + "speaker.newName": "Nuovo nome", + "speaker.find": "Trova un parlante…", + "speaker.noMatches": "Nessun parlante corrispondente", + "speaker.renameTo": "Rinomina {current} in {next}", + "speaker.button": "Parlante", + "speaker.search": "Cerca o crea…", + "speaker.defaultName": "Parlante {number}", + "speaker.create": 'Crea "{name}"', + "export.title": "Esporta", + "export.type": "Tipo di esportazione", + "export.video": "Video", + "export.audio": "Audio", + "export.transcript": "Trascrizione", + "export.subtitles": "Sottotitoli", + "export.videoUnavailable": + "L'esportazione video non è disponibile per i progetti solo audio", + "export.noAudio": "Questo file non ha una traccia audio", + "export.noWordsFirst": "Trascrivi o importa prima una trascrizione", + "export.format": "Formato", + "export.resolution": "Risoluzione", + "export.original": "Originale", + "export.plainText": "TXT", + "export.markdown": "Markdown", + "export.statOriginal": "Originale", + "export.statCuts": "Tagli", + "export.statEdited": "Montato", + "export.includeTimestamps": "Includi i timestamp", + "export.speakerLabels": "Includi i nomi dei parlanti", + "export.shortCues": "Sottotitoli brevi (≤5s)", + "export.transcriptHelp": "Testo con etichette dei parlanti e tagli rimossi.", + "export.transcriptHelpTimestamps": + "Testo con etichette dei parlanti e tagli rimossi. Ogni intervento inizia con il suo tempo sulla timeline montata.", + "export.transcriptHelpPdf": + "Il PDF usa un font latino standard — preferisci DOCX per Unicode completo (CJK, ecc.).", + "export.subtitlesHelp": + "SRT e VTT usano la timeline montata (con i tagli applicati). JSON conserva l'elenco completo delle parole per reimportare.", + "export.subtitlesHelpShortCues": + "SRT e VTT usano la timeline montata (con i tagli applicati), divisi in sottotitoli brevi per frase (≤5s). JSON conserva l'elenco completo delle parole per reimportare.", + "export.encodingHelp": "Ricodifica con ffmpeg.wasm — i file più lunghi richiedono un po' di tempo.", + "export.reexport": "Esporta di nuovo con le ultime modifiche", + "export.rendering": "Rendering nel browser…", + "export.downloadFile": "Scarica {name}", + "export.saveFile": "Salva {name}…", + "export.saving": "Salvataggio…", + "export.saveDialogTitle": "Salva esportazione", + "export.savedAs": "Salvato come {name}", + "export.showInFinder": "Mostra nel Finder", + "export.showInExplorer": "Mostra in Esplora file", + "export.showInFolder": "Mostra nella cartella", + "export.renderingNative": "Rendering…", + "export.encodingHelpNative": "Ricodifica con ffmpeg — i file più lunghi richiedono un po' di tempo.", + "export.downloadFormat": "Scarica .{format}", + "export.exportFormat": "Esporta {format}", + "export.timeline": "Timeline", + "export.timelineTitle": + "Esporta una sequenza NLE / DAW (Resolve, Premiere, Final Cut, Pro Tools, Reaper)", + "export.nle": "Formato", + "export.frameRate": "Frame rate", + "export.timelineHelp": + "La sequenza fa riferimento al file multimediale originale tramite il nome del file — ricollegalo nel NLE dopo l'importazione.", + "export.timelineHelpAaf": "AAF di Pro Tools / Logic (solo metadati).", + "export.timelineHelpFcpx": "FCPXML di Final Cut Pro.", + "export.timelineHelpPremiere": "XML di Adobe Premiere Pro (xmeml).", + "export.timelineHelpResolve": "XML di DaVinci Resolve (xmeml).", + "export.timelineHelpReaper": "Progetto Reaper (.rpp).", + "export.timelineHelpSamplitude": + "EDL di Samplitude (.edl) — si apre anche direttamente in Reaper.", + "export.aafOverCap": + "Questo montaggio ha {count} clip; AAF ne supporta fino a {max}. Usa Resolve, Premiere, Final Cut, Reaper o EDL Samplitude.", + "export.preparing": "Preparazione…", + "import.reading": "Lettura della trascrizione…", + "import.importing": "Importazione…", + "import.failed": "Importazione non riuscita", + "import.readingFile": "Lettura del file…", + "import.chooseFileShort": "Scegli un file…", + "import.chooseFile": "Scegli una trascrizione SRT, VTT o JSON", + "import.selected": "{name} selezionato", + "banner.faster": "Vuoi trascrizioni ed esportazioni più rapide?", + "banner.getDesktop": "Scarica l'app desktop di Rescript", + "banner.downloadFor": "Scarica per {platform}", + "banner.dismiss": "Ignora", + "social.githubRepo": "Repository GitHub", + "social.discordServer": "Server Discord", + "social.xProfile": "Profilo X", + "globalError.title": "Rescript — qualcosa è andato storto", + "globalError.heading": "Qualcosa è andato storto", + "globalError.body": + "L'editor ha riscontrato un errore imprevisto. I tuoi progetti salvati sono ancora su questo dispositivo — ricaricando dovrebbero tornare.", + "progress.loadingMedia": "Caricamento del file multimediale…", + "progress.loadingMediaEngine": "Caricamento del motore multimediale…", + "progress.extractingAudio": "Estrazione dell'audio…", + "progress.preparingMedia": "Preparazione del file multimediale…", + "progress.loadingSpeechModel": "Caricamento del modello vocale…", + "progress.loadingSpeechCache": "Caricamento del modello vocale dalla cache…", + "progress.downloadingSpeech": "Download del modello vocale…", + "progress.gpuFallback": "GPU interrotta — si continua sulla CPU…", + "progress.detectingSpeech": "Rilevamento del parlato…", + "progress.transcribing": "Trascrizione in corso…", + "progress.loadingAlignCache": "Caricamento del modello di allineamento dalla cache…", + "progress.downloadingAlign": "Download del modello di allineamento…", + "progress.aligning": "Allineamento delle parole…", + "progress.speakers": "Identificazione dei parlanti…", + "error.selectModel": "Seleziona un modello vocale per trascrivere.", + "error.workerCrashed": "Il worker di trascrizione è andato in crash.", + "error.mediaEngineNetwork": + "Impossibile caricare il motore multimediale — la connessione si è interrotta. Controlla la connessione e riprova.", + "error.mediaEngineStalled": + "Il motore multimediale ha smesso di rispondere ed è stato riavviato. Chiudi altre app e schede per liberare memoria, poi riprova.", + "error.mediaEngineNotReady": + "Il motore multimediale non è ancora pronto — ricarica la pagina e riprova.", + "error.simdUnsupported": + "Questo processore non supporta WebAssembly SIMD, di cui il motore multimediale ha bisogno. Su un PC Windows su Arm, aggiorna all'ultima versione di Rescript: gli installer attuali includono una build nativa Arm64 che non viene eseguita in emulazione.", + "error.processFile": "Elaborazione del file non riuscita.", + "error.extractAudio": "Non è stato possibile estrarre l'audio da questo file.", + "error.nothingToExport": "È stato tutto eliminato — niente da esportare.", + "error.videoExport": "Esportazione non riuscita durante il rendering del video.", + "error.audioExport": "Esportazione non riuscita durante il rendering dell'audio.", + "error.exportSave": "Non è stato possibile salvare il file esportato.", + "error.diskFull": + "Non c'è abbastanza spazio libero su disco per finire. Libera un po' di spazio e riprova.", + "error.export": "Esportazione non riuscita.", + "error.timelineExport": "Esportazione della timeline non riuscita.", + "error.timelineEmpty": "Niente da mettere sulla timeline.", + "error.aafTemplate": "Non è stato possibile caricare il modello di esportazione AAF.", + "error.emptyTranscript": "Quel file di trascrizione è vuoto.", + "error.noTimedWords": "Nessuna parola temporizzata trovata in quella trascrizione.", + "error.parseJson": "Non è stato possibile analizzare quella trascrizione JSON.", + "error.jsonShape": 'Il JSON deve essere un array di parole oppure { "words": [...] }.', + "error.noWords": "Nessuna parola da esportare.", + "error.projectMissing": "Quel progetto non è più salvato.", + "error.openProject": "Non è stato possibile aprire quel progetto.", + "error.removeProject": "Non è stato possibile rimuovere quel progetto.", + "error.readTranscript": "Non è stato possibile leggere quella trascrizione.", + "error.clearRecent": "Non è stato possibile cancellare i progetti recenti.", + "error.modelDownload": + "Non è stato possibile completare il download del modello vocale — la connessione si è interrotta. Controlla la connessione e riprova; le parti già scaricate vengono conservate.", + "error.gpuReset": + "La trascrizione è stata interrotta quando la GPU si è reimpostata (spesso dopo aver bloccato lo schermo). Riprova.", + "confirm.clearRecent": "Rimuovere tutti i progetti recenti? Le loro modifiche salvate verranno eliminate.", +}; diff --git a/tests/i18n-test.ts b/tests/i18n-test.ts index 5d846c0..770dab9 100644 --- a/tests/i18n-test.ts +++ b/tests/i18n-test.ts @@ -27,6 +27,7 @@ assert(resolveUiLocale("system", ["es-MX"]) === "es", "spanish detection"); assert(resolveUiLocale("system", ["fr-CA"]) === "fr", "french detection"); assert(resolveUiLocale("system", ["de-AT"]) === "de", "german detection"); assert(resolveUiLocale("system", ["pt-BR"]) === "pt", "portuguese detection"); +assert(resolveUiLocale("system", ["it-IT"]) === "it", "italian detection"); assert(resolveUiLocale("system", ["pt-PT", "en-US"]) === "pt", "portuguese ordered"); assert(resolveUiLocale("system", ["fr-FR", "en-US"]) === "fr", "ordered fallback"); assert(resolveUiLocale("system", ["xx-YY", "en-US"]) === "en", "unsupported then en"); @@ -38,6 +39,7 @@ assert(translate("zh-CN", "common.settings") === "设置", "Chinese settings"); assert(translate("ja", "common.settings") === "設定", "Japanese settings"); assert(translate("es", "common.settings") === "Ajustes", "Spanish settings"); assert(translate("pt", "common.settings") === "Configurações", "Portuguese settings"); +assert(translate("it", "common.settings") === "Impostazioni", "Italian settings"); assert( translate("de", "export.downloadFile", { name: "demo.mp4" }).includes("demo.mp4"), "named interpolation" @@ -82,6 +84,7 @@ for (const locale of UI_LOCALES) { const nsis = nsisInstallerLanguages(); assert(nsis.includes("en_US") && nsis.includes("ja_JP") && nsis.includes("zh_TW"), "nsis codes"); assert(nsis.includes("pt_BR"), "nsis includes Portuguese"); +assert(nsis.includes("it_IT"), "nsis includes Italian"); const boot = buildLocaleBootScript(); assert(boot.includes("zh-TW"), "boot script knows Traditional Chinese");