diff --git a/assets/i18n/de.yaml b/assets/i18n/de.yaml new file mode 100644 index 000000000..c885e5ca0 --- /dev/null +++ b/assets/i18n/de.yaml @@ -0,0 +1,433 @@ +# Oak Video Editor UI strings — Deutsch (de) +# Format: "i18n.key": "Translated text". Add new languages as new files named .yaml. +"menu.file": "Datei(D)" +"menu.edit": "Bearbeiten(B)" +"menu.view": "Ansicht(A)" +"menu.playback": "Wiedergabe(W)" +"menu.sequence": "Sequenz(S)" +"menu.window": "Fenster(F)" +"menu.tools": "Werkzeuge(W)" +"menu.help": "Hilfe(H)" +"menu.file.new": "Neu" +"menu.file.new_project": "Neues Projekt…" +"menu.file.new_sequence": "Neue Sequenz…" +"menu.file.new_folder": "Neuer Ordner" +"menu.file.open_project": "Projektdatei öffnen…" +"menu.file.open_library": "Aus Bibliothek öffnen…" +"menu.file.open_recent": "Zuletzt geöffnet" +"menu.file.clear_recent": "Liste der zuletzt geöffneten Dateien löschen" +"menu.file.project_manager": "Projektmanager…" +"menu.file.import_footage": "Material importieren…" +"menu.file.export_project": "Projektdatei exportieren…" +"menu.file.save_as": "Speichern unter…" +"menu.file.revert": "Wiederherstellen" +"menu.file.export_media": "Medien exportieren…" +"menu.file.project_properties": "Projekteigenschaften…" +"menu.file.close": "Projekt schließen" +"menu.file.quit": "Beenden" +"menu.edit.undo": "Rückgängig" +"menu.edit.redo": "Wiederholen" +"menu.edit.cut": "Ausschneiden" +"menu.edit.copy": "Kopieren" +"menu.edit.paste": "Einfügen" +"menu.edit.paste_insert": "Insert einfügen" +"menu.edit.duplicate": "Duplizieren" +"menu.edit.rename": "Umbenennen" +"menu.edit.delete": "Löschen" +"menu.edit.ripple_delete": "Ripple-Löschen" +"menu.edit.split": "Am Abspielkopf teilen" +"menu.edit.speed_duration": "Geschwindigkeit/Dauer…" +"menu.edit.default_transition": "Als Standardübergang festlegen" +"menu.edit.link_unlink": "Verknüpfen/Verknüpfung aufheben" +"menu.edit.enable_disable": "Aktivieren/Deaktivieren" +"menu.edit.nest": "Verschachteln" +"menu.edit.select_all": "Alles auswählen" +"menu.edit.deselect_all": "Auswahl aufheben" +"menu.edit.insert": "Einsetzen" +"menu.edit.overwrite": "Überschreiben" +"menu.edit.ripple_to_in": "Ripple zum Einstiegspunkt" +"menu.edit.ripple_to_out": "Ripple zum Ausstiegspunkt" +"menu.edit.edit_to_in": "Bis zum Einstiegspunkt bearbeiten" +"menu.edit.edit_to_out": "Bis zum Ausstiegspunkt bearbeiten" +"menu.edit.nudge_left": "Nach links schieben" +"menu.edit.nudge_right": "Nach rechts schieben" +"menu.edit.move_in_to_playhead": "Einstiegspunkt zum Abspielkopf verschieben" +"menu.edit.move_out_to_playhead": "Ausstiegspunkt zum Abspielkopf verschieben" +"menu.edit.set_in_point": "Einstiegspunkt setzen" +"menu.edit.set_out_point": "Ausstiegspunkt setzen" +"menu.edit.reset_in": "Einstiegspunkt zurücksetzen" +"menu.edit.reset_out": "Ausstiegspunkt zurücksetzen" +"menu.edit.clear_in_out": "Ein/Aus löschen" +"menu.edit.delete_in_out": "Von Einstieg bis Ausstieg löschen" +"menu.edit.ripple_delete_in_out": "Ripple-Löschen von Einstieg bis Ausstieg" +"menu.edit.marker": "Marker hinzufügen" +"menu.view.theme": "Design" +"menu.view.theme.dark": "Olive Dark" +"menu.view.theme.light": "Olive Light" +"menu.view.language": "Sprache" +"language.name": "Deutsch" +"menu.view.zoom_in": "Vergrößern" +"menu.view.zoom_out": "Verkleinern" +"menu.view.increase_track_height": "Spurhöhe erhöhen" +"menu.view.decrease_track_height": "Spurhöhe verringern" +"menu.view.show_all": "Alle Spuren anzeigen" +"menu.view.full_screen": "Vollbild" +"menu.view.full_screen_viewer": "Viewer im Vollbild" +"menu.view.preferences": "Einstellungen…" +"menu.playback.play_pause": "Wiedergabe/Pause" +"menu.playback.play_in_to_out": "Von Ein bis Aus abspielen" +"menu.playback.loop": "Schleife" +"menu.playback.shuttle_left": "Shuttle links" +"menu.playback.shuttle_stop": "Shuttle anhalten" +"menu.playback.shuttle_right": "Shuttle rechts" +"menu.playback.prev_frame": "Vorheriges Einzelbild" +"menu.playback.next_frame": "Nächstes Einzelbild" +"menu.playback.to_start": "Zum Sequenzanfang springen" +"menu.playback.go_to_end": "Zum Sequenzende springen" +"menu.playback.prev_cut": "Zum vorherigen Schnitt springen" +"menu.playback.next_cut": "Zum nächsten Schnitt springen" +"menu.playback.go_to_in": "Zum Einstiegspunkt springen" +"menu.playback.go_to_out": "Zum Ausstiegspunkt springen" +"menu.sequence.add_video_track": "Videospur hinzufügen" +"menu.sequence.add_audio_track": "Audiospur hinzufügen" +"menu.sequence.remove_track": "Ausgewählte Spur entfernen" +"menu.sequence.remove_marker": "Marker entfernen" +"menu.sequence.set_workarea": "Arbeitsbereich festlegen" +"menu.sequence.clear_workarea": "Arbeitsbereich löschen" +"menu.sequence.cache": "Render-Cache" +"menu.sequence.cache_in_out": "Render-Cache von Ein bis Aus" +"menu.sequence.cache_clear": "Render-Cache löschen" +"menu.sequence.settings": "Sequenzeinstellungen…" +"menu.window.project": "Projekt" +"menu.window.source_viewer": "Quell-Viewer" +"menu.window.program_viewer": "Programm-Viewer" +"menu.window.node_editor": "Node-Editor" +"menu.window.inspector": "Inspektor" +"menu.window.history": "Verlauf" +"menu.window.timeline": "Zeitleiste" +"menu.window.effect_library": "Effektbibliothek" +"menu.window.multicam": "Multikamera" +"menu.window.maximize_panel": "Panel maximieren" +"menu.window.reset_layout": "Layout zurücksetzen" +"menu.tools.pointer": "Zeiger" +"menu.tools.track_select": "Spur auswählen" +"menu.tools.edit": "Bearbeiten" +"menu.tools.ripple": "Ripple-Bearbeitung" +"menu.tools.rolling": "Rolling-Bearbeitung" +"menu.tools.razor_tool": "Rasierer" +"menu.tools.slip": "Slip-Bearbeitung" +"menu.tools.slide": "Slide-Bearbeitung" +"menu.tools.hand": "Hand" +"menu.tools.zoom_tool": "Zoom-Werkzeug" +"menu.tools.transition": "Übergang" +"menu.tools.add": "Hinzufügen" +"menu.tools.record": "Aufnehmen" +"menu.tools.addable.empty": "Leerer Clip" +"menu.tools.addable.bars": "Farbbalken" +"menu.tools.addable.shape": "Form" +"menu.tools.addable.solid": "Einfarbig" +"menu.tools.addable.title": "Titel" +"menu.tools.addable.tone": "Testton" +"menu.tools.addable.subtitle": "Untertitel" +"menu.tools.snapping": "Einrasten" +"menu.tools.use_proxy": "Proxy-Medien verwenden" +"menu.tools.proxy_settings": "Proxy-Einstellungen…" +"menu.help.action_search": "Aktionen durchsuchen…" +"menu.help.feedback": "Feedback senden…" +"menu.help.about": "Über Oak…" +"shortcut.space": "Leertaste" +"panel.project": "Projekt" +"panel.source_viewer": "Quell-Viewer" +"panel.program_viewer": "Programm-Viewer" +"panel.node_editor": "Node-Editor" +"panel.inspector": "Inspektor" +"panel.history": "Verlauf" +"panel.timeline": "Zeitleiste" +"panel.effect_library": "Effektbibliothek" +"panel.multicam": "Multikamera" +"multicam.no_multicam": "Kein Multikamera-Clip erkannt" +"multicam.switch_1": "Zu Kamera 1 wechseln" +"multicam.switch_2": "Zu Kamera 2 wechseln" +"multicam.switch_3": "Zu Kamera 3 wechseln" +"multicam.switch_4": "Zu Kamera 4 wechseln" +"multicam.switch_5": "Zu Kamera 5 wechseln" +"multicam.switch_6": "Zu Kamera 6 wechseln" +"multicam.switch_7": "Zu Kamera 7 wechseln" +"multicam.switch_8": "Zu Kamera 8 wechseln" +"multicam.switch_9": "Zu Kamera 9 wechseln" +"effect_library.hint": "Doppelklicken, um zum ausgewählten Clip hinzuzufügen" +"effect_library.group.builtin": "Integriert" +"status.ready": "Bereit" +"status.cache": "Cache: Aktiviert" +"status.proxy.on": "Proxy: Ein" +"status.proxy.off": "Proxy: Aus" +"status.storage.written": "Bibliothek: gespeichert" +"status.storage.unbound": "Bibliothek: aus" +"status.storage.error": "Bibliothek: Schreibfehler" +"status.untitled": "Unbenanntes Projekt" +"status.backend": "Render-Engine:" +"manager.title": "Projektmanager" +"manager.new": "Neues Projekt" +"manager.new.default_name": "Unbenanntes Projekt" +"manager.open": "Öffnen" +"manager.rename": "Umbenennen…" +"manager.rename.title": "Projekt umbenennen" +"manager.rename.label": "Neuer Name" +"manager.duplicate": "Duplizieren" +"manager.delete": "Löschen" +"manager.delete.title": "Projekt löschen" +"manager.delete.confirm": "Projekt „{name}“ aus der Bibliothek löschen? Dieser Vorgang kann nicht rückgängig gemacht werden." +"manager.import": "Importieren…" +"manager.import.title": "Projekt importieren" +"manager.export": "Exportieren…" +"manager.export.title": "Projekt exportieren" +"manager.col.name": "Name" +"manager.col.modified": "Geändert" +"manager.col.duration": "Dauer" +"manager.col.tracks": "Spuren" +"manager.col.clips": "Clips" +"manager.col.footage": "Material" +"manager.empty": "Noch keine Projekte in der Bibliothek." +"timeline.tool.select": "Auswahl" +"timeline.tool.razor": "Rasierer" +"timeline.tool.ripple": "Ripple-Bearbeitung" +"timeline.tool.slip": "Slip-Bearbeitung" +"timeline.tool.roll": "Rolling-Bearbeitung" +"timeline.tool.zoom": "Zoom-Werkzeug" +"timeline.tool.slide": "Slide-Bearbeitung" +"timeline.tool.track_select": "Spur auswählen" +"timeline.zoom": "Zoomen" +"timeline.zoom_in": "Vergrößern" +"timeline.zoom_out": "Verkleinern" +"timeline.track_height": "Spurhöhe" +"timeline.snap": "Einrasten" +"timeline.add_video_track": "Videospur hinzufügen" +"timeline.add_audio_track": "Audiospur hinzufügen" +"bin.footage": "Material" +"bin.music": "Musik" +"history.command": "Befehl" +"history.empty": "Kein Verlauf" +"history.jump_here": "Zu diesem Schritt springen" +"node.fit": "Anpassen" +"viewer.picture": "Bild" +"viewer.scopes": "Oszilloskope" +"scope.histogram": "Histogramm" +"scope.waveform": "Wellenform" +"scope.vectorscope": "Vektorskop" +"viewer.in_point": "Einstiegspunkt setzen" +"viewer.step_back": "Vorheriges Einzelbild" +"viewer.play": "Abspielen" +"viewer.pause": "Pause" +"viewer.step_forward": "Nächstes Einzelbild" +"viewer.out_point": "Ausstiegspunkt setzen" +"viewer.clear_range": "Ein/Aus-Bereich löschen" +"explorer.tree": "Baum" +"explorer.icons": "Symbole" +"viewer.safe_frames": "Sichere Ränder" +"viewer.zoom": "Zoomen" +"viewer.no_frame_source": "Keine Bildquelle" +"effect_stack.empty": "Keine Auswahl" +"effect_stack.add": "+ Effekt hinzufügen" +"inspector.params": "Keine bearbeitbaren Parameter" +"inspector.add_effect": "Effekt hinzufügen" +"inspector.badge.openfx": "OpenFX" +"ofx.progress.title": "OpenFX-Plugin-Fortschritt" +"ofx.color.hex": "Hexadezimal" +"ofx.color.ok": "Okay" +"ofx.color.cancel": "Abbrechen" +"ofx.color.invalid": "Ungültige Hexadezimalfarbe" +"dialog.cancel": "Abbrechen" +"dialog.close": "Schließen" +"file.open.title": "Projekt öffnen" +"file.import_footage.title": "Material importieren" +"preferences.title": "Einstellungen" +"preferences.section.general": "Allgemein" +"preferences.section.render": "Rendering" +"preferences.section.cache": "Cache" +"preferences.section.proxy": "Proxy" +"preferences.section.color": "Farbe" +"preferences.color.enable": "Farbmanagement anzeigen (Monitor-ICC)" +"preferences.color.custom": "Benutzerdefiniertes ICC-Profil" +"preferences.color.browse": "Durchsuchen…" +"preferences.color.restart_hint": "Eine Änderung des Modus wird nach einem Neustart wirksam." +"preferences.section.project": "Projekt" +"preferences.section.audio": "Audio" +"preferences.backend": "Renderer-Backend" +"preferences.hwdecode.enable": "Hardwarebeschleunigte Videodekodierung (VideoToolbox / VA-API / NVDEC / D3D11VA)" +"preferences.backend.placeholder": "Backend auswählen…" +"preferences.language": "Sprache" +"preferences.language.placeholder": "Sprache auswählen…" +"preferences.theme": "Design" +"preferences.theme.dark": "Olive Dark" +"preferences.theme.light": "Olive Light" +"preferences.cache.dir": "Verzeichnis für den Platten-Cache" +"preferences.cache.browse": "Durchsuchen…" +"preferences.proxy.enable": "Proxy-Medien verwenden" +"preferences.proxy.resolution": "Proxy-Auflösung" +"preferences.proxy.full": "Volle Auflösung" +"preferences.snapshot.interval": "Intervall der automatischen Speicherung (Momentaufnahme) in Sekunden" +"preferences.transition.default": "Standardlänge von Übergängen in Sekunden" +"preferences.audio.output": "Audioausgabegerät" +"preferences.audio.output.placeholder": "Ausgabegerät auswählen…" +"preferences.audio.input": "Audioeingabegerät" +"preferences.audio.input.placeholder": "Eingabegerät auswählen…" +"preferences.audio.default": "Systemstandard" +"preferences.hint": "Das Renderer-Backend wird beim nächsten Start auf den Render-Worker angewendet; alle anderen Einstellungen wirken sofort und werden beim Beenden gespeichert." +"preferences.tab.general": "Allgemein" +"preferences.tab.keyboard": "Tastatur" +"preferences.section.keyboard": "Tastatur" +"preferences.keyboard.search": "Nach Aktion oder Tastenkürzel suchen" +"preferences.keyboard.action": "Aktion" +"preferences.keyboard.shortcut": "Tastenkürzel" +"preferences.keyboard.click_to_edit": "Zum Festlegen des Tastenkürzels klicken…" +"preferences.keyboard.capturing": "Tasten drücken… (Esc zum Abbrechen)" +"preferences.keyboard.unbound": "Keine" +"preferences.keyboard.import": "Importieren…" +"preferences.keyboard.export": "Exportieren…" +"preferences.keyboard.reset_selected": "Auswahl zurücksetzen" +"preferences.keyboard.reset_all": "Alle zurücksetzen" +"preferences.keyboard.reset_all.confirm": "Möchten Sie wirklich alle Tastenkürzel auf die Standardwerte zurücksetzen?" +"preferences.keyboard.conflict": "Das Tastenkürzel ist bereits an {action} gebunden; die Zuordnung wurde verschoben." +"preferences.keyboard.cleared": "Tastenkürzel gelöscht (Aktion nicht mehr zugeordnet)." +"preferences.keyboard.reset": "Tastenkürzel auf den Standardwert zurückgesetzt." +"preferences.keyboard.reset_all_done": "Alle Tastenkürzel wurden auf die Standardwerte zurückgesetzt." +"preferences.keyboard.imported": "Tastenkürzel erfolgreich importiert." +"preferences.keyboard.exported": "Tastenkürzel erfolgreich exportiert." +"preferences.keyboard.import_failed": "Die Datei konnte nicht zum Lesen geöffnet werden." +"preferences.keyboard.export_failed": "Die Datei konnte nicht zum Schreiben geöffnet werden." +"actionsearch.search_placeholder": "Nach Aktion suchen…" +"actionsearch.empty": "Keine passenden Aktionen" +"actionsearch.no_actions": "Keine Aktionen verfügbar" +"export.title": "Sequenz exportieren" +"export.format": "Format" +"export.format.placeholder": "Format auswählen…" +"export.path": "Ausgabepfad" +"export.run": "Exportieren" +"export.hint": "Die Sequenz wird über den oaktask-Exportpfad exportiert; der Fortschritt wird im Dialog angezeigt." +"export.progress.title": "Exportieren" +"export.progress.label": "Frames werden gerendert…" +"proxydialog.title": "Proxy-Einstellungen" +"proxydialog.footage_group": "Material" +"proxydialog.no_footage": "Kein Material im Projekt" +"proxydialog.custom": "Benutzerdefinierte Einstellungen für das Material verwenden" +"proxydialog.custom_suffix": " (benutzerdefinierte Einstellungen)" +"proxydialog.global": "Globale Proxy-Einstellungen" +"proxydialog.resolution": "Proxy-Auflösung" +"proxydialog.resolution.custom": "Benutzerdefinierte Größe" +"proxydialog.resolution.half": "1/2 der Quelle" +"proxydialog.resolution.quarter": "1/4 der Quelle" +"proxydialog.resolution.eighth": "1/8 der Quelle" +"proxydialog.width": "Proxy-Breite" +"proxydialog.height": "Proxy-Höhe" +"proxydialog.crf": "Proxy-CRF" +"proxydialog.preset": "Proxy-Preset" +"proxydialog.include_audio": "Audio in Proxys einschließen" +"proxydialog.ffmpeg": "ffmpeg-Programm" +"proxydialog.generate": "Proxys generieren" +"proxydialog.delete": "Proxys löschen" +"proxydialog.close": "Schließen" +"proxydialog.state.missing": "Fehlt" +"proxydialog.state.generating": "Wird generiert" +"proxydialog.state.ready": "Bereit" +"proxydialog.state.failed": "Fehlgeschlagen" +"menu.color.label": "Farbkennzeichnung" +"menu.color.red": "Rot" +"menu.color.maroon": "Kastanienbraun" +"menu.color.orange": "Orange" +"menu.color.brown": "Braun" +"menu.color.yellow": "Gelb" +"menu.color.oak": "Eiche" +"menu.color.lime": "Limette" +"menu.color.green": "Grün" +"menu.color.cyan": "Cyan" +"menu.color.teal": "Blaugrün" +"menu.color.blue": "Blau" +"menu.color.navy": "Marineblau" +"menu.color.pink": "Rosa" +"menu.color.purple": "Violett" +"menu.color.silver": "Silber" +"menu.color.gray": "Grau" +"menu.context.properties": "Eigenschaften" +"timeline.context.sync_source_time": "Nach Quellzeit synchronisieren" +"timeline.context.sync_waveform": "Nach Wellenform synchronisieren" +"timeline.context.sync_waveform_speed": "Nach Wellenform synchronisieren (Geschwindigkeit anpassen)" +"timeline.context.cache": "Cache" +"timeline.context.auto_cache": "Automatischer Cache" +"timeline.context.cache_all": "Alle zwischenspeichern" +"timeline.context.cache_in_out": "Ein/Aus-Bereich zwischenspeichern" +"timeline.context.cache_discard": "Verwerfen" +"timeline.context.proxy": "Proxy" +"timeline.context.generate_proxy": "Proxy generieren" +"timeline.context.use_proxy": "Proxy verwenden" +"timeline.context.reveal_proxy": "Proxy anzeigen" +"timeline.context.delete_proxy": "Proxy löschen" +"timeline.context.reveal_in_footage_viewer": "Im Material-Viewer anzeigen" +"timeline.context.reveal_in_project": "Im Projekt anzeigen" +"timeline.context.multicam": "Multikamera" +"timeline.context.use_audio_time_units": "Audiozeiteinheiten verwenden" +"timeline.context.show_thumbnails": "Miniaturen anzeigen" +"timeline.context.thumbnails_off": "Deaktiviert" +"timeline.context.thumbnails_at_in_points": "Nur an Einstiegspunkten" +"timeline.context.thumbnails_on": "Aktiviert" +"timeline.context.show_waveforms": "Wellenformen anzeigen" +"timeline.context.add_video_track": "Videospur hinzufügen" +"timeline.context.add_audio_track": "Audiospur hinzufügen" +"timeline.context.delete_track": "Löschen" +"timeline.context.delete_all_empty": "Alle leeren Spuren löschen" +"timeline.context.timecode_drop_frame": "Drop-Frame" +"timeline.context.timecode_non_drop_frame": "Non-Drop-Frame" +"timeline.context.timecode_seconds": "Sekunden" +"timeline.context.timecode_frames": "Einzelbilder" +"timeline.context.timecode_milliseconds": "Millisekunden" +"node.category.output": "Ausgabe" +"node.category.effect": "Effekt" +"node.category.generator": "Erzeuger" +"node.category.input": "Eingabe" +"node.category.math": "Mathematik" +"node.category.color": "Farbe" +"node.category.distort": "Verzerren" +"node.category.filter": "Filtern" +"node.category.keying": "Freistellen" +"node.category.openfx": "OpenFX" +"node.category.group": "Gruppe" +"project.context.new": "Neu" +"project.context.reveal_in_finder": "Im Finder anzeigen" +"project.context.replace_footage": "Material ersetzen" +"project.context.rename": "Umbenennen" +"project.context.delete": "Löschen" +"project.context.open_in_new_tab": "In neuem Tab öffnen" +"project.context.open_in_new_window": "In neuem Fenster öffnen" +"viewer.context.zoom": "Zoomen" +"viewer.context.zoom_fit": "Anpassen" +"viewer.context.full_screen": "Vollbild" +"viewer.context.playback_resolution": "Wiedergabeauflösung" +"viewer.context.res_full": "Voll" +"viewer.context.res_half": "1/2" +"viewer.context.res_quarter": "1/4" +"viewer.context.res_eighth": "1/8" +"viewer.context.safe_margins": "Sichere Ränder" +"viewer.context.safe_off": "Aus" +"viewer.context.safe_on": "Ein" +"viewer.context.safe_custom": "Benutzerdefiniertes Seitenverhältnis" +"viewer.context.stop_on_last": "Wiedergabe am letzten Einzelbild anhalten" +"viewer.context.audio_waveform": "Audiowellenform" +"viewer.context.wf_automatic": "Automatisch ein-/ausblenden" +"viewer.context.wf_only": "Nur Wellenform anzeigen" +"viewer.context.wf_both": "Viewer und Wellenform anzeigen" +"viewer.context.show_fps": "FPS anzeigen" +"viewer.context.save_frame": "Einzelbild als Bild speichern" +"node.context.group": "Gruppieren" +"node.context.ungroup": "Gruppierung aufheben" +"node.context.open_in_viewer": "Im Viewer öffnen" +"node.context.show_in_param_editor": "Im Parametereditor anzeigen" +"node.context.smooth_edges": "Kanten glätten" +"node.context.direction": "Richtung" +"node.context.dir_top_bottom": "Von oben nach unten" +"node.context.dir_bottom_top": "Von unten nach oben" +"node.context.dir_left_right": "Von links nach rechts" +"node.context.dir_right_left": "Von rechts nach links" +"node.context.add": "Hinzufügen" +"inspector.context.enable": "Aktivieren" +"inspector.context.disable": "Deaktivieren" +"inspector.context.remove": "Entfernen" +"inspector.context.rename": "Umbenennen" diff --git a/assets/i18n/en.yaml b/assets/i18n/en-US.yaml similarity index 99% rename from assets/i18n/en.yaml rename to assets/i18n/en-US.yaml index 54add5061..98c12829d 100644 --- a/assets/i18n/en.yaml +++ b/assets/i18n/en-US.yaml @@ -65,8 +65,7 @@ "menu.view.theme.dark": "Olive Dark" "menu.view.theme.light": "Olive Light" "menu.view.language": "Language" -"menu.view.language.en": "English" -"menu.view.language.zh": "简体中文" +"language.name": "English" "menu.view.zoom_in": "Zoom In" "menu.view.zoom_out": "Zoom Out" "menu.view.increase_track_height": "Increase Track Height" diff --git a/assets/i18n/es.yaml b/assets/i18n/es.yaml new file mode 100644 index 000000000..db7f71313 --- /dev/null +++ b/assets/i18n/es.yaml @@ -0,0 +1,433 @@ +# Oak Video Editor UI strings — Español (es) +# Format: "i18n.key": "Translated text". Add new languages as new files named .yaml. +"menu.file": "Archivo(A)" +"menu.edit": "Editar(E)" +"menu.view": "Ver(V)" +"menu.playback": "Reproducción(R)" +"menu.sequence": "Secuencia(S)" +"menu.window": "Ventana(V)" +"menu.tools": "Herramientas(H)" +"menu.help": "Ayuda(A)" +"menu.file.new": "Nuevo" +"menu.file.new_project": "Nuevo proyecto…" +"menu.file.new_sequence": "Nueva secuencia…" +"menu.file.new_folder": "Nueva carpeta" +"menu.file.open_project": "Abrir archivo de proyecto…" +"menu.file.open_library": "Abrir desde la biblioteca…" +"menu.file.open_recent": "Abrir recientes" +"menu.file.clear_recent": "Borrar lista de recientes" +"menu.file.project_manager": "Administrador de proyectos…" +"menu.file.import_footage": "Importar material…" +"menu.file.export_project": "Exportar archivo de proyecto…" +"menu.file.save_as": "Guardar como…" +"menu.file.revert": "Revertir" +"menu.file.export_media": "Exportar medios…" +"menu.file.project_properties": "Propiedades del proyecto…" +"menu.file.close": "Cerrar proyecto" +"menu.file.quit": "Salir" +"menu.edit.undo": "Deshacer" +"menu.edit.redo": "Rehacer" +"menu.edit.cut": "Cortar" +"menu.edit.copy": "Copiar" +"menu.edit.paste": "Pegar" +"menu.edit.paste_insert": "Pegar como inserción" +"menu.edit.duplicate": "Duplicar" +"menu.edit.rename": "Cambiar nombre" +"menu.edit.delete": "Eliminar" +"menu.edit.ripple_delete": "Eliminación en cadena" +"menu.edit.split": "Dividir en el cabezal" +"menu.edit.speed_duration": "Velocidad/Duración…" +"menu.edit.default_transition": "Establecer como transición predeterminada" +"menu.edit.link_unlink": "Vincular/Desvincular" +"menu.edit.enable_disable": "Activar/Desactivar" +"menu.edit.nest": "Anidar" +"menu.edit.select_all": "Seleccionar todo" +"menu.edit.deselect_all": "Deseleccionar todo" +"menu.edit.insert": "Insertar" +"menu.edit.overwrite": "Sobrescribir" +"menu.edit.ripple_to_in": "Ondulación hasta la entrada" +"menu.edit.ripple_to_out": "Ondulación hasta la salida" +"menu.edit.edit_to_in": "Editar hasta la entrada" +"menu.edit.edit_to_out": "Editar hasta la salida" +"menu.edit.nudge_left": "Desplazar a la izquierda" +"menu.edit.nudge_right": "Desplazar a la derecha" +"menu.edit.move_in_to_playhead": "Mover el punto de entrada al cabezal" +"menu.edit.move_out_to_playhead": "Mover el punto de salida al cabezal" +"menu.edit.set_in_point": "Establecer punto de entrada" +"menu.edit.set_out_point": "Establecer punto de salida" +"menu.edit.reset_in": "Restablecer punto de entrada" +"menu.edit.reset_out": "Restablecer punto de salida" +"menu.edit.clear_in_out": "Borrar entrada/salida" +"menu.edit.delete_in_out": "Eliminar de entrada a salida" +"menu.edit.ripple_delete_in_out": "Eliminación en cadena de entrada a salida" +"menu.edit.marker": "Añadir marcador" +"menu.view.theme": "Tema" +"menu.view.theme.dark": "Olive Dark" +"menu.view.theme.light": "Olive Light" +"menu.view.language": "Idioma" +"language.name": "Español" +"menu.view.zoom_in": "Acercar" +"menu.view.zoom_out": "Alejar" +"menu.view.increase_track_height": "Aumentar altura de pista" +"menu.view.decrease_track_height": "Reducir altura de pista" +"menu.view.show_all": "Mostrar todas las pistas" +"menu.view.full_screen": "Pantalla completa" +"menu.view.full_screen_viewer": "Visor a pantalla completa" +"menu.view.preferences": "Preferencias…" +"menu.playback.play_pause": "Reproducir/Pausar" +"menu.playback.play_in_to_out": "Reproducir de entrada a salida" +"menu.playback.loop": "Bucle" +"menu.playback.shuttle_left": "Lanzadera a la izquierda" +"menu.playback.shuttle_stop": "Detener lanzadera" +"menu.playback.shuttle_right": "Lanzadera a la derecha" +"menu.playback.prev_frame": "Fotograma anterior" +"menu.playback.next_frame": "Fotograma siguiente" +"menu.playback.to_start": "Ir al inicio de la secuencia" +"menu.playback.go_to_end": "Ir al final de la secuencia" +"menu.playback.prev_cut": "Ir al corte anterior" +"menu.playback.next_cut": "Ir al corte siguiente" +"menu.playback.go_to_in": "Ir al punto de entrada" +"menu.playback.go_to_out": "Ir al punto de salida" +"menu.sequence.add_video_track": "Añadir pista de vídeo" +"menu.sequence.add_audio_track": "Añadir pista de audio" +"menu.sequence.remove_track": "Eliminar pista seleccionada" +"menu.sequence.remove_marker": "Quitar marcador" +"menu.sequence.set_workarea": "Establecer área de trabajo" +"menu.sequence.clear_workarea": "Borrar área de trabajo" +"menu.sequence.cache": "Caché de renderizado" +"menu.sequence.cache_in_out": "Caché de renderizado de entrada a salida" +"menu.sequence.cache_clear": "Borrar caché de renderizado" +"menu.sequence.settings": "Configuración de secuencia…" +"menu.window.project": "Proyecto" +"menu.window.source_viewer": "Visor de origen" +"menu.window.program_viewer": "Visor de programa" +"menu.window.node_editor": "Editor de nodos" +"menu.window.inspector": "Inspector" +"menu.window.history": "Historial" +"menu.window.timeline": "Línea de tiempo" +"menu.window.effect_library": "Biblioteca de efectos" +"menu.window.multicam": "Multicámara" +"menu.window.maximize_panel": "Maximizar panel" +"menu.window.reset_layout": "Restablecer distribución" +"menu.tools.pointer": "Puntero" +"menu.tools.track_select": "Selección de pista" +"menu.tools.edit": "Edición" +"menu.tools.ripple": "Ondulación" +"menu.tools.rolling": "Rodadura" +"menu.tools.razor_tool": "Navaja" +"menu.tools.slip": "Deslizamiento" +"menu.tools.slide": "Desplazamiento" +"menu.tools.hand": "Mano" +"menu.tools.zoom_tool": "Zoom" +"menu.tools.transition": "Transición" +"menu.tools.add": "Añadir" +"menu.tools.record": "Grabar" +"menu.tools.addable.empty": "Clip vacío" +"menu.tools.addable.bars": "Barras de color" +"menu.tools.addable.shape": "Forma" +"menu.tools.addable.solid": "Color sólido" +"menu.tools.addable.title": "Título" +"menu.tools.addable.tone": "Tono de prueba" +"menu.tools.addable.subtitle": "Subtítulo" +"menu.tools.snapping": "Ajuste" +"menu.tools.use_proxy": "Usar medios proxy" +"menu.tools.proxy_settings": "Configuración de proxy…" +"menu.help.action_search": "Buscar acciones…" +"menu.help.feedback": "Enviar comentarios…" +"menu.help.about": "Acerca de Oak…" +"shortcut.space": "Barra espaciadora" +"panel.project": "Proyecto" +"panel.source_viewer": "Visor de origen" +"panel.program_viewer": "Visor de programa" +"panel.node_editor": "Editor de nodos" +"panel.inspector": "Inspector" +"panel.history": "Historial" +"panel.timeline": "Línea de tiempo" +"panel.effect_library": "Biblioteca de efectos" +"panel.multicam": "Multicámara" +"multicam.no_multicam": "No se ha detectado ningún clip multicámara" +"multicam.switch_1": "Cambiar a cámara 1" +"multicam.switch_2": "Cambiar a cámara 2" +"multicam.switch_3": "Cambiar a cámara 3" +"multicam.switch_4": "Cambiar a cámara 4" +"multicam.switch_5": "Cambiar a cámara 5" +"multicam.switch_6": "Cambiar a cámara 6" +"multicam.switch_7": "Cambiar a cámara 7" +"multicam.switch_8": "Cambiar a cámara 8" +"multicam.switch_9": "Cambiar a cámara 9" +"effect_library.hint": "Haz doble clic para añadir al clip seleccionado" +"effect_library.group.builtin": "Integradas" +"status.ready": "Listo" +"status.cache": "Caché: activada" +"status.proxy.on": "Proxy: activado" +"status.proxy.off": "Proxy: desactivado" +"status.storage.written": "Biblioteca: guardada" +"status.storage.unbound": "Biblioteca: desactivada" +"status.storage.error": "Biblioteca: error de escritura" +"status.untitled": "Proyecto sin título" +"status.backend": "Motor:" +"manager.title": "Administrador de proyectos" +"manager.new": "Nuevo proyecto" +"manager.new.default_name": "Proyecto sin título" +"manager.open": "Abrir" +"manager.rename": "Cambiar nombre…" +"manager.rename.title": "Cambiar nombre del proyecto" +"manager.rename.label": "Nuevo nombre" +"manager.duplicate": "Duplicar" +"manager.delete": "Eliminar" +"manager.delete.title": "Eliminar proyecto" +"manager.delete.confirm": "¿Eliminar el proyecto «{name}» de la biblioteca? Esta acción no se puede deshacer." +"manager.import": "Importar…" +"manager.import.title": "Importar proyecto" +"manager.export": "Exportar…" +"manager.export.title": "Exportar proyecto" +"manager.col.name": "Nombre" +"manager.col.modified": "Modificado" +"manager.col.duration": "Duración" +"manager.col.tracks": "Pistas" +"manager.col.clips": "Clips" +"manager.col.footage": "Material" +"manager.empty": "Aún no hay proyectos en la biblioteca." +"timeline.tool.select": "Selección" +"timeline.tool.razor": "Navaja" +"timeline.tool.ripple": "Ondulación" +"timeline.tool.slip": "Deslizamiento" +"timeline.tool.roll": "Rodadura" +"timeline.tool.zoom": "Zoom" +"timeline.tool.slide": "Desplazamiento" +"timeline.tool.track_select": "Selección de pista" +"timeline.zoom": "Zoom" +"timeline.zoom_in": "Acercar" +"timeline.zoom_out": "Alejar" +"timeline.track_height": "Altura de pista" +"timeline.snap": "Ajuste" +"timeline.add_video_track": "Añadir pista de vídeo" +"timeline.add_audio_track": "Añadir pista de audio" +"bin.footage": "Material" +"bin.music": "Música" +"history.command": "Comando" +"history.empty": "Sin historial" +"history.jump_here": "Saltar a este paso" +"node.fit": "Ajustar" +"viewer.picture": "Imagen" +"viewer.scopes": "Osciloscopios" +"scope.histogram": "Histograma" +"scope.waveform": "Forma de onda" +"scope.vectorscope": "Vectorscopio" +"viewer.in_point": "Establecer punto de entrada" +"viewer.step_back": "Fotograma anterior" +"viewer.play": "Reproducir" +"viewer.pause": "Pausar" +"viewer.step_forward": "Fotograma siguiente" +"viewer.out_point": "Establecer punto de salida" +"viewer.clear_range": "Borrar rango entrada/salida" +"explorer.tree": "Árbol" +"explorer.icons": "Iconos" +"viewer.safe_frames": "Marcos de seguridad" +"viewer.zoom": "Zoom" +"viewer.no_frame_source": "Sin fuente de fotogramas" +"effect_stack.empty": "Sin selección" +"effect_stack.add": "+ Añadir efecto" +"inspector.params": "No hay parámetros editables" +"inspector.add_effect": "Añadir efecto" +"inspector.badge.openfx": "OpenFX" +"ofx.progress.title": "Progreso del complemento OpenFX" +"ofx.color.hex": "Hexadecimal" +"ofx.color.ok": "Aceptar" +"ofx.color.cancel": "Cancelar" +"ofx.color.invalid": "Color hexadecimal no válido" +"dialog.cancel": "Cancelar" +"dialog.close": "Cerrar" +"file.open.title": "Abrir proyecto" +"file.import_footage.title": "Importar material" +"preferences.title": "Preferencias" +"preferences.section.general": "General" +"preferences.section.render": "Renderizado" +"preferences.section.cache": "Caché" +"preferences.section.proxy": "Proxy" +"preferences.section.color": "Color" +"preferences.color.enable": "Mostrar gestión de color (ICC de pantalla)" +"preferences.color.custom": "Perfil ICC personalizado" +"preferences.color.browse": "Examinar…" +"preferences.color.restart_hint": "Un cambio de modo surte efecto tras reiniciar." +"preferences.section.project": "Proyecto" +"preferences.section.audio": "Audio" +"preferences.backend": "Motor de renderizado" +"preferences.hwdecode.enable": "Decodificación de vídeo acelerada por hardware (VideoToolbox / VA-API / NVDEC / D3D11VA)" +"preferences.backend.placeholder": "Seleccionar un motor…" +"preferences.language": "Idioma" +"preferences.language.placeholder": "Seleccionar un idioma…" +"preferences.theme": "Tema" +"preferences.theme.dark": "Olive Dark" +"preferences.theme.light": "Olive Light" +"preferences.cache.dir": "Directorio de caché en disco" +"preferences.cache.browse": "Examinar…" +"preferences.proxy.enable": "Usar medios proxy" +"preferences.proxy.resolution": "Resolución del proxy" +"preferences.proxy.full": "Resolución completa" +"preferences.snapshot.interval": "Intervalo de guardado automático (instantánea), en segundos" +"preferences.transition.default": "Duración predeterminada de las transiciones, en segundos" +"preferences.audio.output": "Dispositivo de salida de audio" +"preferences.audio.output.placeholder": "Seleccionar un dispositivo de salida…" +"preferences.audio.input": "Dispositivo de entrada de audio" +"preferences.audio.input.placeholder": "Seleccionar un dispositivo de entrada…" +"preferences.audio.default": "Predeterminado del sistema" +"preferences.hint": "El motor de renderizado se aplica al trabajador de renderizado en el próximo inicio; el resto de ajustes surten efecto de inmediato y se guardan al salir." +"preferences.tab.general": "General" +"preferences.tab.keyboard": "Teclado" +"preferences.section.keyboard": "Teclado" +"preferences.keyboard.search": "Buscar acción o atajo" +"preferences.keyboard.action": "Acción" +"preferences.keyboard.shortcut": "Atajo" +"preferences.keyboard.click_to_edit": "Haz clic para establecer el atajo…" +"preferences.keyboard.capturing": "Pulsa las teclas… (Esc para cancelar)" +"preferences.keyboard.unbound": "Ninguno" +"preferences.keyboard.import": "Importar…" +"preferences.keyboard.export": "Exportar…" +"preferences.keyboard.reset_selected": "Restablecer selección" +"preferences.keyboard.reset_all": "Restablecer todo" +"preferences.keyboard.reset_all.confirm": "¿Seguro que quieres restablecer todos los atajos de teclado a sus valores predeterminados?" +"preferences.keyboard.conflict": "El atajo ya está asignado a {action}; la asignación se ha movido." +"preferences.keyboard.cleared": "Atajo borrado (acción sin asignar)." +"preferences.keyboard.reset": "Atajo restablecido a su valor predeterminado." +"preferences.keyboard.reset_all_done": "Todos los atajos se han restablecido a sus valores predeterminados." +"preferences.keyboard.imported": "Atajos importados correctamente." +"preferences.keyboard.exported": "Atajos exportados correctamente." +"preferences.keyboard.import_failed": "No se pudo abrir el archivo para leerlo." +"preferences.keyboard.export_failed": "No se pudo abrir el archivo para escribir." +"actionsearch.search_placeholder": "Buscar acción…" +"actionsearch.empty": "No hay acciones coincidentes" +"actionsearch.no_actions": "No hay acciones disponibles" +"export.title": "Exportar secuencia" +"export.format": "Formato" +"export.format.placeholder": "Seleccionar un formato…" +"export.path": "Ruta de salida" +"export.run": "Exportar" +"export.hint": "La secuencia se exporta a través de la ruta de exportación oaktask; el progreso se muestra en el diálogo." +"export.progress.title": "Exportando" +"export.progress.label": "Renderizando fotogramas…" +"proxydialog.title": "Configuración de proxy" +"proxydialog.footage_group": "Material" +"proxydialog.no_footage": "No hay material en el proyecto" +"proxydialog.custom": "Usar configuración personalizada para el material" +"proxydialog.custom_suffix": " (configuración personalizada)" +"proxydialog.global": "Configuración global de proxy" +"proxydialog.resolution": "Resolución del proxy" +"proxydialog.resolution.custom": "Tamaño personalizado" +"proxydialog.resolution.half": "1/2 del origen" +"proxydialog.resolution.quarter": "1/4 del origen" +"proxydialog.resolution.eighth": "1/8 del origen" +"proxydialog.width": "Ancho del proxy" +"proxydialog.height": "Alto del proxy" +"proxydialog.crf": "CRF del proxy" +"proxydialog.preset": "Preajuste del proxy" +"proxydialog.include_audio": "Incluir audio en los proxies" +"proxydialog.ffmpeg": "Ejecutable de ffmpeg" +"proxydialog.generate": "Generar proxies" +"proxydialog.delete": "Eliminar proxies" +"proxydialog.close": "Cerrar" +"proxydialog.state.missing": "Falta" +"proxydialog.state.generating": "Generando" +"proxydialog.state.ready": "Listo" +"proxydialog.state.failed": "Error" +"menu.color.label": "Etiqueta de color" +"menu.color.red": "Rojo" +"menu.color.maroon": "Granate" +"menu.color.orange": "Naranja" +"menu.color.brown": "Marrón" +"menu.color.yellow": "Amarillo" +"menu.color.oak": "Roble" +"menu.color.lime": "Lima" +"menu.color.green": "Verde" +"menu.color.cyan": "Cian" +"menu.color.teal": "Verde azulado" +"menu.color.blue": "Azul" +"menu.color.navy": "Azul marino" +"menu.color.pink": "Rosa" +"menu.color.purple": "Púrpura" +"menu.color.silver": "Plata" +"menu.color.gray": "Gris" +"menu.context.properties": "Propiedades" +"timeline.context.sync_source_time": "Sincronizar por tiempo de origen" +"timeline.context.sync_waveform": "Sincronizar por forma de onda" +"timeline.context.sync_waveform_speed": "Sincronizar por forma de onda (ajustar velocidad)" +"timeline.context.cache": "Caché" +"timeline.context.auto_cache": "Caché automática" +"timeline.context.cache_all": "Almacenar todo en caché" +"timeline.context.cache_in_out": "Almacenar entrada/salida en caché" +"timeline.context.cache_discard": "Descartar" +"timeline.context.proxy": "Proxy" +"timeline.context.generate_proxy": "Generar proxy" +"timeline.context.use_proxy": "Usar proxy" +"timeline.context.reveal_proxy": "Mostrar proxy" +"timeline.context.delete_proxy": "Eliminar proxy" +"timeline.context.reveal_in_footage_viewer": "Mostrar en el visor de material" +"timeline.context.reveal_in_project": "Mostrar en el proyecto" +"timeline.context.multicam": "Multicámara" +"timeline.context.use_audio_time_units": "Usar unidades de tiempo de audio" +"timeline.context.show_thumbnails": "Mostrar miniaturas" +"timeline.context.thumbnails_off": "Desactivadas" +"timeline.context.thumbnails_at_in_points": "Solo en puntos de entrada" +"timeline.context.thumbnails_on": "Activadas" +"timeline.context.show_waveforms": "Mostrar formas de onda" +"timeline.context.add_video_track": "Añadir pista de vídeo" +"timeline.context.add_audio_track": "Añadir pista de audio" +"timeline.context.delete_track": "Eliminar" +"timeline.context.delete_all_empty": "Eliminar todas las pistas vacías" +"timeline.context.timecode_drop_frame": "Drop-frame" +"timeline.context.timecode_non_drop_frame": "Non-drop-frame" +"timeline.context.timecode_seconds": "Segundos" +"timeline.context.timecode_frames": "Fotogramas" +"timeline.context.timecode_milliseconds": "Milisegundos" +"node.category.output": "Salida" +"node.category.effect": "Efecto" +"node.category.generator": "Generador" +"node.category.input": "Entrada" +"node.category.math": "Matemáticas" +"node.category.color": "Color" +"node.category.distort": "Distorsión" +"node.category.filter": "Filtro" +"node.category.keying": "Incrustación" +"node.category.openfx": "OpenFX" +"node.category.group": "Grupo" +"project.context.new": "Nuevo" +"project.context.reveal_in_finder": "Mostrar en Finder" +"project.context.replace_footage": "Reemplazar material" +"project.context.rename": "Cambiar nombre" +"project.context.delete": "Eliminar" +"project.context.open_in_new_tab": "Abrir en pestaña nueva" +"project.context.open_in_new_window": "Abrir en ventana nueva" +"viewer.context.zoom": "Zoom" +"viewer.context.zoom_fit": "Ajustar" +"viewer.context.full_screen": "Pantalla completa" +"viewer.context.playback_resolution": "Resolución de reproducción" +"viewer.context.res_full": "Completa" +"viewer.context.res_half": "1/2" +"viewer.context.res_quarter": "1/4" +"viewer.context.res_eighth": "1/8" +"viewer.context.safe_margins": "Márgenes seguros" +"viewer.context.safe_off": "Desactivado" +"viewer.context.safe_on": "Activado" +"viewer.context.safe_custom": "Relación de aspecto personalizada" +"viewer.context.stop_on_last": "Detener reproducción en el último fotograma" +"viewer.context.audio_waveform": "Forma de onda de audio" +"viewer.context.wf_automatic": "Mostrar/ocultar automáticamente" +"viewer.context.wf_only": "Mostrar solo la forma de onda" +"viewer.context.wf_both": "Mostrar visor y forma de onda" +"viewer.context.show_fps": "Mostrar FPS" +"viewer.context.save_frame": "Guardar fotograma como imagen" +"node.context.group": "Agrupar" +"node.context.ungroup": "Desagrupar" +"node.context.open_in_viewer": "Abrir en el visor" +"node.context.show_in_param_editor": "Mostrar en el editor de parámetros" +"node.context.smooth_edges": "Suavizar bordes" +"node.context.direction": "Dirección" +"node.context.dir_top_bottom": "De arriba abajo" +"node.context.dir_bottom_top": "De abajo arriba" +"node.context.dir_left_right": "De izquierda a derecha" +"node.context.dir_right_left": "De derecha a izquierda" +"node.context.add": "Añadir" +"inspector.context.enable": "Activar" +"inspector.context.disable": "Desactivar" +"inspector.context.remove": "Quitar" +"inspector.context.rename": "Cambiar nombre" diff --git a/assets/i18n/fr.yaml b/assets/i18n/fr.yaml new file mode 100644 index 000000000..2f88e3741 --- /dev/null +++ b/assets/i18n/fr.yaml @@ -0,0 +1,433 @@ +# Oak Video Editor UI strings — Français (fr) +# Format: "i18n.key": "Translated text". Add new languages as new files named .yaml. +"menu.file": "Fichier(F)" +"menu.edit": "Édition(E)" +"menu.view": "Affichage(A)" +"menu.playback": "Lecture(L)" +"menu.sequence": "Séquence(S)" +"menu.window": "Fenêtre(F)" +"menu.tools": "Outils(O)" +"menu.help": "Aide(A)" +"menu.file.new": "Nouveau" +"menu.file.new_project": "Nouveau projet…" +"menu.file.new_sequence": "Nouvelle séquence…" +"menu.file.new_folder": "Nouveau dossier" +"menu.file.open_project": "Ouvrir un fichier projet…" +"menu.file.open_library": "Ouvrir depuis la bibliothèque…" +"menu.file.open_recent": "Fichiers récents" +"menu.file.clear_recent": "Effacer la liste des fichiers récents" +"menu.file.project_manager": "Gestionnaire de projets…" +"menu.file.import_footage": "Importer des médias…" +"menu.file.export_project": "Exporter un fichier projet…" +"menu.file.save_as": "Enregistrer sous…" +"menu.file.revert": "Restaurer" +"menu.file.export_media": "Exporter les médias…" +"menu.file.project_properties": "Propriétés du projet…" +"menu.file.close": "Fermer le projet" +"menu.file.quit": "Quitter" +"menu.edit.undo": "Annuler" +"menu.edit.redo": "Rétablir" +"menu.edit.cut": "Couper" +"menu.edit.copy": "Copier" +"menu.edit.paste": "Coller" +"menu.edit.paste_insert": "Coller en insertion" +"menu.edit.duplicate": "Dupliquer" +"menu.edit.rename": "Renommer" +"menu.edit.delete": "Supprimer" +"menu.edit.ripple_delete": "Suppression en ondulation" +"menu.edit.split": "Fractionner au curseur" +"menu.edit.speed_duration": "Vitesse/Durée…" +"menu.edit.default_transition": "Définir comme transition par défaut" +"menu.edit.link_unlink": "Lier/Dissocier" +"menu.edit.enable_disable": "Activer/Désactiver" +"menu.edit.nest": "Imbriquer" +"menu.edit.select_all": "Tout sélectionner" +"menu.edit.deselect_all": "Désélectionner tout" +"menu.edit.insert": "Insérer" +"menu.edit.overwrite": "Écraser" +"menu.edit.ripple_to_in": "Ondulation jusqu'à l'entrée" +"menu.edit.ripple_to_out": "Ondulation jusqu'à la sortie" +"menu.edit.edit_to_in": "Modifier jusqu'à l'entrée" +"menu.edit.edit_to_out": "Modifier jusqu'à la sortie" +"menu.edit.nudge_left": "Décaler à gauche" +"menu.edit.nudge_right": "Décaler à droite" +"menu.edit.move_in_to_playhead": "Déplacer le point d'entrée au curseur" +"menu.edit.move_out_to_playhead": "Déplacer le point de sortie au curseur" +"menu.edit.set_in_point": "Définir le point d'entrée" +"menu.edit.set_out_point": "Définir le point de sortie" +"menu.edit.reset_in": "Réinitialiser le point d'entrée" +"menu.edit.reset_out": "Réinitialiser le point de sortie" +"menu.edit.clear_in_out": "Effacer entrée/sortie" +"menu.edit.delete_in_out": "Supprimer de l'entrée à la sortie" +"menu.edit.ripple_delete_in_out": "Suppression en ondulation de l'entrée à la sortie" +"menu.edit.marker": "Ajouter un marqueur" +"menu.view.theme": "Thème" +"menu.view.theme.dark": "Olive Dark" +"menu.view.theme.light": "Olive Light" +"menu.view.language": "Langue" +"language.name": "Français" +"menu.view.zoom_in": "Zoom avant" +"menu.view.zoom_out": "Zoom arrière" +"menu.view.increase_track_height": "Augmenter la hauteur de piste" +"menu.view.decrease_track_height": "Réduire la hauteur de piste" +"menu.view.show_all": "Afficher toutes les pistes" +"menu.view.full_screen": "Plein écran" +"menu.view.full_screen_viewer": "Visionneuse plein écran" +"menu.view.preferences": "Préférences…" +"menu.playback.play_pause": "Lecture/Pause" +"menu.playback.play_in_to_out": "Lire de l'entrée à la sortie" +"menu.playback.loop": "Boucle" +"menu.playback.shuttle_left": "Navette à gauche" +"menu.playback.shuttle_stop": "Arrêter la navette" +"menu.playback.shuttle_right": "Navette à droite" +"menu.playback.prev_frame": "Image précédente" +"menu.playback.next_frame": "Image suivante" +"menu.playback.to_start": "Aller au début de la séquence" +"menu.playback.go_to_end": "Aller à la fin de la séquence" +"menu.playback.prev_cut": "Aller à la coupe précédente" +"menu.playback.next_cut": "Aller à la coupe suivante" +"menu.playback.go_to_in": "Aller au point d'entrée" +"menu.playback.go_to_out": "Aller au point de sortie" +"menu.sequence.add_video_track": "Ajouter une piste vidéo" +"menu.sequence.add_audio_track": "Ajouter une piste audio" +"menu.sequence.remove_track": "Supprimer la piste sélectionnée" +"menu.sequence.remove_marker": "Supprimer le marqueur" +"menu.sequence.set_workarea": "Définir la zone de travail" +"menu.sequence.clear_workarea": "Effacer la zone de travail" +"menu.sequence.cache": "Cache de rendu" +"menu.sequence.cache_in_out": "Cache de rendu de l'entrée à la sortie" +"menu.sequence.cache_clear": "Effacer le cache de rendu" +"menu.sequence.settings": "Paramètres de séquence…" +"menu.window.project": "Projet" +"menu.window.source_viewer": "Visionneuse source" +"menu.window.program_viewer": "Visionneuse du programme" +"menu.window.node_editor": "Éditeur de nœuds" +"menu.window.inspector": "Inspecteur" +"menu.window.history": "Historique" +"menu.window.timeline": "Montage" +"menu.window.effect_library": "Bibliothèque d'effets" +"menu.window.multicam": "Multicam" +"menu.window.maximize_panel": "Agrandir le panneau" +"menu.window.reset_layout": "Réinitialiser la disposition" +"menu.tools.pointer": "Pointeur" +"menu.tools.track_select": "Sélection de piste" +"menu.tools.edit": "Modifier" +"menu.tools.ripple": "Ondulation" +"menu.tools.rolling": "Roulage" +"menu.tools.razor_tool": "Rasoir" +"menu.tools.slip": "Glissement" +"menu.tools.slide": "Coulissement" +"menu.tools.hand": "Main" +"menu.tools.zoom_tool": "Zoom" +"menu.tools.transition": "Transition" +"menu.tools.add": "Ajouter" +"menu.tools.record": "Enregistrer" +"menu.tools.addable.empty": "Clip vide" +"menu.tools.addable.bars": "Barres de couleur" +"menu.tools.addable.shape": "Forme" +"menu.tools.addable.solid": "Couleur unie" +"menu.tools.addable.title": "Titre" +"menu.tools.addable.tone": "Tonalité de test" +"menu.tools.addable.subtitle": "Sous-titre" +"menu.tools.snapping": "Magnétisme" +"menu.tools.use_proxy": "Utiliser les médias proxy" +"menu.tools.proxy_settings": "Paramètres de proxy…" +"menu.help.action_search": "Rechercher des actions…" +"menu.help.feedback": "Envoyer un retour…" +"menu.help.about": "À propos d'Oak…" +"shortcut.space": "Espace" +"panel.project": "Projet" +"panel.source_viewer": "Visionneuse source" +"panel.program_viewer": "Visionneuse du programme" +"panel.node_editor": "Éditeur de nœuds" +"panel.inspector": "Inspecteur" +"panel.history": "Historique" +"panel.timeline": "Montage" +"panel.effect_library": "Bibliothèque d'effets" +"panel.multicam": "Multicam" +"multicam.no_multicam": "Aucun clip multicaméra détecté" +"multicam.switch_1": "Passer à la caméra 1" +"multicam.switch_2": "Passer à la caméra 2" +"multicam.switch_3": "Passer à la caméra 3" +"multicam.switch_4": "Passer à la caméra 4" +"multicam.switch_5": "Passer à la caméra 5" +"multicam.switch_6": "Passer à la caméra 6" +"multicam.switch_7": "Passer à la caméra 7" +"multicam.switch_8": "Passer à la caméra 8" +"multicam.switch_9": "Passer à la caméra 9" +"effect_library.hint": "Double-cliquer pour ajouter au clip sélectionné" +"effect_library.group.builtin": "Intégré" +"status.ready": "Prêt" +"status.cache": "Cache : activé" +"status.proxy.on": "Proxy : activé" +"status.proxy.off": "Proxy : désactivé" +"status.storage.written": "Bibliothèque : enregistrée" +"status.storage.unbound": "Bibliothèque : désactivée" +"status.storage.error": "Bibliothèque : échec de l'écriture" +"status.untitled": "Projet sans titre" +"status.backend": "Moteur :" +"manager.title": "Gestionnaire de projets" +"manager.new": "Nouveau projet" +"manager.new.default_name": "Projet sans titre" +"manager.open": "Ouvrir" +"manager.rename": "Renommer…" +"manager.rename.title": "Renommer le projet" +"manager.rename.label": "Nouveau nom" +"manager.duplicate": "Dupliquer" +"manager.delete": "Supprimer" +"manager.delete.title": "Supprimer le projet" +"manager.delete.confirm": "Supprimer le projet « {name} » de la bibliothèque ? Cette action est irréversible." +"manager.import": "Importer…" +"manager.import.title": "Importer un projet" +"manager.export": "Exporter…" +"manager.export.title": "Exporter un projet" +"manager.col.name": "Nom" +"manager.col.modified": "Modifié" +"manager.col.duration": "Durée" +"manager.col.tracks": "Pistes" +"manager.col.clips": "Clips" +"manager.col.footage": "Médias" +"manager.empty": "Aucun projet dans la bibliothèque pour le moment." +"timeline.tool.select": "Sélection" +"timeline.tool.razor": "Rasoir" +"timeline.tool.ripple": "Ondulation" +"timeline.tool.slip": "Glissement" +"timeline.tool.roll": "Roulage" +"timeline.tool.zoom": "Zoom" +"timeline.tool.slide": "Coulissement" +"timeline.tool.track_select": "Sélection de piste" +"timeline.zoom": "Zoom" +"timeline.zoom_in": "Zoom avant" +"timeline.zoom_out": "Zoom arrière" +"timeline.track_height": "Hauteur de piste" +"timeline.snap": "Magnétisme" +"timeline.add_video_track": "Ajouter une piste vidéo" +"timeline.add_audio_track": "Ajouter une piste audio" +"bin.footage": "Médias" +"bin.music": "Musique" +"history.command": "Commande" +"history.empty": "Aucun historique" +"history.jump_here": "Aller à cette étape" +"node.fit": "Ajuster" +"viewer.picture": "Image" +"viewer.scopes": "Oscilloscopes" +"scope.histogram": "Histogramme" +"scope.waveform": "Forme d'onde" +"scope.vectorscope": "Vecteurscope" +"viewer.in_point": "Définir le point d'entrée" +"viewer.step_back": "Image précédente" +"viewer.play": "Lecture" +"viewer.pause": "Pause" +"viewer.step_forward": "Image suivante" +"viewer.out_point": "Définir le point de sortie" +"viewer.clear_range": "Effacer la plage entrée/sortie" +"explorer.tree": "Arborescence" +"explorer.icons": "Icônes" +"viewer.safe_frames": "Cadres de sécurité" +"viewer.zoom": "Zoom" +"viewer.no_frame_source": "Aucune source d'image" +"effect_stack.empty": "Aucune sélection" +"effect_stack.add": "+ Ajouter un effet" +"inspector.params": "Aucun paramètre modifiable" +"inspector.add_effect": "Ajouter un effet" +"inspector.badge.openfx": "OpenFX" +"ofx.progress.title": "Progression du plugin OpenFX" +"ofx.color.hex": "Hexadécimal" +"ofx.color.ok": "Valider" +"ofx.color.cancel": "Annuler" +"ofx.color.invalid": "Couleur hexadécimale invalide" +"dialog.cancel": "Annuler" +"dialog.close": "Fermer" +"file.open.title": "Ouvrir le projet" +"file.import_footage.title": "Importer des médias" +"preferences.title": "Préférences" +"preferences.section.general": "Général" +"preferences.section.render": "Rendu" +"preferences.section.cache": "Cache" +"preferences.section.proxy": "Proxy" +"preferences.section.color": "Couleur" +"preferences.color.enable": "Afficher la gestion des couleurs (ICC du moniteur)" +"preferences.color.custom": "Profil ICC personnalisé" +"preferences.color.browse": "Parcourir…" +"preferences.color.restart_hint": "Un changement de mode prend effet après un redémarrage." +"preferences.section.project": "Projet" +"preferences.section.audio": "Audio" +"preferences.backend": "Moteur de rendu" +"preferences.hwdecode.enable": "Décodage vidéo accéléré par le matériel (VideoToolbox / VA-API / NVDEC / D3D11VA)" +"preferences.backend.placeholder": "Sélectionner un moteur…" +"preferences.language": "Langue" +"preferences.language.placeholder": "Sélectionner une langue…" +"preferences.theme": "Thème" +"preferences.theme.dark": "Olive Dark" +"preferences.theme.light": "Olive Light" +"preferences.cache.dir": "Dossier du cache disque" +"preferences.cache.browse": "Parcourir…" +"preferences.proxy.enable": "Utiliser les médias proxy" +"preferences.proxy.resolution": "Résolution du proxy" +"preferences.proxy.full": "Pleine résolution" +"preferences.snapshot.interval": "Intervalle d'enregistrement automatique (instantané), en secondes" +"preferences.transition.default": "Durée par défaut des transitions, en secondes" +"preferences.audio.output": "Périphérique de sortie audio" +"preferences.audio.output.placeholder": "Sélectionner un périphérique de sortie…" +"preferences.audio.input": "Périphérique d'entrée audio" +"preferences.audio.input.placeholder": "Sélectionner un périphérique d'entrée…" +"preferences.audio.default": "Par défaut du système" +"preferences.hint": "Le moteur de rendu s'applique au processus de rendu au prochain lancement ; tous les autres réglages prennent effet immédiatement et sont enregistrés à la fermeture." +"preferences.tab.general": "Général" +"preferences.tab.keyboard": "Clavier" +"preferences.section.keyboard": "Clavier" +"preferences.keyboard.search": "Rechercher une action ou un raccourci" +"preferences.keyboard.action": "Action" +"preferences.keyboard.shortcut": "Raccourci" +"preferences.keyboard.click_to_edit": "Cliquer pour définir le raccourci…" +"preferences.keyboard.capturing": "Appuyer sur les touches… (Échap pour annuler)" +"preferences.keyboard.unbound": "Aucun" +"preferences.keyboard.import": "Importer…" +"preferences.keyboard.export": "Exporter…" +"preferences.keyboard.reset_selected": "Réinitialiser la sélection" +"preferences.keyboard.reset_all": "Tout réinitialiser" +"preferences.keyboard.reset_all.confirm": "Voulez-vous vraiment réinitialiser tous les raccourcis clavier à leurs valeurs par défaut ?" +"preferences.keyboard.conflict": "Le raccourci est déjà affecté à {action} ; l'affectation a été déplacée." +"preferences.keyboard.cleared": "Raccourci effacé (action désaffectée)." +"preferences.keyboard.reset": "Raccourci réinitialisé à sa valeur par défaut." +"preferences.keyboard.reset_all_done": "Tous les raccourcis ont été réinitialisés à leurs valeurs par défaut." +"preferences.keyboard.imported": "Raccourcis importés avec succès." +"preferences.keyboard.exported": "Raccourcis exportés avec succès." +"preferences.keyboard.import_failed": "Impossible d'ouvrir le fichier en lecture." +"preferences.keyboard.export_failed": "Impossible d'ouvrir le fichier en écriture." +"actionsearch.search_placeholder": "Rechercher une action…" +"actionsearch.empty": "Aucune action correspondante" +"actionsearch.no_actions": "Aucune action disponible" +"export.title": "Exporter la séquence" +"export.format": "Format" +"export.format.placeholder": "Sélectionner un format…" +"export.path": "Chemin de sortie" +"export.run": "Exporter" +"export.hint": "La séquence est exportée via le chemin d'exportation oaktask ; la progression s'affiche dans la boîte de dialogue." +"export.progress.title": "Exportation" +"export.progress.label": "Rendu des images…" +"proxydialog.title": "Paramètres de proxy" +"proxydialog.footage_group": "Médias" +"proxydialog.no_footage": "Aucun média dans le projet" +"proxydialog.custom": "Utiliser des paramètres personnalisés pour les médias" +"proxydialog.custom_suffix": " (paramètres personnalisés)" +"proxydialog.global": "Paramètres de proxy globaux" +"proxydialog.resolution": "Résolution du proxy" +"proxydialog.resolution.custom": "Taille personnalisée" +"proxydialog.resolution.half": "1/2 de la source" +"proxydialog.resolution.quarter": "1/4 de la source" +"proxydialog.resolution.eighth": "1/8 de la source" +"proxydialog.width": "Largeur du proxy" +"proxydialog.height": "Hauteur du proxy" +"proxydialog.crf": "CRF du proxy" +"proxydialog.preset": "Présélection du proxy" +"proxydialog.include_audio": "Inclure l'audio dans les proxies" +"proxydialog.ffmpeg": "Exécutable ffmpeg" +"proxydialog.generate": "Générer les proxies" +"proxydialog.delete": "Supprimer les proxies" +"proxydialog.close": "Fermer" +"proxydialog.state.missing": "Manquant" +"proxydialog.state.generating": "Génération" +"proxydialog.state.ready": "Prêt" +"proxydialog.state.failed": "Échec" +"menu.color.label": "Étiquette de couleur" +"menu.color.red": "Rouge" +"menu.color.maroon": "Bordeaux" +"menu.color.orange": "Orange" +"menu.color.brown": "Marron" +"menu.color.yellow": "Jaune" +"menu.color.oak": "Chêne" +"menu.color.lime": "Citron vert" +"menu.color.green": "Vert" +"menu.color.cyan": "Cyan" +"menu.color.teal": "Sarcelle" +"menu.color.blue": "Bleu" +"menu.color.navy": "Bleu marine" +"menu.color.pink": "Rose" +"menu.color.purple": "Violet" +"menu.color.silver": "Argent" +"menu.color.gray": "Gris" +"menu.context.properties": "Propriétés" +"timeline.context.sync_source_time": "Synchroniser par l'heure de la source" +"timeline.context.sync_waveform": "Synchroniser par la forme d'onde" +"timeline.context.sync_waveform_speed": "Synchroniser par la forme d'onde (ajuster la vitesse)" +"timeline.context.cache": "Cache" +"timeline.context.auto_cache": "Cache automatique" +"timeline.context.cache_all": "Tout mettre en cache" +"timeline.context.cache_in_out": "Mettre en cache entrée/sortie" +"timeline.context.cache_discard": "Jeter" +"timeline.context.proxy": "Proxy" +"timeline.context.generate_proxy": "Générer le proxy" +"timeline.context.use_proxy": "Utiliser le proxy" +"timeline.context.reveal_proxy": "Révéler le proxy" +"timeline.context.delete_proxy": "Supprimer le proxy" +"timeline.context.reveal_in_footage_viewer": "Révéler dans la visionneuse de médias" +"timeline.context.reveal_in_project": "Révéler dans le projet" +"timeline.context.multicam": "Multicaméra" +"timeline.context.use_audio_time_units": "Utiliser les unités de temps audio" +"timeline.context.show_thumbnails": "Afficher les vignettes" +"timeline.context.thumbnails_off": "Désactivé" +"timeline.context.thumbnails_at_in_points": "Uniquement aux points d'entrée" +"timeline.context.thumbnails_on": "Activé" +"timeline.context.show_waveforms": "Afficher les formes d'onde" +"timeline.context.add_video_track": "Ajouter une piste vidéo" +"timeline.context.add_audio_track": "Ajouter une piste audio" +"timeline.context.delete_track": "Supprimer" +"timeline.context.delete_all_empty": "Supprimer toutes les pistes vides" +"timeline.context.timecode_drop_frame": "Timecode drop frame" +"timeline.context.timecode_non_drop_frame": "Timecode non-drop frame" +"timeline.context.timecode_seconds": "Secondes" +"timeline.context.timecode_frames": "Images" +"timeline.context.timecode_milliseconds": "Millisecondes" +"node.category.output": "Sortie" +"node.category.effect": "Effet" +"node.category.generator": "Générateur" +"node.category.input": "Entrée" +"node.category.math": "Maths" +"node.category.color": "Couleur" +"node.category.distort": "Déformation" +"node.category.filter": "Filtre" +"node.category.keying": "Incrustation" +"node.category.openfx": "OpenFX" +"node.category.group": "Groupe" +"project.context.new": "Nouveau" +"project.context.reveal_in_finder": "Révéler dans le Finder" +"project.context.replace_footage": "Remplacer les médias" +"project.context.rename": "Renommer" +"project.context.delete": "Supprimer" +"project.context.open_in_new_tab": "Ouvrir dans un nouvel onglet" +"project.context.open_in_new_window": "Ouvrir dans une nouvelle fenêtre" +"viewer.context.zoom": "Zoom" +"viewer.context.zoom_fit": "Ajuster" +"viewer.context.full_screen": "Plein écran" +"viewer.context.playback_resolution": "Résolution de lecture" +"viewer.context.res_full": "Pleine" +"viewer.context.res_half": "1/2" +"viewer.context.res_quarter": "1/4" +"viewer.context.res_eighth": "1/8" +"viewer.context.safe_margins": "Marges de sécurité" +"viewer.context.safe_off": "Désactivé" +"viewer.context.safe_on": "Activé" +"viewer.context.safe_custom": "Format d'image personnalisé" +"viewer.context.stop_on_last": "Arrêter la lecture sur la dernière image" +"viewer.context.audio_waveform": "Forme d'onde audio" +"viewer.context.wf_automatic": "Afficher/masquer automatiquement" +"viewer.context.wf_only": "Afficher uniquement la forme d'onde" +"viewer.context.wf_both": "Afficher la visionneuse et la forme d'onde" +"viewer.context.show_fps": "Afficher les FPS" +"viewer.context.save_frame": "Enregistrer l'image comme fichier" +"node.context.group": "Grouper" +"node.context.ungroup": "Dégrouper" +"node.context.open_in_viewer": "Ouvrir dans la visionneuse" +"node.context.show_in_param_editor": "Afficher dans l'éditeur de paramètres" +"node.context.smooth_edges": "Bords lissés" +"node.context.direction": "Direction" +"node.context.dir_top_bottom": "Du haut vers le bas" +"node.context.dir_bottom_top": "Du bas vers le haut" +"node.context.dir_left_right": "De gauche à droite" +"node.context.dir_right_left": "De droite à gauche" +"node.context.add": "Ajouter" +"inspector.context.enable": "Activer" +"inspector.context.disable": "Désactiver" +"inspector.context.remove": "Retirer" +"inspector.context.rename": "Renommer" diff --git a/assets/i18n/ja.yaml b/assets/i18n/ja.yaml new file mode 100644 index 000000000..932accd7c --- /dev/null +++ b/assets/i18n/ja.yaml @@ -0,0 +1,433 @@ +# Oak Video Editor UI strings — 日本語 (ja) +# Format: "i18n.key": "Translated text". Add new languages as new files named .yaml. +"menu.file": "ファイル(F)" +"menu.edit": "編集(E)" +"menu.view": "表示(V)" +"menu.playback": "再生(P)" +"menu.sequence": "シーケンス(S)" +"menu.window": "ウィンドウ(W)" +"menu.tools": "ツール(T)" +"menu.help": "ヘルプ(H)" +"menu.file.new": "新規" +"menu.file.new_project": "新規プロジェクト…" +"menu.file.new_sequence": "新規シーケンス…" +"menu.file.new_folder": "新規フォルダー" +"menu.file.open_project": "プロジェクトファイルを開く…" +"menu.file.open_library": "ライブラリから開く…" +"menu.file.open_recent": "最近使ったファイル" +"menu.file.clear_recent": "最近使ったファイルの一覧をクリア" +"menu.file.project_manager": "プロジェクトマネージャー…" +"menu.file.import_footage": "素材を読み込み…" +"menu.file.export_project": "プロジェクトファイルを書き出し…" +"menu.file.save_as": "別名で保存…" +"menu.file.revert": "保存済みに戻す" +"menu.file.export_media": "メディアを書き出し…" +"menu.file.project_properties": "プロジェクトのプロパティ…" +"menu.file.close": "プロジェクトを閉じる" +"menu.file.quit": "終了" +"menu.edit.undo": "元に戻す" +"menu.edit.redo": "やり直す" +"menu.edit.cut": "切り取り" +"menu.edit.copy": "コピー" +"menu.edit.paste": "貼り付け" +"menu.edit.paste_insert": "挿入として貼り付け" +"menu.edit.duplicate": "複製" +"menu.edit.rename": "名前を変更" +"menu.edit.delete": "削除" +"menu.edit.ripple_delete": "リップル削除" +"menu.edit.split": "再生ヘッドで分割" +"menu.edit.speed_duration": "速度/長さ…" +"menu.edit.default_transition": "既定のトランジションに設定" +"menu.edit.link_unlink": "リンク/リンク解除" +"menu.edit.enable_disable": "有効/無効" +"menu.edit.nest": "ネスト" +"menu.edit.select_all": "すべて選択" +"menu.edit.deselect_all": "選択を解除" +"menu.edit.insert": "挿入" +"menu.edit.overwrite": "上書き" +"menu.edit.ripple_to_in": "イン点までリップル" +"menu.edit.ripple_to_out": "アウト点までリップル" +"menu.edit.edit_to_in": "イン点まで編集" +"menu.edit.edit_to_out": "アウト点まで編集" +"menu.edit.nudge_left": "左へ微調整" +"menu.edit.nudge_right": "右へ微調整" +"menu.edit.move_in_to_playhead": "イン点を再生ヘッドへ移動" +"menu.edit.move_out_to_playhead": "アウト点を再生ヘッドへ移動" +"menu.edit.set_in_point": "イン点を設定" +"menu.edit.set_out_point": "アウト点を設定" +"menu.edit.reset_in": "イン点をリセット" +"menu.edit.reset_out": "アウト点をリセット" +"menu.edit.clear_in_out": "イン/アウトをクリア" +"menu.edit.delete_in_out": "インからアウトまで削除" +"menu.edit.ripple_delete_in_out": "インからアウトまでリップル削除" +"menu.edit.marker": "マーカーを追加" +"menu.view.theme": "テーマ" +"menu.view.theme.dark": "Olive Dark" +"menu.view.theme.light": "Olive Light" +"menu.view.language": "言語" +"language.name": "日本語" +"menu.view.zoom_in": "ズームイン" +"menu.view.zoom_out": "ズームアウト" +"menu.view.increase_track_height": "トラックの高さを増やす" +"menu.view.decrease_track_height": "トラックの高さを減らす" +"menu.view.show_all": "すべてのトラックを表示" +"menu.view.full_screen": "フルスクリーン" +"menu.view.full_screen_viewer": "ビューアをフルスクリーン表示" +"menu.view.preferences": "環境設定…" +"menu.playback.play_pause": "再生/一時停止" +"menu.playback.play_in_to_out": "インからアウトまで再生" +"menu.playback.loop": "ループ" +"menu.playback.shuttle_left": "シャトル左" +"menu.playback.shuttle_stop": "シャトル停止" +"menu.playback.shuttle_right": "シャトル右" +"menu.playback.prev_frame": "前のフレーム" +"menu.playback.next_frame": "次のフレーム" +"menu.playback.to_start": "シーケンスの先頭へ移動" +"menu.playback.go_to_end": "シーケンスの末尾へ移動" +"menu.playback.prev_cut": "前のカットへ移動" +"menu.playback.next_cut": "次のカットへ移動" +"menu.playback.go_to_in": "イン点へ移動" +"menu.playback.go_to_out": "アウト点へ移動" +"menu.sequence.add_video_track": "ビデオトラックを追加" +"menu.sequence.add_audio_track": "オーディオトラックを追加" +"menu.sequence.remove_track": "選択したトラックを削除" +"menu.sequence.remove_marker": "マーカーを削除" +"menu.sequence.set_workarea": "作業領域を設定" +"menu.sequence.clear_workarea": "作業領域をクリア" +"menu.sequence.cache": "レンダーキャッシュ" +"menu.sequence.cache_in_out": "インからアウトまでをレンダーキャッシュ" +"menu.sequence.cache_clear": "レンダーキャッシュをクリア" +"menu.sequence.settings": "シーケンス設定…" +"menu.window.project": "プロジェクト" +"menu.window.source_viewer": "ソースビューア" +"menu.window.program_viewer": "プログラムビューア" +"menu.window.node_editor": "ノードエディター" +"menu.window.inspector": "インスペクター" +"menu.window.history": "履歴" +"menu.window.timeline": "タイムライン" +"menu.window.effect_library": "エフェクトライブラリ" +"menu.window.multicam": "マルチカム" +"menu.window.maximize_panel": "パネルを最大化" +"menu.window.reset_layout": "レイアウトをリセット" +"menu.tools.pointer": "ポインター" +"menu.tools.track_select": "トラック選択" +"menu.tools.edit": "編集" +"menu.tools.ripple": "リップル" +"menu.tools.rolling": "ローリング" +"menu.tools.razor_tool": "カミソリ" +"menu.tools.slip": "スリップ" +"menu.tools.slide": "スライド" +"menu.tools.hand": "手のひら" +"menu.tools.zoom_tool": "ズーム" +"menu.tools.transition": "トランジション" +"menu.tools.add": "追加" +"menu.tools.record": "録画" +"menu.tools.addable.empty": "空のクリップ" +"menu.tools.addable.bars": "カラーバー" +"menu.tools.addable.shape": "シェイプ" +"menu.tools.addable.solid": "単色" +"menu.tools.addable.title": "タイトル" +"menu.tools.addable.tone": "テストトーン" +"menu.tools.addable.subtitle": "字幕" +"menu.tools.snapping": "スナップ" +"menu.tools.use_proxy": "プロキシメディアを使用" +"menu.tools.proxy_settings": "プロキシ設定…" +"menu.help.action_search": "アクションを検索…" +"menu.help.feedback": "フィードバックを送信…" +"menu.help.about": "Oak について…" +"shortcut.space": "スペース" +"panel.project": "プロジェクト" +"panel.source_viewer": "ソースビューア" +"panel.program_viewer": "プログラムビューア" +"panel.node_editor": "ノードエディター" +"panel.inspector": "インスペクター" +"panel.history": "履歴" +"panel.timeline": "タイムライン" +"panel.effect_library": "エフェクトライブラリ" +"panel.multicam": "マルチカム" +"multicam.no_multicam": "マルチカメラクリップが検出されませんでした" +"multicam.switch_1": "カメラ1に切り替え" +"multicam.switch_2": "カメラ2に切り替え" +"multicam.switch_3": "カメラ3に切り替え" +"multicam.switch_4": "カメラ4に切り替え" +"multicam.switch_5": "カメラ5に切り替え" +"multicam.switch_6": "カメラ6に切り替え" +"multicam.switch_7": "カメラ7に切り替え" +"multicam.switch_8": "カメラ8に切り替え" +"multicam.switch_9": "カメラ9に切り替え" +"effect_library.hint": "ダブルクリックで選択中のクリップに追加します" +"effect_library.group.builtin": "内蔵" +"status.ready": "準備完了" +"status.cache": "キャッシュ:有効" +"status.proxy.on": "プロキシ:オン" +"status.proxy.off": "プロキシ:オフ" +"status.storage.written": "ライブラリ:保存済み" +"status.storage.unbound": "ライブラリ:オフ" +"status.storage.error": "ライブラリ:書き込みエラー" +"status.untitled": "無題のプロジェクト" +"status.backend": "エンジン:" +"manager.title": "プロジェクトマネージャー" +"manager.new": "新規プロジェクト" +"manager.new.default_name": "無題のプロジェクト" +"manager.open": "開く" +"manager.rename": "名前を変更…" +"manager.rename.title": "プロジェクトの名前を変更" +"manager.rename.label": "新しい名前" +"manager.duplicate": "複製" +"manager.delete": "削除" +"manager.delete.title": "プロジェクトを削除" +"manager.delete.confirm": "ライブラリからプロジェクト「{name}」を削除しますか?この操作は元に戻せません。" +"manager.import": "読み込み…" +"manager.import.title": "プロジェクトを読み込み" +"manager.export": "書き出し…" +"manager.export.title": "プロジェクトを書き出し" +"manager.col.name": "名前" +"manager.col.modified": "更新日時" +"manager.col.duration": "長さ" +"manager.col.tracks": "トラック" +"manager.col.clips": "クリップ" +"manager.col.footage": "素材" +"manager.empty": "ライブラリにプロジェクトはまだありません。" +"timeline.tool.select": "選択" +"timeline.tool.razor": "カミソリ" +"timeline.tool.ripple": "リップル" +"timeline.tool.slip": "スリップ" +"timeline.tool.roll": "ローリング" +"timeline.tool.zoom": "ズーム" +"timeline.tool.slide": "スライド" +"timeline.tool.track_select": "トラック選択" +"timeline.zoom": "ズーム" +"timeline.zoom_in": "ズームイン" +"timeline.zoom_out": "ズームアウト" +"timeline.track_height": "トラックの高さ" +"timeline.snap": "スナップ" +"timeline.add_video_track": "ビデオトラックを追加" +"timeline.add_audio_track": "オーディオトラックを追加" +"bin.footage": "素材" +"bin.music": "音楽" +"history.command": "コマンド" +"history.empty": "履歴なし" +"history.jump_here": "このステップに移動" +"node.fit": "フィット" +"viewer.picture": "画像" +"viewer.scopes": "スコープ" +"scope.histogram": "ヒストグラム" +"scope.waveform": "ウェーブフォーム" +"scope.vectorscope": "ベクトルスコープ" +"viewer.in_point": "イン点を設定" +"viewer.step_back": "前のフレーム" +"viewer.play": "再生" +"viewer.pause": "一時停止" +"viewer.step_forward": "次のフレーム" +"viewer.out_point": "アウト点を設定" +"viewer.clear_range": "イン/アウト範囲をクリア" +"explorer.tree": "ツリー" +"explorer.icons": "アイコン" +"viewer.safe_frames": "セーフエリア" +"viewer.zoom": "ズーム" +"viewer.no_frame_source": "フレームソースがありません" +"effect_stack.empty": "選択なし" +"effect_stack.add": "+ エフェクトを追加" +"inspector.params": "編集可能なパラメーターはありません" +"inspector.add_effect": "エフェクトを追加" +"inspector.badge.openfx": "OpenFX" +"ofx.progress.title": "OpenFX プラグインの進捗" +"ofx.color.hex": "16進" +"ofx.color.ok": "決定" +"ofx.color.cancel": "キャンセル" +"ofx.color.invalid": "無効な16進カラーです" +"dialog.cancel": "キャンセル" +"dialog.close": "閉じる" +"file.open.title": "プロジェクトを開く" +"file.import_footage.title": "素材を読み込み" +"preferences.title": "環境設定" +"preferences.section.general": "一般" +"preferences.section.render": "レンダリング" +"preferences.section.cache": "キャッシュ" +"preferences.section.proxy": "プロキシ" +"preferences.section.color": "カラー" +"preferences.color.enable": "ディスプレイのカラーマネジメントを有効にする(ディスプレイICC)" +"preferences.color.custom": "カスタムICCプロファイル" +"preferences.color.browse": "参照…" +"preferences.color.restart_hint": "モードの変更は再起動後に有効になります。" +"preferences.section.project": "プロジェクト" +"preferences.section.audio": "オーディオ" +"preferences.backend": "レンダラーバックエンド" +"preferences.hwdecode.enable": "ハードウェアアクセラレーションによるビデオデコード(VideoToolbox / VA-API / NVDEC / D3D11VA)" +"preferences.backend.placeholder": "バックエンドを選択…" +"preferences.language": "言語" +"preferences.language.placeholder": "言語を選択…" +"preferences.theme": "テーマ" +"preferences.theme.dark": "Olive Dark" +"preferences.theme.light": "Olive Light" +"preferences.cache.dir": "ディスクキャッシュの保存先" +"preferences.cache.browse": "参照…" +"preferences.proxy.enable": "プロキシメディアを使用" +"preferences.proxy.resolution": "プロキシ解像度" +"preferences.proxy.full": "フル解像度" +"preferences.snapshot.interval": "自動保存(スナップショット)の間隔(秒)" +"preferences.transition.default": "既定のトランジションの長さ(秒)" +"preferences.audio.output": "オーディオ出力デバイス" +"preferences.audio.output.placeholder": "出力デバイスを選択…" +"preferences.audio.input": "オーディオ入力デバイス" +"preferences.audio.input.placeholder": "入力デバイスを選択…" +"preferences.audio.default": "システムの既定" +"preferences.hint": "レンダラーバックエンドは次回起動時にレンダーワーカーへ適用されます。その他の設定はすぐに有効になり、終了時に保存されます。" +"preferences.tab.general": "一般" +"preferences.tab.keyboard": "キーボード" +"preferences.section.keyboard": "キーボード" +"preferences.keyboard.search": "アクションまたはショートカットを検索" +"preferences.keyboard.action": "アクション" +"preferences.keyboard.shortcut": "ショートカット" +"preferences.keyboard.click_to_edit": "クリックしてショートカットを設定…" +"preferences.keyboard.capturing": "キーを押してください…(Escでキャンセル)" +"preferences.keyboard.unbound": "なし" +"preferences.keyboard.import": "読み込み…" +"preferences.keyboard.export": "書き出し…" +"preferences.keyboard.reset_selected": "選択項目をリセット" +"preferences.keyboard.reset_all": "すべてリセット" +"preferences.keyboard.reset_all.confirm": "すべてのキーボードショートカットを既定にリセットしますか?" +"preferences.keyboard.conflict": "ショートカットは既に{action}に割り当てられています。割り当ては移動されました。" +"preferences.keyboard.cleared": "ショートカットをクリアしました(アクションは未割り当て)。" +"preferences.keyboard.reset": "ショートカットを既定にリセットしました。" +"preferences.keyboard.reset_all_done": "すべてのショートカットを既定にリセットしました。" +"preferences.keyboard.imported": "ショートカットを読み込みました。" +"preferences.keyboard.exported": "ショートカットを書き出しました。" +"preferences.keyboard.import_failed": "ファイルを読み取り用に開けませんでした。" +"preferences.keyboard.export_failed": "ファイルを書き込み用に開けませんでした。" +"actionsearch.search_placeholder": "アクションを検索…" +"actionsearch.empty": "一致するアクションがありません" +"actionsearch.no_actions": "利用可能なアクションがありません" +"export.title": "シーケンスを書き出し" +"export.format": "フォーマット" +"export.format.placeholder": "フォーマットを選択…" +"export.path": "出力先" +"export.run": "書き出し" +"export.hint": "シーケンスはoaktaskの書き出しパスを通じて書き出されます。進捗はダイアログに表示されます。" +"export.progress.title": "書き出し中" +"export.progress.label": "フレームをレンダリング中…" +"proxydialog.title": "プロキシ設定" +"proxydialog.footage_group": "素材" +"proxydialog.no_footage": "プロジェクトに素材がありません" +"proxydialog.custom": "素材にカスタム設定を使用" +"proxydialog.custom_suffix": " (カスタム設定)" +"proxydialog.global": "グローバルプロキシ設定" +"proxydialog.resolution": "プロキシ解像度" +"proxydialog.resolution.custom": "カスタムサイズ" +"proxydialog.resolution.half": "ソースの1/2" +"proxydialog.resolution.quarter": "ソースの1/4" +"proxydialog.resolution.eighth": "ソースの1/8" +"proxydialog.width": "プロキシ幅" +"proxydialog.height": "プロキシ高さ" +"proxydialog.crf": "プロキシCRF" +"proxydialog.preset": "プロキシプリセット" +"proxydialog.include_audio": "プロキシにオーディオを含める" +"proxydialog.ffmpeg": "ffmpeg 実行ファイル" +"proxydialog.generate": "プロキシを生成" +"proxydialog.delete": "プロキシを削除" +"proxydialog.close": "閉じる" +"proxydialog.state.missing": "見つかりません" +"proxydialog.state.generating": "生成中" +"proxydialog.state.ready": "準備完了" +"proxydialog.state.failed": "失敗" +"menu.color.label": "カラーラベル" +"menu.color.red": "赤" +"menu.color.maroon": "えび茶" +"menu.color.orange": "オレンジ" +"menu.color.brown": "茶" +"menu.color.yellow": "黄" +"menu.color.oak": "オーク" +"menu.color.lime": "ライム" +"menu.color.green": "緑" +"menu.color.cyan": "シアン" +"menu.color.teal": "ティール" +"menu.color.blue": "青" +"menu.color.navy": "紺" +"menu.color.pink": "ピンク" +"menu.color.purple": "紫" +"menu.color.silver": "銀" +"menu.color.gray": "灰" +"menu.context.properties": "プロパティ" +"timeline.context.sync_source_time": "ソース時間で同期" +"timeline.context.sync_waveform": "ウェーブフォームで同期" +"timeline.context.sync_waveform_speed": "ウェーブフォームで同期(速度調整)" +"timeline.context.cache": "キャッシュ" +"timeline.context.auto_cache": "自動キャッシュ" +"timeline.context.cache_all": "すべてキャッシュ" +"timeline.context.cache_in_out": "イン/アウトをキャッシュ" +"timeline.context.cache_discard": "破棄" +"timeline.context.proxy": "プロキシ" +"timeline.context.generate_proxy": "プロキシを生成" +"timeline.context.use_proxy": "プロキシを使用" +"timeline.context.reveal_proxy": "プロキシを表示" +"timeline.context.delete_proxy": "プロキシを削除" +"timeline.context.reveal_in_footage_viewer": "素材ビューアに表示" +"timeline.context.reveal_in_project": "プロジェクトに表示" +"timeline.context.multicam": "マルチカム" +"timeline.context.use_audio_time_units": "オーディオ時間単位を使用" +"timeline.context.show_thumbnails": "サムネイルを表示" +"timeline.context.thumbnails_off": "無効" +"timeline.context.thumbnails_at_in_points": "イン点のみ" +"timeline.context.thumbnails_on": "有効" +"timeline.context.show_waveforms": "ウェーブフォームを表示" +"timeline.context.add_video_track": "ビデオトラックを追加" +"timeline.context.add_audio_track": "オーディオトラックを追加" +"timeline.context.delete_track": "削除" +"timeline.context.delete_all_empty": "空のトラックをすべて削除" +"timeline.context.timecode_drop_frame": "ドロップフレーム" +"timeline.context.timecode_non_drop_frame": "ノンドロップフレーム" +"timeline.context.timecode_seconds": "秒" +"timeline.context.timecode_frames": "フレーム" +"timeline.context.timecode_milliseconds": "ミリ秒" +"node.category.output": "出力" +"node.category.effect": "エフェクト" +"node.category.generator": "ジェネレーター" +"node.category.input": "入力" +"node.category.math": "数学" +"node.category.color": "カラー" +"node.category.distort": "ディストーション" +"node.category.filter": "フィルター" +"node.category.keying": "キーイング" +"node.category.openfx": "OpenFX" +"node.category.group": "グループ" +"project.context.new": "新規" +"project.context.reveal_in_finder": "Finderに表示" +"project.context.replace_footage": "素材を置き換え" +"project.context.rename": "名前を変更" +"project.context.delete": "削除" +"project.context.open_in_new_tab": "新しいタブで開く" +"project.context.open_in_new_window": "新しいウィンドウで開く" +"viewer.context.zoom": "ズーム" +"viewer.context.zoom_fit": "フィット" +"viewer.context.full_screen": "フルスクリーン" +"viewer.context.playback_resolution": "再生解像度" +"viewer.context.res_full": "フル" +"viewer.context.res_half": "1/2" +"viewer.context.res_quarter": "1/4" +"viewer.context.res_eighth": "1/8" +"viewer.context.safe_margins": "セーフマージン" +"viewer.context.safe_off": "オフ" +"viewer.context.safe_on": "オン" +"viewer.context.safe_custom": "カスタムアスペクト比" +"viewer.context.stop_on_last": "最後のフレームで再生を停止" +"viewer.context.audio_waveform": "オーディオウェーブフォーム" +"viewer.context.wf_automatic": "自動的に表示/非表示" +"viewer.context.wf_only": "ウェーブフォームのみ表示" +"viewer.context.wf_both": "ビューアとウェーブフォームの両方を表示" +"viewer.context.show_fps": "FPSを表示" +"viewer.context.save_frame": "フレームを画像として保存" +"node.context.group": "グループ化" +"node.context.ungroup": "グループ化を解除" +"node.context.open_in_viewer": "ビューアで開く" +"node.context.show_in_param_editor": "パラメーターエディターに表示" +"node.context.smooth_edges": "エッジを滑らかに" +"node.context.direction": "方向" +"node.context.dir_top_bottom": "上から下" +"node.context.dir_bottom_top": "下から上" +"node.context.dir_left_right": "左から右" +"node.context.dir_right_left": "右から左" +"node.context.add": "追加" +"inspector.context.enable": "有効にする" +"inspector.context.disable": "無効にする" +"inspector.context.remove": "削除" +"inspector.context.rename": "名前を変更" diff --git a/assets/i18n/pt.yaml b/assets/i18n/pt.yaml new file mode 100644 index 000000000..b42b57c22 --- /dev/null +++ b/assets/i18n/pt.yaml @@ -0,0 +1,433 @@ +# Oak Video Editor UI strings — Português (pt) +# Format: "i18n.key": "Translated text". Add new languages as new files named .yaml. +"menu.file": "Arquivo(A)" +"menu.edit": "Editar(E)" +"menu.view": "Ver(V)" +"menu.playback": "Reprodução(R)" +"menu.sequence": "Sequência(S)" +"menu.window": "Janela(J)" +"menu.tools": "Ferramentas(F)" +"menu.help": "Ajuda(A)" +"menu.file.new": "Novo" +"menu.file.new_project": "Novo projeto…" +"menu.file.new_sequence": "Nova sequência…" +"menu.file.new_folder": "Nova pasta" +"menu.file.open_project": "Abrir arquivo de projeto…" +"menu.file.open_library": "Abrir da biblioteca…" +"menu.file.open_recent": "Abrir recentes" +"menu.file.clear_recent": "Limpar lista de recentes" +"menu.file.project_manager": "Gerenciador de projetos…" +"menu.file.import_footage": "Importar mídia…" +"menu.file.export_project": "Exportar arquivo de projeto…" +"menu.file.save_as": "Salvar como…" +"menu.file.revert": "Reverter" +"menu.file.export_media": "Exportar mídia…" +"menu.file.project_properties": "Propriedades do projeto…" +"menu.file.close": "Fechar projeto" +"menu.file.quit": "Sair" +"menu.edit.undo": "Desfazer" +"menu.edit.redo": "Refazer" +"menu.edit.cut": "Recortar" +"menu.edit.copy": "Copiar" +"menu.edit.paste": "Colar" +"menu.edit.paste_insert": "Colar como inserção" +"menu.edit.duplicate": "Duplicar" +"menu.edit.rename": "Renomear" +"menu.edit.delete": "Excluir" +"menu.edit.ripple_delete": "Exclusão em cadeia" +"menu.edit.split": "Dividir no cursor" +"menu.edit.speed_duration": "Velocidade/Duração…" +"menu.edit.default_transition": "Definir como transição padrão" +"menu.edit.link_unlink": "Vincular/Desvincular" +"menu.edit.enable_disable": "Ativar/Desativar" +"menu.edit.nest": "Aninhar" +"menu.edit.select_all": "Selecionar tudo" +"menu.edit.deselect_all": "Desmarcar tudo" +"menu.edit.insert": "Inserir" +"menu.edit.overwrite": "Sobrescrever" +"menu.edit.ripple_to_in": "Ondulação até a entrada" +"menu.edit.ripple_to_out": "Ondulação até a saída" +"menu.edit.edit_to_in": "Editar até a entrada" +"menu.edit.edit_to_out": "Editar até a saída" +"menu.edit.nudge_left": "Deslocar para a esquerda" +"menu.edit.nudge_right": "Deslocar para a direita" +"menu.edit.move_in_to_playhead": "Mover ponto de entrada para o cursor" +"menu.edit.move_out_to_playhead": "Mover ponto de saída para o cursor" +"menu.edit.set_in_point": "Definir ponto de entrada" +"menu.edit.set_out_point": "Definir ponto de saída" +"menu.edit.reset_in": "Redefinir ponto de entrada" +"menu.edit.reset_out": "Redefinir ponto de saída" +"menu.edit.clear_in_out": "Limpar entrada/saída" +"menu.edit.delete_in_out": "Excluir da entrada à saída" +"menu.edit.ripple_delete_in_out": "Exclusão em cadeia da entrada à saída" +"menu.edit.marker": "Adicionar marcador" +"menu.view.theme": "Tema" +"menu.view.theme.dark": "Olive Dark" +"menu.view.theme.light": "Olive Light" +"menu.view.language": "Idioma" +"language.name": "Português" +"menu.view.zoom_in": "Aproximar" +"menu.view.zoom_out": "Afastar" +"menu.view.increase_track_height": "Aumentar altura da trilha" +"menu.view.decrease_track_height": "Diminuir altura da trilha" +"menu.view.show_all": "Mostrar todas as trilhas" +"menu.view.full_screen": "Tela cheia" +"menu.view.full_screen_viewer": "Visualizador em tela cheia" +"menu.view.preferences": "Preferências…" +"menu.playback.play_pause": "Reproduzir/Pausar" +"menu.playback.play_in_to_out": "Reproduzir da entrada à saída" +"menu.playback.loop": "Loop" +"menu.playback.shuttle_left": "Shuttle à esquerda" +"menu.playback.shuttle_stop": "Parar shuttle" +"menu.playback.shuttle_right": "Shuttle à direita" +"menu.playback.prev_frame": "Quadro anterior" +"menu.playback.next_frame": "Próximo quadro" +"menu.playback.to_start": "Ir para o início da sequência" +"menu.playback.go_to_end": "Ir para o fim da sequência" +"menu.playback.prev_cut": "Ir para o corte anterior" +"menu.playback.next_cut": "Ir para o próximo corte" +"menu.playback.go_to_in": "Ir para o ponto de entrada" +"menu.playback.go_to_out": "Ir para o ponto de saída" +"menu.sequence.add_video_track": "Adicionar trilha de vídeo" +"menu.sequence.add_audio_track": "Adicionar trilha de áudio" +"menu.sequence.remove_track": "Remover trilha selecionada" +"menu.sequence.remove_marker": "Remover marcador" +"menu.sequence.set_workarea": "Definir área de trabalho" +"menu.sequence.clear_workarea": "Limpar área de trabalho" +"menu.sequence.cache": "Cache de renderização" +"menu.sequence.cache_in_out": "Cache de renderização da entrada à saída" +"menu.sequence.cache_clear": "Limpar cache de renderização" +"menu.sequence.settings": "Configurações da sequência…" +"menu.window.project": "Projeto" +"menu.window.source_viewer": "Visualizador de origem" +"menu.window.program_viewer": "Visualizador de programa" +"menu.window.node_editor": "Editor de nós" +"menu.window.inspector": "Inspetor" +"menu.window.history": "Histórico" +"menu.window.timeline": "Linha do tempo" +"menu.window.effect_library": "Biblioteca de efeitos" +"menu.window.multicam": "Multicâmera" +"menu.window.maximize_panel": "Maximizar painel" +"menu.window.reset_layout": "Redefinir layout" +"menu.tools.pointer": "Ponteiro" +"menu.tools.track_select": "Seleção de trilha" +"menu.tools.edit": "Edição" +"menu.tools.ripple": "Ondulação" +"menu.tools.rolling": "Rolagem" +"menu.tools.razor_tool": "Navalha" +"menu.tools.slip": "Deslizamento" +"menu.tools.slide": "Deslocamento" +"menu.tools.hand": "Mão" +"menu.tools.zoom_tool": "Zoom" +"menu.tools.transition": "Transição" +"menu.tools.add": "Adicionar" +"menu.tools.record": "Gravar" +"menu.tools.addable.empty": "Clipe vazio" +"menu.tools.addable.bars": "Barras de cores" +"menu.tools.addable.shape": "Forma" +"menu.tools.addable.solid": "Cor sólida" +"menu.tools.addable.title": "Título" +"menu.tools.addable.tone": "Tom de teste" +"menu.tools.addable.subtitle": "Legenda" +"menu.tools.snapping": "Ajuste" +"menu.tools.use_proxy": "Usar mídia proxy" +"menu.tools.proxy_settings": "Configurações de proxy…" +"menu.help.action_search": "Pesquisar ações…" +"menu.help.feedback": "Enviar comentários…" +"menu.help.about": "Sobre o Oak…" +"shortcut.space": "Espaço" +"panel.project": "Projeto" +"panel.source_viewer": "Visualizador de origem" +"panel.program_viewer": "Visualizador de programa" +"panel.node_editor": "Editor de nós" +"panel.inspector": "Inspetor" +"panel.history": "Histórico" +"panel.timeline": "Linha do tempo" +"panel.effect_library": "Biblioteca de efeitos" +"panel.multicam": "Multicâmera" +"multicam.no_multicam": "Nenhum clipe multicâmera detectado" +"multicam.switch_1": "Alternar para a câmera 1" +"multicam.switch_2": "Alternar para a câmera 2" +"multicam.switch_3": "Alternar para a câmera 3" +"multicam.switch_4": "Alternar para a câmera 4" +"multicam.switch_5": "Alternar para a câmera 5" +"multicam.switch_6": "Alternar para a câmera 6" +"multicam.switch_7": "Alternar para a câmera 7" +"multicam.switch_8": "Alternar para a câmera 8" +"multicam.switch_9": "Alternar para a câmera 9" +"effect_library.hint": "Clique duas vezes para adicionar ao clipe selecionado" +"effect_library.group.builtin": "Integrados" +"status.ready": "Pronto" +"status.cache": "Cache: ativado" +"status.proxy.on": "Proxy: ativado" +"status.proxy.off": "Proxy: desativado" +"status.storage.written": "Biblioteca: salva" +"status.storage.unbound": "Biblioteca: desativada" +"status.storage.error": "Biblioteca: falha ao gravar" +"status.untitled": "Projeto sem título" +"status.backend": "Mecanismo:" +"manager.title": "Gerenciador de projetos" +"manager.new": "Novo projeto" +"manager.new.default_name": "Projeto sem título" +"manager.open": "Abrir" +"manager.rename": "Renomear…" +"manager.rename.title": "Renomear projeto" +"manager.rename.label": "Novo nome" +"manager.duplicate": "Duplicar" +"manager.delete": "Excluir" +"manager.delete.title": "Excluir projeto" +"manager.delete.confirm": "Excluir o projeto \"{name}\" da biblioteca? Esta ação não pode ser desfeita." +"manager.import": "Importar…" +"manager.import.title": "Importar projeto" +"manager.export": "Exportar…" +"manager.export.title": "Exportar projeto" +"manager.col.name": "Nome" +"manager.col.modified": "Modificado" +"manager.col.duration": "Duração" +"manager.col.tracks": "Trilhas" +"manager.col.clips": "Clipes" +"manager.col.footage": "Mídia" +"manager.empty": "Ainda não há projetos na biblioteca." +"timeline.tool.select": "Seleção" +"timeline.tool.razor": "Navalha" +"timeline.tool.ripple": "Ondulação" +"timeline.tool.slip": "Deslizamento" +"timeline.tool.roll": "Rolagem" +"timeline.tool.zoom": "Zoom" +"timeline.tool.slide": "Deslocamento" +"timeline.tool.track_select": "Seleção de trilha" +"timeline.zoom": "Zoom" +"timeline.zoom_in": "Aproximar" +"timeline.zoom_out": "Afastar" +"timeline.track_height": "Altura da trilha" +"timeline.snap": "Ajuste" +"timeline.add_video_track": "Adicionar trilha de vídeo" +"timeline.add_audio_track": "Adicionar trilha de áudio" +"bin.footage": "Mídia" +"bin.music": "Música" +"history.command": "Comando" +"history.empty": "Sem histórico" +"history.jump_here": "Ir para esta etapa" +"node.fit": "Ajustar" +"viewer.picture": "Imagem" +"viewer.scopes": "Escopos" +"scope.histogram": "Histograma" +"scope.waveform": "Forma de onda" +"scope.vectorscope": "Vectorscópio" +"viewer.in_point": "Definir ponto de entrada" +"viewer.step_back": "Quadro anterior" +"viewer.play": "Reproduzir" +"viewer.pause": "Pausar" +"viewer.step_forward": "Próximo quadro" +"viewer.out_point": "Definir ponto de saída" +"viewer.clear_range": "Limpar intervalo entrada/saída" +"explorer.tree": "Árvore" +"explorer.icons": "Ícones" +"viewer.safe_frames": "Margens seguras" +"viewer.zoom": "Zoom" +"viewer.no_frame_source": "Nenhuma fonte de quadro" +"effect_stack.empty": "Nenhuma seleção" +"effect_stack.add": "+ Adicionar efeito" +"inspector.params": "Nenhum parâmetro editável" +"inspector.add_effect": "Adicionar efeito" +"inspector.badge.openfx": "OpenFX" +"ofx.progress.title": "Progresso do plugin OpenFX" +"ofx.color.hex": "Hexadecimal" +"ofx.color.ok": "Confirmar" +"ofx.color.cancel": "Cancelar" +"ofx.color.invalid": "Cor hexadecimal inválida" +"dialog.cancel": "Cancelar" +"dialog.close": "Fechar" +"file.open.title": "Abrir projeto" +"file.import_footage.title": "Importar mídia" +"preferences.title": "Preferências" +"preferences.section.general": "Geral" +"preferences.section.render": "Renderização" +"preferences.section.cache": "Cache" +"preferences.section.proxy": "Proxy" +"preferences.section.color": "Cor" +"preferences.color.enable": "Exibir gerenciamento de cores (ICC da tela)" +"preferences.color.custom": "Perfil ICC personalizado" +"preferences.color.browse": "Procurar…" +"preferences.color.restart_hint": "A alteração do modo entra em vigor após reiniciar." +"preferences.section.project": "Projeto" +"preferences.section.audio": "Áudio" +"preferences.backend": "Backend do renderizador" +"preferences.hwdecode.enable": "Decodificação de vídeo acelerada por hardware (VideoToolbox / VA-API / NVDEC / D3D11VA)" +"preferences.backend.placeholder": "Selecionar um backend…" +"preferences.language": "Idioma" +"preferences.language.placeholder": "Selecionar um idioma…" +"preferences.theme": "Tema" +"preferences.theme.dark": "Olive Dark" +"preferences.theme.light": "Olive Light" +"preferences.cache.dir": "Diretório do cache em disco" +"preferences.cache.browse": "Procurar…" +"preferences.proxy.enable": "Usar mídia proxy" +"preferences.proxy.resolution": "Resolução do proxy" +"preferences.proxy.full": "Resolução completa" +"preferences.snapshot.interval": "Intervalo de salvamento automático (snapshot), em segundos" +"preferences.transition.default": "Duração padrão das transições, em segundos" +"preferences.audio.output": "Dispositivo de saída de áudio" +"preferences.audio.output.placeholder": "Selecionar um dispositivo de saída…" +"preferences.audio.input": "Dispositivo de entrada de áudio" +"preferences.audio.input.placeholder": "Selecionar um dispositivo de entrada…" +"preferences.audio.default": "Padrão do sistema" +"preferences.hint": "O backend do renderizador é aplicado ao worker de renderização no próximo início; as demais configurações entram em vigor imediatamente e são salvas ao sair." +"preferences.tab.general": "Geral" +"preferences.tab.keyboard": "Teclado" +"preferences.section.keyboard": "Teclado" +"preferences.keyboard.search": "Pesquisar ação ou atalho" +"preferences.keyboard.action": "Ação" +"preferences.keyboard.shortcut": "Atalho" +"preferences.keyboard.click_to_edit": "Clique para definir o atalho…" +"preferences.keyboard.capturing": "Pressione as teclas… (Esc para cancelar)" +"preferences.keyboard.unbound": "Nenhum" +"preferences.keyboard.import": "Importar…" +"preferences.keyboard.export": "Exportar…" +"preferences.keyboard.reset_selected": "Redefinir selecionados" +"preferences.keyboard.reset_all": "Redefinir tudo" +"preferences.keyboard.reset_all.confirm": "Tem certeza de que deseja redefinir todos os atalhos de teclado para os padrões?" +"preferences.keyboard.conflict": "O atalho já está atribuído a {action}; a atribuição foi movida." +"preferences.keyboard.cleared": "Atalho limpo (ação sem atribuição)." +"preferences.keyboard.reset": "Atalho redefinido para o padrão." +"preferences.keyboard.reset_all_done": "Todos os atalhos foram redefinidos para os padrões." +"preferences.keyboard.imported": "Atalhos importados com sucesso." +"preferences.keyboard.exported": "Atalhos exportados com sucesso." +"preferences.keyboard.import_failed": "Não foi possível abrir o arquivo para leitura." +"preferences.keyboard.export_failed": "Não foi possível abrir o arquivo para gravação." +"actionsearch.search_placeholder": "Pesquisar ação…" +"actionsearch.empty": "Nenhuma ação correspondente" +"actionsearch.no_actions": "Nenhuma ação disponível" +"export.title": "Exportar sequência" +"export.format": "Formato" +"export.format.placeholder": "Selecionar um formato…" +"export.path": "Caminho de saída" +"export.run": "Exportar" +"export.hint": "A sequência é exportada pelo caminho de exportação do oaktask; o progresso é exibido no diálogo." +"export.progress.title": "Exportando" +"export.progress.label": "Renderizando quadros…" +"proxydialog.title": "Configurações de proxy" +"proxydialog.footage_group": "Mídia" +"proxydialog.no_footage": "Nenhuma mídia no projeto" +"proxydialog.custom": "Usar configurações personalizadas para a mídia" +"proxydialog.custom_suffix": " (configurações personalizadas)" +"proxydialog.global": "Configurações globais de proxy" +"proxydialog.resolution": "Resolução do proxy" +"proxydialog.resolution.custom": "Tamanho personalizado" +"proxydialog.resolution.half": "1/2 da origem" +"proxydialog.resolution.quarter": "1/4 da origem" +"proxydialog.resolution.eighth": "1/8 da origem" +"proxydialog.width": "Largura do proxy" +"proxydialog.height": "Altura do proxy" +"proxydialog.crf": "CRF do proxy" +"proxydialog.preset": "Predefinição do proxy" +"proxydialog.include_audio": "Incluir áudio nos proxies" +"proxydialog.ffmpeg": "Executável do ffmpeg" +"proxydialog.generate": "Gerar proxies" +"proxydialog.delete": "Excluir proxies" +"proxydialog.close": "Fechar" +"proxydialog.state.missing": "Ausente" +"proxydialog.state.generating": "Gerando" +"proxydialog.state.ready": "Pronto" +"proxydialog.state.failed": "Falha" +"menu.color.label": "Rótulo de cor" +"menu.color.red": "Vermelho" +"menu.color.maroon": "Castanho" +"menu.color.orange": "Laranja" +"menu.color.brown": "Marrom" +"menu.color.yellow": "Amarelo" +"menu.color.oak": "Carvalho" +"menu.color.lime": "Lima" +"menu.color.green": "Verde" +"menu.color.cyan": "Ciano" +"menu.color.teal": "Verde-azulado" +"menu.color.blue": "Azul" +"menu.color.navy": "Azul-marinho" +"menu.color.pink": "Rosa" +"menu.color.purple": "Roxo" +"menu.color.silver": "Prata" +"menu.color.gray": "Cinza" +"menu.context.properties": "Propriedades" +"timeline.context.sync_source_time": "Sincronizar pelo tempo da origem" +"timeline.context.sync_waveform": "Sincronizar pela forma de onda" +"timeline.context.sync_waveform_speed": "Sincronizar pela forma de onda (ajustar velocidade)" +"timeline.context.cache": "Cache" +"timeline.context.auto_cache": "Cache automático" +"timeline.context.cache_all": "Armazenar tudo em cache" +"timeline.context.cache_in_out": "Armazenar entrada/saída em cache" +"timeline.context.cache_discard": "Descartar" +"timeline.context.proxy": "Proxy" +"timeline.context.generate_proxy": "Gerar proxy" +"timeline.context.use_proxy": "Usar proxy" +"timeline.context.reveal_proxy": "Revelar proxy" +"timeline.context.delete_proxy": "Excluir proxy" +"timeline.context.reveal_in_footage_viewer": "Revelar no visualizador de mídia" +"timeline.context.reveal_in_project": "Revelar no projeto" +"timeline.context.multicam": "Multicâmera" +"timeline.context.use_audio_time_units": "Usar unidades de tempo de áudio" +"timeline.context.show_thumbnails": "Mostrar miniaturas" +"timeline.context.thumbnails_off": "Desativadas" +"timeline.context.thumbnails_at_in_points": "Somente nos pontos de entrada" +"timeline.context.thumbnails_on": "Ativadas" +"timeline.context.show_waveforms": "Mostrar formas de onda" +"timeline.context.add_video_track": "Adicionar trilha de vídeo" +"timeline.context.add_audio_track": "Adicionar trilha de áudio" +"timeline.context.delete_track": "Excluir" +"timeline.context.delete_all_empty": "Excluir todas as trilhas vazias" +"timeline.context.timecode_drop_frame": "Drop-frame" +"timeline.context.timecode_non_drop_frame": "Non-drop-frame" +"timeline.context.timecode_seconds": "Segundos" +"timeline.context.timecode_frames": "Quadros" +"timeline.context.timecode_milliseconds": "Milissegundos" +"node.category.output": "Saída" +"node.category.effect": "Efeito" +"node.category.generator": "Gerador" +"node.category.input": "Entrada" +"node.category.math": "Matemática" +"node.category.color": "Cor" +"node.category.distort": "Distorção" +"node.category.filter": "Filtro" +"node.category.keying": "Chroma key" +"node.category.openfx": "OpenFX" +"node.category.group": "Grupo" +"project.context.new": "Novo" +"project.context.reveal_in_finder": "Revelar no Finder" +"project.context.replace_footage": "Substituir mídia" +"project.context.rename": "Renomear" +"project.context.delete": "Excluir" +"project.context.open_in_new_tab": "Abrir em nova guia" +"project.context.open_in_new_window": "Abrir em nova janela" +"viewer.context.zoom": "Zoom" +"viewer.context.zoom_fit": "Ajustar" +"viewer.context.full_screen": "Tela cheia" +"viewer.context.playback_resolution": "Resolução de reprodução" +"viewer.context.res_full": "Completa" +"viewer.context.res_half": "1/2" +"viewer.context.res_quarter": "1/4" +"viewer.context.res_eighth": "1/8" +"viewer.context.safe_margins": "Margens seguras" +"viewer.context.safe_off": "Desativado" +"viewer.context.safe_on": "Ativado" +"viewer.context.safe_custom": "Proporção personalizada" +"viewer.context.stop_on_last": "Parar reprodução no último quadro" +"viewer.context.audio_waveform": "Forma de onda de áudio" +"viewer.context.wf_automatic": "Mostrar/ocultar automaticamente" +"viewer.context.wf_only": "Mostrar somente a forma de onda" +"viewer.context.wf_both": "Mostrar visualizador e forma de onda" +"viewer.context.show_fps": "Mostrar FPS" +"viewer.context.save_frame": "Salvar quadro como imagem" +"node.context.group": "Agrupar" +"node.context.ungroup": "Desagrupar" +"node.context.open_in_viewer": "Abrir no visualizador" +"node.context.show_in_param_editor": "Mostrar no editor de parâmetros" +"node.context.smooth_edges": "Suavizar bordas" +"node.context.direction": "Direção" +"node.context.dir_top_bottom": "De cima para baixo" +"node.context.dir_bottom_top": "De baixo para cima" +"node.context.dir_left_right": "Da esquerda para a direita" +"node.context.dir_right_left": "Da direita para a esquerda" +"node.context.add": "Adicionar" +"inspector.context.enable": "Ativar" +"inspector.context.disable": "Desativar" +"inspector.context.remove": "Remover" +"inspector.context.rename": "Renomear" diff --git a/assets/i18n/ru.yaml b/assets/i18n/ru.yaml new file mode 100644 index 000000000..cc9a27f8c --- /dev/null +++ b/assets/i18n/ru.yaml @@ -0,0 +1,433 @@ +# Oak Video Editor UI strings — Русский (ru) +# Format: "i18n.key": "Translated text". Add new languages as new files named .yaml. +"menu.file": "Файл(Ф)" +"menu.edit": "Правка(П)" +"menu.view": "Вид(В)" +"menu.playback": "Воспроизведение(В)" +"menu.sequence": "Последовательность(П)" +"menu.window": "Окно(О)" +"menu.tools": "Инструменты(И)" +"menu.help": "Справка(С)" +"menu.file.new": "Создать" +"menu.file.new_project": "Новый проект…" +"menu.file.new_sequence": "Новая последовательность…" +"menu.file.new_folder": "Новая папка" +"menu.file.open_project": "Открыть файл проекта…" +"menu.file.open_library": "Открыть из библиотеки…" +"menu.file.open_recent": "Недавние файлы" +"menu.file.clear_recent": "Очистить список недавних файлов" +"menu.file.project_manager": "Диспетчер проектов…" +"menu.file.import_footage": "Импортировать медиафайлы…" +"menu.file.export_project": "Экспортировать файл проекта…" +"menu.file.save_as": "Сохранить как…" +"menu.file.revert": "Восстановить" +"menu.file.export_media": "Экспортировать медиафайлы…" +"menu.file.project_properties": "Свойства проекта…" +"menu.file.close": "Закрыть проект" +"menu.file.quit": "Выход" +"menu.edit.undo": "Отменить" +"menu.edit.redo": "Повторить" +"menu.edit.cut": "Вырезать" +"menu.edit.copy": "Копировать" +"menu.edit.paste": "Вставить" +"menu.edit.paste_insert": "Вставить вставкой" +"menu.edit.duplicate": "Дублировать" +"menu.edit.rename": "Переименовать" +"menu.edit.delete": "Удалить" +"menu.edit.ripple_delete": "Удалить со сдвигом" +"menu.edit.split": "Разделить в позиции курсора" +"menu.edit.speed_duration": "Скорость/Длительность…" +"menu.edit.default_transition": "Назначить переходом по умолчанию" +"menu.edit.link_unlink": "Связать/Отменить связь" +"menu.edit.enable_disable": "Включить/Отключить" +"menu.edit.nest": "Вложить" +"menu.edit.select_all": "Выделить всё" +"menu.edit.deselect_all": "Снять выделение" +"menu.edit.insert": "Вставка" +"menu.edit.overwrite": "Перезаписать" +"menu.edit.ripple_to_in": "Со сдвигом до точки входа" +"menu.edit.ripple_to_out": "Со сдвигом до точки выхода" +"menu.edit.edit_to_in": "Обрезать до точки входа" +"menu.edit.edit_to_out": "Обрезать до точки выхода" +"menu.edit.nudge_left": "Сдвинуть влево" +"menu.edit.nudge_right": "Сдвинуть вправо" +"menu.edit.move_in_to_playhead": "Переместить точку входа к курсору" +"menu.edit.move_out_to_playhead": "Переместить точку выхода к курсору" +"menu.edit.set_in_point": "Задать точку входа" +"menu.edit.set_out_point": "Задать точку выхода" +"menu.edit.reset_in": "Сбросить точку входа" +"menu.edit.reset_out": "Сбросить точку выхода" +"menu.edit.clear_in_out": "Очистить вход/выход" +"menu.edit.delete_in_out": "Удалить от входа до выхода" +"menu.edit.ripple_delete_in_out": "Удалить со сдвигом от входа до выхода" +"menu.edit.marker": "Добавить маркер" +"menu.view.theme": "Тема" +"menu.view.theme.dark": "Olive Dark" +"menu.view.theme.light": "Olive Light" +"menu.view.language": "Язык" +"language.name": "Русский" +"menu.view.zoom_in": "Увеличить" +"menu.view.zoom_out": "Уменьшить" +"menu.view.increase_track_height": "Увеличить высоту дорожки" +"menu.view.decrease_track_height": "Уменьшить высоту дорожки" +"menu.view.show_all": "Показать все дорожки" +"menu.view.full_screen": "Во весь экран" +"menu.view.full_screen_viewer": "Просмотр на весь экран" +"menu.view.preferences": "Настройки…" +"menu.playback.play_pause": "Воспроизведение/Пауза" +"menu.playback.play_in_to_out": "Воспроизвести от входа до выхода" +"menu.playback.loop": "Зациклить" +"menu.playback.shuttle_left": "Шаттл влево" +"menu.playback.shuttle_stop": "Остановить шаттл" +"menu.playback.shuttle_right": "Шаттл вправо" +"menu.playback.prev_frame": "Предыдущий кадр" +"menu.playback.next_frame": "Следующий кадр" +"menu.playback.to_start": "Перейти в начало последовательности" +"menu.playback.go_to_end": "Перейти в конец последовательности" +"menu.playback.prev_cut": "Перейти к предыдущей склейке" +"menu.playback.next_cut": "Перейти к следующей склейке" +"menu.playback.go_to_in": "Перейти к точке входа" +"menu.playback.go_to_out": "Перейти к точке выхода" +"menu.sequence.add_video_track": "Добавить видеодорожку" +"menu.sequence.add_audio_track": "Добавить аудиодорожку" +"menu.sequence.remove_track": "Удалить выбранную дорожку" +"menu.sequence.remove_marker": "Удалить маркер" +"menu.sequence.set_workarea": "Задать рабочую область" +"menu.sequence.clear_workarea": "Очистить рабочую область" +"menu.sequence.cache": "Кэш рендеринга" +"menu.sequence.cache_in_out": "Кэшировать от входа до выхода" +"menu.sequence.cache_clear": "Очистить кэш рендеринга" +"menu.sequence.settings": "Параметры последовательности…" +"menu.window.project": "Проект" +"menu.window.source_viewer": "Монитор источника" +"menu.window.program_viewer": "Монитор программы" +"menu.window.node_editor": "Редактор узлов" +"menu.window.inspector": "Инспектор" +"menu.window.history": "История" +"menu.window.timeline": "Таймлайн" +"menu.window.effect_library": "Библиотека эффектов" +"menu.window.multicam": "Мультикамера" +"menu.window.maximize_panel": "Развернуть панель" +"menu.window.reset_layout": "Сбросить расположение" +"menu.tools.pointer": "Указатель" +"menu.tools.track_select": "Выбор дорожки" +"menu.tools.edit": "Редактирование" +"menu.tools.ripple": "Сдвиг" +"menu.tools.rolling": "Накат" +"menu.tools.razor_tool": "Бритва" +"menu.tools.slip": "Скольжение" +"menu.tools.slide": "Перемещение" +"menu.tools.hand": "Рука" +"menu.tools.zoom_tool": "Масштаб" +"menu.tools.transition": "Переход" +"menu.tools.add": "Добавить" +"menu.tools.record": "Запись" +"menu.tools.addable.empty": "Пустой клип" +"menu.tools.addable.bars": "Цветные полосы" +"menu.tools.addable.shape": "Фигура" +"menu.tools.addable.solid": "Сплошной цвет" +"menu.tools.addable.title": "Титры" +"menu.tools.addable.tone": "Тестовый тон" +"menu.tools.addable.subtitle": "Субтитры" +"menu.tools.snapping": "Привязка" +"menu.tools.use_proxy": "Использовать прокси-медиа" +"menu.tools.proxy_settings": "Параметры прокси…" +"menu.help.action_search": "Поиск действий…" +"menu.help.feedback": "Отправить отзыв…" +"menu.help.about": "О программе Oak…" +"shortcut.space": "Пробел" +"panel.project": "Проект" +"panel.source_viewer": "Монитор источника" +"panel.program_viewer": "Монитор программы" +"panel.node_editor": "Редактор узлов" +"panel.inspector": "Инспектор" +"panel.history": "История" +"panel.timeline": "Таймлайн" +"panel.effect_library": "Библиотека эффектов" +"panel.multicam": "Мультикамера" +"multicam.no_multicam": "Не обнаружен клип мультикамеры" +"multicam.switch_1": "Переключиться на камеру 1" +"multicam.switch_2": "Переключиться на камеру 2" +"multicam.switch_3": "Переключиться на камеру 3" +"multicam.switch_4": "Переключиться на камеру 4" +"multicam.switch_5": "Переключиться на камеру 5" +"multicam.switch_6": "Переключиться на камеру 6" +"multicam.switch_7": "Переключиться на камеру 7" +"multicam.switch_8": "Переключиться на камеру 8" +"multicam.switch_9": "Переключиться на камеру 9" +"effect_library.hint": "Двойной щелчок добавляет эффект к выбранному клипу" +"effect_library.group.builtin": "Встроенные" +"status.ready": "Готово" +"status.cache": "Кэш: включён" +"status.proxy.on": "Прокси: вкл" +"status.proxy.off": "Прокси: выкл" +"status.storage.written": "Библиотека: сохранена" +"status.storage.unbound": "Библиотека: выключена" +"status.storage.error": "Библиотека: ошибка записи" +"status.untitled": "Проект без названия" +"status.backend": "Движок:" +"manager.title": "Диспетчер проектов" +"manager.new": "Новый проект" +"manager.new.default_name": "Проект без названия" +"manager.open": "Открыть" +"manager.rename": "Переименовать…" +"manager.rename.title": "Переименовать проект" +"manager.rename.label": "Новое имя" +"manager.duplicate": "Дублировать" +"manager.delete": "Удалить" +"manager.delete.title": "Удалить проект" +"manager.delete.confirm": "Удалить проект «{name}» из библиотеки? Это действие нельзя отменить." +"manager.import": "Импортировать…" +"manager.import.title": "Импорт проекта" +"manager.export": "Экспортировать…" +"manager.export.title": "Экспорт проекта" +"manager.col.name": "Имя" +"manager.col.modified": "Изменён" +"manager.col.duration": "Длительность" +"manager.col.tracks": "Дорожки" +"manager.col.clips": "Клипы" +"manager.col.footage": "Медиафайлы" +"manager.empty": "В библиотеке пока нет проектов." +"timeline.tool.select": "Выбор" +"timeline.tool.razor": "Бритва" +"timeline.tool.ripple": "Сдвиг" +"timeline.tool.slip": "Скольжение" +"timeline.tool.roll": "Накат" +"timeline.tool.zoom": "Масштаб" +"timeline.tool.slide": "Перемещение" +"timeline.tool.track_select": "Выбор дорожки" +"timeline.zoom": "Масштаб" +"timeline.zoom_in": "Увеличить" +"timeline.zoom_out": "Уменьшить" +"timeline.track_height": "Высота дорожки" +"timeline.snap": "Привязка" +"timeline.add_video_track": "Добавить видеодорожку" +"timeline.add_audio_track": "Добавить аудиодорожку" +"bin.footage": "Медиафайлы" +"bin.music": "Музыка" +"history.command": "Команда" +"history.empty": "История пуста" +"history.jump_here": "Перейти к этому шагу" +"node.fit": "Вписать" +"viewer.picture": "Изображение" +"viewer.scopes": "Осциллографы" +"scope.histogram": "Гистограмма" +"scope.waveform": "Осциллограмма" +"scope.vectorscope": "Вектороскоп" +"viewer.in_point": "Задать точку входа" +"viewer.step_back": "Предыдущий кадр" +"viewer.play": "Воспроизвести" +"viewer.pause": "Пауза" +"viewer.step_forward": "Следующий кадр" +"viewer.out_point": "Задать точку выхода" +"viewer.clear_range": "Очистить диапазон входа/выхода" +"explorer.tree": "Дерево" +"explorer.icons": "Значки" +"viewer.safe_frames": "Безопасные зоны" +"viewer.zoom": "Масштаб" +"viewer.no_frame_source": "Нет источника кадров" +"effect_stack.empty": "Ничего не выбрано" +"effect_stack.add": "+ Добавить эффект" +"inspector.params": "Нет редактируемых параметров" +"inspector.add_effect": "Добавить эффект" +"inspector.badge.openfx": "OpenFX" +"ofx.progress.title": "Выполнение плагина OpenFX" +"ofx.color.hex": "Шестнадцатеричный" +"ofx.color.ok": "ОК" +"ofx.color.cancel": "Отмена" +"ofx.color.invalid": "Недопустимый шестнадцатеричный цвет" +"dialog.cancel": "Отмена" +"dialog.close": "Закрыть" +"file.open.title": "Открыть проект" +"file.import_footage.title": "Импорт медиафайлов" +"preferences.title": "Настройки" +"preferences.section.general": "Общие" +"preferences.section.render": "Рендеринг" +"preferences.section.cache": "Кэш" +"preferences.section.proxy": "Прокси" +"preferences.section.color": "Цвет" +"preferences.color.enable": "Управление цветом отображения (ICC дисплея)" +"preferences.color.custom": "Пользовательский профиль ICC" +"preferences.color.browse": "Обзор…" +"preferences.color.restart_hint": "Изменение режима вступит в силу после перезапуска." +"preferences.section.project": "Проект" +"preferences.section.audio": "Аудио" +"preferences.backend": "Бэкенд рендеринга" +"preferences.hwdecode.enable": "Аппаратное декодирование видео (VideoToolbox / VA-API / NVDEC / D3D11VA)" +"preferences.backend.placeholder": "Выберите бэкенд…" +"preferences.language": "Язык" +"preferences.language.placeholder": "Выберите язык…" +"preferences.theme": "Тема" +"preferences.theme.dark": "Olive Dark" +"preferences.theme.light": "Olive Light" +"preferences.cache.dir": "Каталог дискового кэша" +"preferences.cache.browse": "Обзор…" +"preferences.proxy.enable": "Использовать прокси-медиа" +"preferences.proxy.resolution": "Разрешение прокси" +"preferences.proxy.full": "Полное разрешение" +"preferences.snapshot.interval": "Интервал автосохранения (снимков), в секундах" +"preferences.transition.default": "Длительность перехода по умолчанию, в секундах" +"preferences.audio.output": "Устройство вывода звука" +"preferences.audio.output.placeholder": "Выберите устройство вывода…" +"preferences.audio.input": "Устройство ввода звука" +"preferences.audio.input.placeholder": "Выберите устройство ввода…" +"preferences.audio.default": "Системное значение по умолчанию" +"preferences.hint": "Бэкенд рендеринга применяется к рабочему процессу рендеринга при следующем запуске; все остальные настройки вступают в силу немедленно и сохраняются при выходе." +"preferences.tab.general": "Общие" +"preferences.tab.keyboard": "Клавиатура" +"preferences.section.keyboard": "Клавиатура" +"preferences.keyboard.search": "Поиск действия или сочетания клавиш" +"preferences.keyboard.action": "Действие" +"preferences.keyboard.shortcut": "Сочетание клавиш" +"preferences.keyboard.click_to_edit": "Нажмите, чтобы задать сочетание…" +"preferences.keyboard.capturing": "Нажмите клавиши… (Esc — отмена)" +"preferences.keyboard.unbound": "Нет" +"preferences.keyboard.import": "Импортировать…" +"preferences.keyboard.export": "Экспортировать…" +"preferences.keyboard.reset_selected": "Сбросить выбранное" +"preferences.keyboard.reset_all": "Сбросить всё" +"preferences.keyboard.reset_all.confirm": "Сбросить все сочетания клавиш к значениям по умолчанию?" +"preferences.keyboard.conflict": "Сочетание уже назначено для {action}; привязка перемещена." +"preferences.keyboard.cleared": "Сочетание очищено (действие без привязки)." +"preferences.keyboard.reset": "Сочетание сброшено к значению по умолчанию." +"preferences.keyboard.reset_all_done": "Все сочетания клавиш сброшены к значениям по умолчанию." +"preferences.keyboard.imported": "Сочетания импортированы." +"preferences.keyboard.exported": "Сочетания экспортированы." +"preferences.keyboard.import_failed": "Не удалось открыть файл для чтения." +"preferences.keyboard.export_failed": "Не удалось открыть файл для записи." +"actionsearch.search_placeholder": "Поиск действия…" +"actionsearch.empty": "Нет подходящих действий" +"actionsearch.no_actions": "Нет доступных действий" +"export.title": "Экспорт последовательности" +"export.format": "Формат" +"export.format.placeholder": "Выберите формат…" +"export.path": "Путь вывода" +"export.run": "Экспортировать" +"export.hint": "Последовательность экспортируется через путь экспорта oaktask; ход выполнения отображается в диалоговом окне." +"export.progress.title": "Экспорт" +"export.progress.label": "Рендеринг кадров…" +"proxydialog.title": "Параметры прокси" +"proxydialog.footage_group": "Медиафайлы" +"proxydialog.no_footage": "В проекте нет медиафайлов" +"proxydialog.custom": "Использовать свои настройки для медиафайлов" +"proxydialog.custom_suffix": " (свои настройки)" +"proxydialog.global": "Общие параметры прокси" +"proxydialog.resolution": "Разрешение прокси" +"proxydialog.resolution.custom": "Свой размер" +"proxydialog.resolution.half": "1/2 исходного" +"proxydialog.resolution.quarter": "1/4 исходного" +"proxydialog.resolution.eighth": "1/8 исходного" +"proxydialog.width": "Ширина прокси" +"proxydialog.height": "Высота прокси" +"proxydialog.crf": "CRF прокси" +"proxydialog.preset": "Пресет прокси" +"proxydialog.include_audio": "Включать звук в прокси" +"proxydialog.ffmpeg": "Исполняемый файл ffmpeg" +"proxydialog.generate": "Создать прокси" +"proxydialog.delete": "Удалить прокси" +"proxydialog.close": "Закрыть" +"proxydialog.state.missing": "Отсутствует" +"proxydialog.state.generating": "Создание" +"proxydialog.state.ready": "Готово" +"proxydialog.state.failed": "Сбой" +"menu.color.label": "Цветовая метка" +"menu.color.red": "Красный" +"menu.color.maroon": "Бордовый" +"menu.color.orange": "Оранжевый" +"menu.color.brown": "Коричневый" +"menu.color.yellow": "Жёлтый" +"menu.color.oak": "Дуб" +"menu.color.lime": "Лаймовый" +"menu.color.green": "Зелёный" +"menu.color.cyan": "Голубой" +"menu.color.teal": "Бирюзовый" +"menu.color.blue": "Синий" +"menu.color.navy": "Тёмно-синий" +"menu.color.pink": "Розовый" +"menu.color.purple": "Фиолетовый" +"menu.color.silver": "Серебристый" +"menu.color.gray": "Серый" +"menu.context.properties": "Свойства" +"timeline.context.sync_source_time": "Синхронизировать по времени источника" +"timeline.context.sync_waveform": "Синхронизировать по осциллограмме" +"timeline.context.sync_waveform_speed": "Синхронизировать по осциллограмме (с изменением скорости)" +"timeline.context.cache": "Кэш" +"timeline.context.auto_cache": "Автокэш" +"timeline.context.cache_all": "Кэшировать всё" +"timeline.context.cache_in_out": "Кэшировать вход/выход" +"timeline.context.cache_discard": "Отбросить" +"timeline.context.proxy": "Прокси" +"timeline.context.generate_proxy": "Создать прокси" +"timeline.context.use_proxy": "Использовать прокси" +"timeline.context.reveal_proxy": "Показать прокси" +"timeline.context.delete_proxy": "Удалить прокси" +"timeline.context.reveal_in_footage_viewer": "Показать в окне просмотра медиафайлов" +"timeline.context.reveal_in_project": "Показать в проекте" +"timeline.context.multicam": "Мультикамера" +"timeline.context.use_audio_time_units": "Использовать единицы времени для аудио" +"timeline.context.show_thumbnails": "Показывать миниатюры" +"timeline.context.thumbnails_off": "Отключено" +"timeline.context.thumbnails_at_in_points": "Только в точках входа" +"timeline.context.thumbnails_on": "Включено" +"timeline.context.show_waveforms": "Показывать осциллограммы" +"timeline.context.add_video_track": "Добавить видеодорожку" +"timeline.context.add_audio_track": "Добавить аудиодорожку" +"timeline.context.delete_track": "Удалить" +"timeline.context.delete_all_empty": "Удалить все пустые дорожки" +"timeline.context.timecode_drop_frame": "С пропуском кадров" +"timeline.context.timecode_non_drop_frame": "Без пропуска кадров" +"timeline.context.timecode_seconds": "Секунды" +"timeline.context.timecode_frames": "Кадры" +"timeline.context.timecode_milliseconds": "Миллисекунды" +"node.category.output": "Выход" +"node.category.effect": "Эффект" +"node.category.generator": "Генератор" +"node.category.input": "Вход" +"node.category.math": "Математика" +"node.category.color": "Цвет" +"node.category.distort": "Искажение" +"node.category.filter": "Фильтр" +"node.category.keying": "Ключевание" +"node.category.openfx": "OpenFX" +"node.category.group": "Группа" +"project.context.new": "Создать" +"project.context.reveal_in_finder": "Показать в Finder" +"project.context.replace_footage": "Заменить медиафайлы" +"project.context.rename": "Переименовать" +"project.context.delete": "Удалить" +"project.context.open_in_new_tab": "Открыть в новой вкладке" +"project.context.open_in_new_window": "Открыть в новом окне" +"viewer.context.zoom": "Масштаб" +"viewer.context.zoom_fit": "Вписать" +"viewer.context.full_screen": "Во весь экран" +"viewer.context.playback_resolution": "Разрешение воспроизведения" +"viewer.context.res_full": "Полное" +"viewer.context.res_half": "1/2" +"viewer.context.res_quarter": "1/4" +"viewer.context.res_eighth": "1/8" +"viewer.context.safe_margins": "Безопасные поля" +"viewer.context.safe_off": "Выкл" +"viewer.context.safe_on": "Вкл" +"viewer.context.safe_custom": "Свой формат" +"viewer.context.stop_on_last": "Останавливать воспроизведение на последнем кадре" +"viewer.context.audio_waveform": "Осциллограмма звука" +"viewer.context.wf_automatic": "Автоматически показывать/скрывать" +"viewer.context.wf_only": "Показывать только осциллограмму" +"viewer.context.wf_both": "Показывать и изображение, и осциллограмму" +"viewer.context.show_fps": "Показывать FPS" +"viewer.context.save_frame": "Сохранить кадр как изображение" +"node.context.group": "Сгруппировать" +"node.context.ungroup": "Разгруппировать" +"node.context.open_in_viewer": "Открыть в окне просмотра" +"node.context.show_in_param_editor": "Показать в редакторе параметров" +"node.context.smooth_edges": "Сгладить края" +"node.context.direction": "Направление" +"node.context.dir_top_bottom": "Сверху вниз" +"node.context.dir_bottom_top": "Снизу вверх" +"node.context.dir_left_right": "Слева направо" +"node.context.dir_right_left": "Справа налево" +"node.context.add": "Добавить" +"inspector.context.enable": "Включить" +"inspector.context.disable": "Отключить" +"inspector.context.remove": "Удалить" +"inspector.context.rename": "Переименовать" diff --git a/assets/i18n/zh-CN.yaml b/assets/i18n/zh-CN.yaml index d294f21c4..6c350bbe1 100644 --- a/assets/i18n/zh-CN.yaml +++ b/assets/i18n/zh-CN.yaml @@ -65,8 +65,7 @@ "menu.view.theme.dark": "Olive Dark" "menu.view.theme.light": "Olive Light" "menu.view.language": "语言" -"menu.view.language.en": "English" -"menu.view.language.zh": "简体中文" +"language.name": "简体中文" "menu.view.zoom_in": "放大" "menu.view.zoom_out": "缩小" "menu.view.increase_track_height": "增加轨道高度" diff --git a/src/actions.rs b/src/actions.rs index 5fc0b752e..d8684e9fb 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -162,8 +162,11 @@ define_actions! { FullScreenViewer { cpp: "fullscreenviewer", i18n: "menu.view.full_screen_viewer", keys: [], route: FocusedPanel, menu_id: 1044 }; ThemeDark { cpp: "themedark", i18n: "menu.view.theme.dark", keys: [], route: Global, menu_id: 301 }; ThemeLight { cpp: "themelight", i18n: "menu.view.theme.light", keys: [], route: Global, menu_id: 302 }; - LangZh { cpp: "langzh", i18n: "menu.view.language.zh", keys: [], route: Global, menu_id: 303 }; - LangEn { cpp: "langen", i18n: "menu.view.language.en", keys: [], route: Global, menu_id: 304 }; + // Language switching is deliberately NOT a registry action: the + // language menu is built from the discovered packs (menu ids in the + // LANG_ITEM_BASE range, see menus::shared), so a new language is a new + // YAML file, not a code change. The C++ `langzh`/`langen` action slots + // (menu ids 303/304) stay reserved. // --- Playback ----------------------------------------------------------- GoToStart { cpp: "gotostart", i18n: "menu.playback.to_start", keys: ["home"], route: FocusedPanel, menu_id: 404 }; @@ -820,8 +823,8 @@ mod tests { /// is meant to drive the menus). #[test] fn every_action_appears_in_the_menu_tree() { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(crate::i18n::Language::EnUs); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); fn collect(menu: &gpui_widgets::menu::Menu, out: &mut Vec) { for item in &menu.items { @@ -866,18 +869,22 @@ mod tests { #[test] fn every_i18n_key_exists_in_both_languages() { for entry in REGISTRY { - for language in [crate::i18n::Language::EnUs, crate::i18n::Language::ZhCN] { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(language); + for code in ["en-US", "zh-CN"] { + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code(code); let value = crate::i18n::tr(entry.i18n_key); assert_ne!( value, entry.i18n_key, - "i18n key {} ({language:?}) is missing for action {}", + "i18n key {} ({code}) is missing for action {}", entry.i18n_key, entry.cpp_id ); assert!(!value.is_empty()); } } + // The loop leaves zh-CN active; restore the default so lock-free + // tests building English menus are not fooled mid-assert. + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); } /// The shortcut display formatter renders the documented labels for a diff --git a/src/app.rs b/src/app.rs index fa00f870e..455ded477 100644 --- a/src/app.rs +++ b/src/app.rs @@ -95,8 +95,6 @@ pub(crate) mod menu_ids { pub const RIPPLE_DELETE: usize = ActionId::RippleDelete.menu_id(); pub const THEME_DARK: usize = ActionId::ThemeDark.menu_id(); - pub const LANG_ZH: usize = ActionId::LangZh.menu_id(); - pub const LANG_EN: usize = ActionId::LangEn.menu_id(); pub const PREFERENCES: usize = ActionId::Preferences.menu_id(); pub const FOCUS_PROJECT: usize = ActionId::FocusProject.menu_id(); @@ -759,10 +757,20 @@ impl OakApp { } } - /// Routes a menu click: resolves the item id to its registry action and - /// dispatches it through the same path the keyboard shortcuts use, so a - /// menu click and a key press can never diverge. + /// Routes a menu click: the dynamic language items (one per discovered + /// pack, above the registry id range) switch the language directly; + /// everything else resolves to its registry action and dispatches + /// through the same path the keyboard shortcuts use, so a menu click + /// and a key press can never diverge. fn on_menu(&mut self, item: usize, cx: &mut Context) { + if let Some(index) = crate::menus::shared::language_item_index(item) { + let languages = crate::i18n::available_languages(); + if let Some(code) = languages.get(index) { + let code = code.clone(); + self.switch_language(&code, cx); + } + return; + } let Some(entry) = crate::actions::entry_for_menu_id(item) else { println!("[menu] unknown item {item}"); return; @@ -910,8 +918,6 @@ impl OakApp { println!("[view] full screen: {} (placeholder)", self.full_screen); self.rebuild_menu_bar(cx); } - A::LangZh => self.switch_language(crate::i18n::Language::ZhCN, cx), - A::LangEn => self.switch_language(crate::i18n::Language::EnUs, cx), A::Preferences => self.open_preferences(cx), // --- Playback (the program monitor) ---------------------------- A::PlayPause => { @@ -1338,8 +1344,8 @@ impl OakApp { /// Switches the UI language live: updates the [`i18n`] global, rebuilds /// the menu bar (so the menu labels and the language checkmark move /// immediately), and repaints the whole shell. - fn switch_language(&mut self, language: crate::i18n::Language, cx: &mut Context) { - crate::i18n::set_language(language); + fn switch_language(&mut self, code: &str, cx: &mut Context) { + crate::i18n::set_language_code(code); self.rebuild_menu_bar(cx); cx.notify(); } @@ -2362,12 +2368,6 @@ fn make_menus(state: MenuState) -> Vec { menu_item(A::ThemeLight).with_checked(!state.dark), ]); - let language = crate::i18n::language(); - let language_submenu = Menu::new(vec![ - menu_item(A::LangZh).with_checked(language == crate::i18n::Language::ZhCN), - menu_item(A::LangEn).with_checked(language == crate::i18n::Language::EnUs), - ]); - // 工具: the mutually exclusive tool group in the registry's order, the // add tool growing its addable-items submenu, then snapping + proxy. let mut tools: Vec = Vec::new(); @@ -2478,7 +2478,7 @@ fn make_menus(state: MenuState) -> Vec { tr("menu.view"), Menu::new(vec![ menu_item(A::ThemeDark).with_submenu(theme_submenu), - menu_item(A::LangZh).with_submenu(language_submenu), + crate::menus::shared::language_menu(), menu_item(A::ZoomIn).separated(), menu_item(A::ZoomOut), menu_item(A::IncreaseTrackHeight), @@ -2751,7 +2751,7 @@ mod tests { fn language_menu_tracks_the_active_language() { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let view_entry = |dark: bool| -> MenuBarEntry { make_menus(MenuState::new(dark)) @@ -2766,46 +2766,54 @@ mod tests { .menu .items .iter() - .find(|item| item.id == menu_ids::LANG_ZH) + .find(|item| item.label == crate::i18n::tr("menu.view.language")) .map(|item| item.submenu.clone().map(|m| *m).unwrap_or_default()) .expect("语言/Language submenu exists") }; - crate::i18n::set_language(crate::i18n::Language::EnUs); + crate::i18n::set_language_code("en-US"); + let languages = crate::i18n::available_languages(); + let en_index = languages.iter().position(|c| c == "en-US").unwrap(); + let zh_index = languages.iter().position(|c| c == "zh-CN").unwrap(); let submenu = language_item(&view_entry(true)); + assert_eq!( + submenu.items.len(), + languages.len(), + "one menu item per discovered language pack" + ); let zh = submenu .items .iter() - .find(|i| i.id == menu_ids::LANG_ZH) + .find(|i| i.id == crate::menus::shared::LANG_ITEM_BASE + zh_index) .expect("zh item"); let en = submenu .items .iter() - .find(|i| i.id == menu_ids::LANG_EN) + .find(|i| i.id == crate::menus::shared::LANG_ITEM_BASE + en_index) .expect("en item"); - assert_eq!(zh.label, "简体中文"); - assert_eq!(en.label, "English"); + assert_eq!(zh.label, "简体中文 (zh-CN)"); + assert_eq!(en.label, "English (en-US)"); assert_eq!(zh.checked, Some(false)); assert_eq!(en.checked, Some(true), "en-US is active → checked"); - crate::i18n::set_language(crate::i18n::Language::ZhCN); + crate::i18n::set_language_code("zh-CN"); let submenu = language_item(&view_entry(true)); let zh = submenu .items .iter() - .find(|i| i.id == menu_ids::LANG_ZH) + .find(|i| i.id == crate::menus::shared::LANG_ITEM_BASE + zh_index) .expect("zh item"); let en = submenu .items .iter() - .find(|i| i.id == menu_ids::LANG_EN) + .find(|i| i.id == crate::menus::shared::LANG_ITEM_BASE + en_index) .expect("en item"); assert_eq!(zh.checked, Some(true), "zh-CN is active → checked"); assert_eq!(en.checked, Some(false)); // The menu titles themselves are localized. assert_eq!(view_entry(true).title, "视图(V)"); - crate::i18n::set_language(crate::i18n::Language::EnUs); + crate::i18n::set_language_code("en-US"); assert_eq!(view_entry(true).title, "View(V)"); } @@ -2814,7 +2822,7 @@ mod tests { fn theme_menu_checkmark_follows_dark_flag() { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let dark_item = |dark: bool| -> gpui_widgets::menu::MenuItem { let entries = make_menus(MenuState::new(dark)); @@ -2845,8 +2853,8 @@ mod tests { #[test] fn window_menu_lists_all_panels_and_checks_the_open_ones() { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(crate::i18n::Language::EnUs); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); let mask = panel_bit(INSPECTOR) | panel_bit(TIMELINE); let mut state = MenuState::new(true); @@ -2878,7 +2886,7 @@ mod tests { #[gpui::test] async fn window_menu_toggle_closes_and_reopens_the_inspector(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (_window, root) = mock_shell(cx); // The checkmark shown in the 窗口 menu for the inspector, derived from @@ -2932,7 +2940,7 @@ mod tests { cx: &mut TestAppContext, ) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); let menu_bar_before = cx.read(|app| root.read(app).menu_bar.entity_id()); @@ -2965,7 +2973,7 @@ mod tests { fn file_and_edit_menus_cover_the_project_lifecycle() { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let entry = |title: &str| -> MenuBarEntry { make_menus_for_test() @@ -2974,7 +2982,7 @@ mod tests { .expect("menu exists") }; - crate::i18n::set_language(crate::i18n::Language::EnUs); + crate::i18n::set_language_code("en-US"); let file = entry("File(F)"); for id in [ menu_ids::NEW_PROJECT, @@ -3005,13 +3013,15 @@ mod tests { } // The same ids exist in the zh-CN menu bar. - crate::i18n::set_language(crate::i18n::Language::ZhCN); + crate::i18n::set_language_code("zh-CN"); let file = entry("文件(F)"); assert!(file .menu .items .iter() .any(|item| item.id == menu_ids::EXPORT_PROJECT)); + // Restore the default language for the next (lock-free) test. + crate::i18n::set_language_code("en-US"); } /// Opening 视图 → Preferences… must not crash: the dialog content and the @@ -3021,8 +3031,8 @@ mod tests { async fn preferences_dialog_opens_without_crashing(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(crate::i18n::Language::EnUs); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); cx.update(|cx| cx.init_colors()); let window = cx.open_window(size(px(1600.0), px(900.0)), |window, cx| { @@ -3058,8 +3068,8 @@ mod tests { fn every_shortcut_maps_to_a_menu_item() { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(crate::i18n::Language::EnUs); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); fn collect(menu: &Menu, out: &mut Vec) { for item in &menu.items { @@ -3104,8 +3114,8 @@ mod tests { fn menu_shortcut_labels_match_the_table() { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(crate::i18n::Language::EnUs); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); fn walk(menu: &Menu) -> Vec<(usize, Option)> { let mut out = Vec::new(); @@ -3140,7 +3150,7 @@ mod tests { async fn space_toggles_program_playback(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); let playing = cx.read(|app| root.read(app).program_clock.read(app).is_playing()); @@ -3172,7 +3182,7 @@ mod tests { async fn keymap_defaults_dispatch_their_actions(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); let key = |keystroke: &str| gpui::Keystroke::parse(keystroke).unwrap(); @@ -3285,7 +3295,7 @@ mod tests { async fn edit_shortcuts_dispatch_to_the_engine(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); // Move the playhead inside the first clip (the → shortcut steps one @@ -3341,7 +3351,7 @@ mod tests { async fn shortcuts_are_suppressed_while_a_modal_is_open(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); cx.update(|app| root.update(app, |app, cx| app.on_menu(menu_ids::PREFERENCES, cx))); @@ -3404,7 +3414,7 @@ mod tests { #[gpui::test] async fn action_search_opens_from_the_slash_key(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _lang = crate::i18n::lang_test_lock().lock().unwrap(); + let _lang = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); // A leftover real shortcuts file must not shadow the default `/` // binding under test. @@ -3443,7 +3453,7 @@ mod tests { cx: &mut TestAppContext, ) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _lang = crate::i18n::lang_test_lock().lock().unwrap(); + let _lang = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); crate::actions::reset_all_custom_shortcuts(); cx.update(|app| root.update(app, |app, cx| app.rebind_keys(cx))); @@ -3481,7 +3491,7 @@ mod tests { cx: &mut TestAppContext, ) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _lang = crate::i18n::lang_test_lock().lock().unwrap(); + let _lang = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); crate::actions::reset_all_custom_shortcuts(); cx.update(|app| root.update(app, |app, cx| app.rebind_keys(cx))); @@ -3515,7 +3525,7 @@ mod tests { #[gpui::test] async fn keyboard_tab_capture_assigns_a_shortcut(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _lang = crate::i18n::lang_test_lock().lock().unwrap(); + let _lang = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); crate::actions::reset_all_custom_shortcuts(); cx.update(|app| root.update(app, |app, cx| app.on_menu(menu_ids::PREFERENCES, cx))); @@ -3569,7 +3579,7 @@ mod tests { #[gpui::test] async fn keyboard_tab_capture_escape_cancels_without_closing(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _lang = crate::i18n::lang_test_lock().lock().unwrap(); + let _lang = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); crate::actions::reset_all_custom_shortcuts(); cx.update(|app| root.update(app, |app, cx| app.on_menu(menu_ids::PREFERENCES, cx))); @@ -3611,7 +3621,7 @@ mod tests { #[gpui::test] async fn custom_shortcut_overrides_are_live_after_rebind(cx: &mut TestAppContext) { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _lang = crate::i18n::lang_test_lock().lock().unwrap(); + let _lang = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); crate::actions::reset_all_custom_shortcuts(); @@ -3645,8 +3655,8 @@ mod tests { /// mock engine records it, so the async round trip is observable. #[gpui::test] async fn import_footage_prompts_and_routes_the_picked_path(cx: &mut TestAppContext) { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(crate::i18n::Language::EnUs); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); cx.update(|cx| cx.init_colors()); let window = cx.open_window(size(px(1600.0), px(900.0)), |window, cx| { @@ -3694,7 +3704,7 @@ mod tests { /// delivers it — the OS drag entering the window, then the release. #[gpui::test] async fn dropping_files_onto_the_project_browser_imports_them(cx: &mut TestAppContext) { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); let mut cx = VisualTestContext::from_window(window.into(), cx); @@ -3743,7 +3753,7 @@ mod tests { /// correct parameters. #[gpui::test] async fn dragging_footage_onto_the_timeline_places_a_clip(cx: &mut TestAppContext) { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, root) = mock_shell(cx); let mut cx = VisualTestContext::from_window(window.into(), cx); @@ -3822,7 +3832,7 @@ mod tests { /// grid the same entry (plus folder children) appears as icons. #[gpui::test] async fn explorer_views_list_root_level_footage(cx: &mut TestAppContext) { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (window, _root) = mock_shell(cx); let mut cx = VisualTestContext::from_window(window.into(), cx); @@ -3905,7 +3915,7 @@ mod tests { gpui::WindowHandle>, Entity>, ) { - crate::i18n::set_language(crate::i18n::Language::EnUs); + crate::i18n::set_language_code("en-US"); cx.update(|cx| cx.init_colors()); let window = cx.open_window(size(px(1600.0), px(900.0)), |window, cx| { OakApp::::new(window, None, cx) @@ -3941,7 +3951,7 @@ mod tests { /// / 打开 route) drives the engine's library open and closes the dialog. #[gpui::test] async fn manager_lists_and_opens(cx: &mut TestAppContext) { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (_window, root) = mock_shell(cx); let content = open_manager(cx, &root); let rows = manager_rows(cx, &content); @@ -3971,7 +3981,7 @@ mod tests { /// and its sub-dialogs. #[gpui::test] async fn manager_create_rename_duplicate_delete(cx: &mut TestAppContext) { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (_window, root) = mock_shell(cx); let content = open_manager(cx, &root); @@ -4080,7 +4090,7 @@ mod tests { async fn manager_import_and_export_route_through_the_platform_dialogs( cx: &mut TestAppContext, ) { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); let (_window, root) = mock_shell(cx); let content = open_manager(cx, &root); diff --git a/src/dialogs.rs b/src/dialogs.rs index 86917e26b..df0cb6132 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -148,26 +148,34 @@ impl PreferencesContent { }); // --- 常规 General: language + theme -------------------------------- - let language_options = vec![ - ComboBoxOption::new(0, "English (en-US)"), - ComboBoxOption::new(1, "简体中文 (zh-CN)"), - ]; + // Data-driven: one option per discovered language pack, labelled + // with the pack's own `language.name` — a newly dropped-in pack + // appears here with no code change. + let languages = i18n::available_languages(); + let language_options: Vec<_> = languages + .iter() + .enumerate() + .map(|(index, code)| { + ComboBoxOption::new( + index, + format!("{} ({code})", i18n::pack_native_name(code)), + ) + }) + .collect(); let language = cx.new(|cx| { ComboBox::new(2, language_options, window, cx) .with_placeholder(i18n::tr("preferences.language.placeholder")) }); - let language_selected = match crate::i18n::language() { - crate::i18n::Language::EnUs => 0, - crate::i18n::Language::ZhCN => 1, - }; - cx.subscribe(&language, |_this, _combo, event: &ComboBoxEvent, cx| { + let language_selected = languages + .iter() + .position(|code| *code == i18n::language_code()) + .unwrap_or(0); + cx.subscribe(&language, move |_this, _combo, event: &ComboBoxEvent, cx| { if let ComboBoxEvent::Selected { value, .. } = event { - let language = match *value { - 1 => crate::i18n::Language::ZhCN, - _ => crate::i18n::Language::EnUs, - }; - crate::i18n::set_language(language); - cx.emit(PreferencesEvent::LanguageChanged); + if let Some(code) = languages.get(*value) { + crate::i18n::set_language_code(code); + cx.emit(PreferencesEvent::LanguageChanged); + } } }) .detach(); @@ -2308,8 +2316,8 @@ mod tests { #[test] fn search_filter_matches_label_or_path() { - let _guard = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(crate::i18n::Language::EnUs); + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); assert!(search_filter_matches(ActionId::NewProject, "File", "new")); assert!(search_filter_matches(ActionId::NewProject, "File", "file > new")); assert!(!search_filter_matches(ActionId::NewProject, "File", "undo")); @@ -2329,8 +2337,8 @@ mod tests { #[test] fn keyboard_rows_cover_the_menu_bar() { let _guard = crate::actions::shortcuts_test_lock().lock().unwrap(); - let _lang = crate::i18n::lang_test_lock().lock().unwrap(); - crate::i18n::set_language(crate::i18n::Language::EnUs); + let _lang = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); let rows = crate::app::menu_action_paths(); assert!(!rows.is_empty()); // Every listed action resolves to a registry entry with a menu item. diff --git a/src/i18n.rs b/src/i18n.rs index 5734a970d..b995c2509 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -14,21 +14,20 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Localization: UI strings live in YAML language packs under -//! `assets/i18n/` (one `.yaml` per language, e.g. `en.yaml`, -//! `zh-CN.yaml`), loaded at runtime and shipped inside the app bundle. -//! Users can add or override languages by dropping extra `.yaml` files -//! into the user pack directory (`~/.oak/i18n/`) — no rebuild needed. +//! Localization: UI strings live in YAML language packs discovered at +//! runtime (one `.yaml` per language — the file stem IS the +//! language code, e.g. `en-US.yaml`, `zh-CN.yaml`). Nothing is compiled in: adding a language means +//! dropping one more `.yaml` into any searched directory — no rebuild, +//! no code change. Each pack carries its own display name in the +//! `language.name` key (the endonym, e.g. "Français"), which the +//! language pickers show. //! //! # Lookup order //! //! 1. The user pack directory (`~/.oak/i18n/`); -//! 2. The bundled packs (next to the executable, or the app bundle's -//! `Resources/i18n/`); -//! 3. The dev checkout's `assets/i18n/` (running from the repo); -//! 4. The compiled-in English/Chinese tables (the same YAML embedded -//! with `include_str!`) — the last-resort fallback so a broken -//! install still renders English. +//! 2. The bundled packs (next to the executable, the app bundle's +//! `Resources/i18n/`, or the system install's `/../share/oak/i18n/`); +//! 3. The dev checkout's `assets/i18n/` (running from the repo). //! //! Later sources override earlier ones per key. `tr` falls back from //! the active language to `en-US` and then to the key itself, so a @@ -36,148 +35,80 @@ //! //! # The language setting //! -//! The active language code is a process-global string (an atomic-free +//! The active language code is a process-global string (behind an //! `RwLock` — any thread can read it). At startup [`init`] loads the //! persisted value from the config key `Language`; unknown codes fall -//! back to `en-US`. [`set_language`] flips the global and persists. +//! back to `en-US`. [`set_language_code`] flips the global and persists. use std::collections::HashMap; -use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::{OnceLock, RwLock}; -/// The languages shipped with the app (compiled-in packs). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Language { - /// English (United States). - EnUs, - /// Simplified Chinese. - ZhCN, -} - -impl Language { - /// The stable config/pack-file code, e.g. `"en-US"`. - pub fn code(self) -> &'static str { - match self { - Language::EnUs => "en-US", - Language::ZhCN => "zh-CN", - } - } - - /// Parses a stored config value into a built-in language. Empty or - /// unknown values fall back to en-US (a custom pack's code is kept - /// verbatim by [`set_language_code`]). - fn from_code(code: &str) -> Self { - let code = code.trim().to_ascii_lowercase(); - if code.starts_with("zh") { - Language::ZhCN - } else { - Language::EnUs - } - } -} - -/// The built-in (compiled-in) packs, parsed from the same YAML files the -/// bundle ships: the YAML is the single source of truth, embedded so a -/// missing/corrupt install still renders English. -const BUILTIN_PACKS: &[(&str, &str)] = &[ - ("en-US", include_str!("../assets/i18n/en.yaml")), - ("zh-CN", include_str!("../assets/i18n/zh-CN.yaml")), -]; +/// The fallback language code: the reference pack every other pack is +/// key-compared against, the config default, and `tr`'s last resort +/// before the raw key. +pub const FALLBACK_CODE: &str = "en-US"; /// One language's key → value table (values are leaked once at load — /// the tables live for the whole process). type Table = HashMap; -/// All loaded packs: language code → table. +/// All discovered packs: language code → table. static TABLES: OnceLock>> = OnceLock::new(); -/// The active language code (0/1 fast path for the two built-ins; a -/// custom pack's code lives in [`CURRENT_CUSTOM`]). -static CURRENT: AtomicU8 = AtomicU8::new(0); -static CURRENT_CUSTOM: RwLock> = RwLock::new(None); +/// The active language code. Empty means "not yet initialized" and reads +/// as [`FALLBACK_CODE`]. +static CURRENT_CODE: RwLock = RwLock::new(String::new()); -/// The active language (the built-in view of the current code; custom -/// packs report the nearest built-in). -pub fn language() -> Language { - if CURRENT_CUSTOM - .read() - .unwrap_or_else(|e| e.into_inner()) - .is_some() - { - return match CURRENT.load(Ordering::Relaxed) { - 1 => Language::ZhCN, - _ => Language::EnUs, - }; - } - match CURRENT.load(Ordering::Relaxed) { - 1 => Language::ZhCN, - _ => Language::EnUs, - } -} - -/// The active language code (`en-US`, `zh-CN`, or a pack's code). +/// The active language code (a discovered pack's code, or `en-US`). pub fn language_code() -> String { - if let Some(custom) = CURRENT_CUSTOM - .read() - .unwrap_or_else(|e| e.into_inner()) - .clone() - { - return custom; + let code = CURRENT_CODE.read().unwrap_or_else(|e| e.into_inner()); + if code.is_empty() { + FALLBACK_CODE.to_string() + } else { + code.clone() } - language().code().to_string() } -/// Switches the active language live (built-in) and persists the choice. -pub fn set_language(language: Language) { - CURRENT.store( - match language { - Language::EnUs => 0, - Language::ZhCN => 1, - }, - Ordering::Relaxed, - ); - CURRENT_CUSTOM - .write() - .unwrap_or_else(|e| e.into_inner()) - .take(); - persist_language(language.code()); - sync_widgets(); -} - -/// Switches the active language to any loaded pack's code (a custom -/// language pack). Unknown codes fall back to en-US. +/// Switches the active language to any discovered pack's code and +/// persists the choice. Unknown codes fall back to en-US. pub fn set_language_code(code: &str) { - if code == Language::EnUs.code() || code == Language::ZhCN.code() { - set_language(Language::from_code(code)); - return; - } let exists = tables() .read() .unwrap_or_else(|e| e.into_inner()) .contains_key(code); - if !exists { - set_language(Language::EnUs); - return; - } - *CURRENT_CUSTOM - .write() - .unwrap_or_else(|e| e.into_inner()) = Some(code.to_string()); + let code = if exists { code } else { FALLBACK_CODE }; + *CURRENT_CODE.write().unwrap_or_else(|e| e.into_inner()) = code.to_string(); persist_language(code); sync_widgets(); } -/// The language codes with a loaded pack, built-ins first. +/// A pack's own display name — its `language.name` value (the endonym, +/// e.g. "Français") — which the language pickers show, so a newly +/// dropped-in pack needs no code change. Falls back to the code. +pub fn pack_native_name(code: &str) -> String { + tables() + .read() + .unwrap_or_else(|e| e.into_inner()) + .get(code) + .and_then(|t| t.get("language.name")) + .map(|s| s.to_string()) + .unwrap_or_else(|| code.to_string()) +} + +/// The language codes with a discovered pack: en-US first, zh-CN +/// second, the rest alphabetically by code. pub fn available_languages() -> Vec { let tables = tables().read().unwrap_or_else(|e| e.into_inner()); - let mut codes = vec![Language::EnUs.code().to_string(), Language::ZhCN.code().to_string()]; - let mut extra: Vec = tables - .keys() - .filter(|code| !codes.contains(code)) - .cloned() - .collect(); - extra.sort(); - codes.extend(extra); - codes + let mut codes: Vec = tables.keys().cloned().collect(); + codes.sort(); + let mut out = Vec::with_capacity(codes.len()); + for preferred in [FALLBACK_CODE, "zh-CN"] { + if let Some(pos) = codes.iter().position(|c| c == preferred) { + out.push(codes.remove(pos)); + } + } + out.extend(codes); + out } /// Loads the persisted language from the config. Called once at startup. @@ -204,7 +135,7 @@ pub fn tr(key: &'static str) -> &'static str { if let Some(value) = tables.get(¤t).and_then(|t| t.get(key)) { return value; } - if let Some(value) = tables.get(Language::EnUs.code()).and_then(|t| t.get(key)) { + if let Some(value) = tables.get(FALLBACK_CODE).and_then(|t| t.get(key)) { return value; } key @@ -248,9 +179,8 @@ fn tables() -> &'static RwLock> { TABLES.get_or_init(|| RwLock::new(load_packs())) } -/// The directories searched for packs, lowest precedence first: the -/// compiled-in tables are the base; each directory's packs override the -/// previous source per key. +/// The directories searched for packs, lowest precedence first; each +/// directory's packs override the previous source per key. fn pack_dirs() -> Vec { let mut dirs = Vec::new(); // Dev checkout (running from the repo root or a subdir). @@ -259,9 +189,11 @@ fn pack_dirs() -> Vec { if let Some(dir) = exe.parent() { // Portable layout: /i18n. dirs.push(dir.join("i18n")); - // macOS bundle: /../Resources/i18n. if let Some(contents) = dir.parent() { + // macOS bundle: /../Resources/i18n. dirs.push(contents.join("Resources/i18n")); + // System install (deb/rpm/pkg): /../share/oak/i18n. + dirs.push(contents.join("share/oak/i18n")); } } } @@ -289,14 +221,11 @@ fn parse_pack(code: &str, source: &str) -> Table { table } -/// Loads the built-in packs and every pack file found in [`pack_dirs`] -/// (later sources override earlier ones per key; a file named -/// `.yaml` registers or extends language ``). +/// Loads every pack file found in [`pack_dirs`] (later sources override +/// earlier ones per key; a file named `.yaml` registers or extends +/// language ``). fn load_packs() -> HashMap { let mut packs: HashMap = HashMap::new(); - for (code, source) in BUILTIN_PACKS { - packs.insert(code.to_string(), parse_pack(code, source)); - } for dir in pack_dirs() { let Ok(read_dir) = std::fs::read_dir(&dir) else { continue; @@ -324,6 +253,12 @@ fn load_packs() -> HashMap { .extend(table); } } + if packs.is_empty() { + eprintln!( + "[i18n] no language packs found in {:?}; UI strings degrade to raw keys", + pack_dirs() + ); + } packs } @@ -338,18 +273,35 @@ fn reload_for_test() { mod tests { use super::*; - /// The two compiled-in packs parse and cover the same key set. + /// Every shipped pack (the YAML files in `assets/i18n/`) parses and + /// covers exactly the en-US key set, and carries its `language.name` + /// display name. User packs from `$HOME/.oak/i18n` are deliberately + /// not held to the key set (a partial override pack is legitimate). #[test] - fn every_key_exists_in_both_languages() { + fn every_shipped_pack_matches_en_keys() { let tables = tables().read().unwrap_or_else(|e| e.into_inner()); let en = tables.get("en-US").expect("en-US pack"); - let zh = tables.get("zh-CN").expect("zh-CN pack"); - assert!(!en.is_empty() && !zh.is_empty(), "packs parse to non-empty tables"); - for key in en.keys() { - assert!(zh.contains_key(key), "key {key} missing from the zh-CN pack"); - } - for key in zh.keys() { - assert!(en.contains_key(key), "key {key} missing from the en-US pack"); + assert!(!en.is_empty(), "en-US pack parses to a non-empty table"); + for entry in std::fs::read_dir("assets/i18n").expect("dev checkout packs") { + let path = entry.unwrap().path(); + if path.extension().and_then(|e| e.to_str()) != Some("yaml") { + continue; + } + let code = path.file_stem().unwrap().to_str().unwrap().to_string(); + let table = tables + .get(&code) + .unwrap_or_else(|| panic!("{code} pack")); + assert!(!table.is_empty(), "{code} pack parses to a non-empty table"); + assert!( + table.contains_key("language.name"), + "{code} pack has no language.name display name" + ); + for key in en.keys() { + assert!(table.contains_key(key), "key {key} missing from the {code} pack"); + } + for key in table.keys() { + assert!(en.contains_key(key), "key {key} missing from the en-US pack"); + } } } @@ -391,18 +343,21 @@ mod tests { #[test] fn tr_falls_back_to_english_then_the_key() { let _guard = crate::i18n::test_lock(); - set_language(Language::ZhCN); + set_language_code("zh-CN"); assert_eq!(tr("menu.file"), "文件(F)"); - set_language(Language::EnUs); + set_language_code("en-US"); assert_eq!(tr("menu.file"), "File(F)"); assert_eq!(tr("no.such.key"), "no.such.key"); } /// A custom pack on disk overrides built-in values and registers a - /// new language. + /// new language. The active language is pinned to en-US: the user + /// pack overrides the built-in en-US table, so the assertions below + /// must not inherit another test's active language. #[test] fn user_pack_overrides_and_extends() { let _guard = crate::i18n::test_lock(); + set_language_code("en-US"); let dir = std::env::temp_dir().join(format!("oak_i18n_pack_{}", std::process::id())); let home = dir.join("home"); let pack_dir = home.join(".oak/i18n"); @@ -419,6 +374,8 @@ mod tests { unsafe { std::env::remove_var("HOME") }; reload_for_test(); let _ = std::fs::remove_dir_all(&dir); + // Leave the process language at the default for the next test. + set_language_code("en-US"); } } diff --git a/src/menus/shared.rs b/src/menus/shared.rs index 1caa77878..35eacee64 100644 --- a/src/menus/shared.rs +++ b/src/menus/shared.rs @@ -43,6 +43,41 @@ pub const COLOR_LABEL_BASE: usize = LOCAL_ID_BASE; /// gray). pub const COLOR_LABEL_COUNT: usize = 16; +/// The first id reserved for the dynamic language items: one per +/// discovered language pack, `LANG_ITEM_BASE + index` into +/// [`crate::i18n::available_languages`]. Language switching is +/// data-driven (a new pack is a new YAML file, not a new registry +/// action), so these ids live above the registry range like the color +/// labels. +pub const LANG_ITEM_BASE: usize = COLOR_LABEL_BASE + COLOR_LABEL_COUNT; + +/// The reserved language-item id range (far more than any plausible +/// pack count). +pub const LANG_ITEM_COUNT: usize = 32; + +/// Maps a menu item id in the language range back to its index into +/// [`crate::i18n::available_languages`]. +pub fn language_item_index(item: usize) -> Option { + (item >= LANG_ITEM_BASE && item < LANG_ITEM_BASE + LANG_ITEM_COUNT) + .then(|| item - LANG_ITEM_BASE) +} + +/// Builds the language submenu: one item per discovered pack, labelled +/// with the pack's own `language.name` endonym plus its code, the active +/// pack checkmarked. +pub fn language_menu() -> MenuItem { + let current = crate::i18n::language_code(); + let items = crate::i18n::available_languages() + .iter() + .enumerate() + .map(|(index, code)| { + let label = format!("{} ({code})", crate::i18n::pack_native_name(code)); + MenuItem::new(LANG_ITEM_BASE + index, label).with_checked(*code == current) + }) + .collect(); + MenuItem::new(0, crate::i18n::tr("menu.view.language")).with_submenu(Menu::new(items)) +} + /// One menu item straight from the registry: id and label come from the /// entry, the shortcut annotation from /// [`display_shortcut`](crate::actions::display_shortcut) — the same recipe @@ -375,6 +410,10 @@ mod tests { /// defaults each radio group to its first entry. #[test] fn viewer_menu_offers_every_zoom_level() { + // The label lookups below race with tests that flip the process + // language: pin en-US under the shared lock. + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); let menu = viewer_menu(1); let zoom = menu .items @@ -394,6 +433,10 @@ mod tests { /// (default) entry only. #[test] fn viewer_menu_radio_groups_default_to_the_first_entry() { + // The label lookups below race with tests that flip the process + // language: pin en-US under the shared lock. + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); let menu = viewer_menu(1); for label_key in [ "viewer.context.playback_resolution", @@ -417,6 +460,9 @@ mod tests { /// The resolution radio follows the current playback divider. #[test] fn viewer_menu_resolution_radio_follows_the_divider() { + // Label lookup: pin en-US under the shared language lock. + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); let menu = viewer_menu(4); let item = menu .items diff --git a/src/panels/mod.rs b/src/panels/mod.rs index 0346d0995..038f05b48 100644 --- a/src/panels/mod.rs +++ b/src/panels/mod.rs @@ -78,15 +78,17 @@ pub(crate) fn chip(colors: &gpui::colors::Colors, label: impl gpui::IntoElement) } /// The viewer panel title, per the design: `<面板>·<素材/序列名>` in zh-CN, -/// ` · ` in en-US — no redundant "Source"/"Program" placeholder -/// suffix. The panel key is the localized panel name; `name` is the media or -/// sequence name (data, not translated). +/// ` · ` everywhere else — no redundant "Source"/"Program" +/// placeholder suffix. The panel key is the localized panel name; `name` is +/// the media or sequence name (data, not translated). pub(crate) fn viewer_title(panel_key: &'static str, name: &str) -> String { if name.is_empty() { return crate::i18n::tr(panel_key).to_owned(); } - match crate::i18n::language() { - crate::i18n::Language::ZhCN => format!("{}·{}", crate::i18n::tr(panel_key), name), - crate::i18n::Language::EnUs => format!("{} · {}", crate::i18n::tr(panel_key), name), + // CJK locales read better without the spaced separator. + if crate::i18n::language_code() == "zh-CN" { + format!("{}·{}", crate::i18n::tr(panel_key), name) + } else { + format!("{} · {}", crate::i18n::tr(panel_key), name) } } diff --git a/src/panels/node_editor.rs b/src/panels/node_editor.rs index 7f36376d6..aa6f98a26 100644 --- a/src/panels/node_editor.rs +++ b/src/panels/node_editor.rs @@ -574,6 +574,10 @@ mod tests { /// labels, the reveal entries and properties. #[test] fn node_menu_carries_grouping_and_reveals() { + // The color-label lookup below matches on a localized label: pin + // en-US under the shared language lock. + let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); + crate::i18n::set_language_code("en-US"); let menu = node_menu(); let ids: Vec = menu.items.iter().map(|item| item.id).collect(); assert!(ids.contains(&LOCAL_GROUP));