From 3c1a23462c57f5518f506d08793629ec74763217 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:40:31 +0000 Subject: [PATCH 01/25] feat(desktop-gpui): localize the editor sidebar, segment panels, colour correction, audio library and crop dialog --- apps/desktop-gpui/locales/de.toml | 518 ++++++ apps/desktop-gpui/locales/en.toml | 518 ++++++ apps/desktop-gpui/locales/es.toml | 518 ++++++ apps/desktop-gpui/locales/fr.toml | 518 ++++++ apps/desktop-gpui/locales/hi.toml | 518 ++++++ apps/desktop-gpui/locales/ja.toml | 518 ++++++ apps/desktop-gpui/locales/zh-CN.toml | 518 ++++++ apps/desktop-gpui/src/editor_audio.rs | 14 +- apps/desktop-gpui/src/editor_color.rs | 74 +- apps/desktop-gpui/src/editor_crop.rs | 114 +- apps/desktop-gpui/src/editor_panels.rs | 1634 ++++++++++------- apps/desktop-gpui/src/editor_sidebar.rs | 194 +- .../src/editor_sidebar/animated_gradient.rs | 142 +- .../desktop-gpui/src/editor_sidebar/cursor.rs | 47 +- .../src/i18n/editor_panels_surface_tests.rs | 944 ++++++++++ apps/desktop-gpui/src/i18n/mod.rs | 2 + apps/desktop-gpui/src/i18n/tests.rs | 1 + 17 files changed, 5906 insertions(+), 886 deletions(-) create mode 100644 apps/desktop-gpui/src/i18n/editor_panels_surface_tests.rs diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index 6a03591f376..5a16f5a8eca 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -586,3 +586,521 @@ click_or_press = "Hier klicken oder drücken:" get_started = "Los geht’s" space_key = "Leertaste" tagline = "Die Open-Source-App zum Teilen des Bildschirms für alle." + +# Editor sidebar, segment panels, colour correction, audio library and crop dialog. + +[editor.audio.library] +add_hint = "Fügen Sie Ihrem Video Audio, Musik oder andere Klänge hinzu" +add_title = "Audio hinzufügen" +change_hint = "Wählen Sie einen anderen Track für dieses Segment" +change_title = "Audio ändern" +done = "Fertig" +import = "Datei importieren" + +[editor.color] +apply_to_cursor = "Auf Cursor anwenden" +fine_tune = "Farben feinabstimmen" +title = "Farbkorrektur" + +[editor.color.preset.cinematic] +description = "Petrolfarbene Schatten und orange Lichter mit leichter Körnung" +name = "Kino" + +[editor.color.preset.dreamy] +description = "Weiche, luftige Pastelltöne mit angehobenem Schwarz" +name = "Verträumt" + +[editor.color.preset.frost] +description = "Kühle, klare Töne mit gedämpften Farben" +name = "Frost" + +[editor.color.preset.golden] +description = "Warmes Licht der goldenen Stunde" +name = "Golden" + +[editor.color.preset.midnight] +description = "Dunkles, stimmungsvolles Petrol mit gedämpften Farben" +name = "Mitternacht" + +[editor.color.preset.noir] +description = "Kontrastreiches Schwarzweiß mit starker Körnung" +name = "Noir" + +[editor.color.preset.none] +description = "Originalfarben, keine Korrektur" +name = "Keine" + +[editor.color.preset.vintage] +description = "Verblasster, warmer Filmlook mit weichem Kontrast" +name = "Vintage" + +[editor.color.preset.vivid] +description = "Kräftige Sättigung und mehr Kontrast" +name = "Lebendig" + +[editor.color.slider] +contrast = "Kontrast" +exposure = "Belichtung" +fade = "Verblassen" +grain = "Körnung" +saturation = "Sättigung" +split_tone = "Teiltonung" +strength = "Stärke" +temperature = "Temperatur" +tint = "Tönung" +vignette = "Vignette" + +[editor.crop] +aspect_ratio = "Seitenverhältnis" +center = "Zentrieren" +dimensions = "Größe" +editing_style = "Nur Stil {number} wird bearbeitet" +editing_style_override = "Nur Stil {number} wird bearbeitet · Speichern aktiviert seinen eigenen Hintergrund" +free = "Frei" +full = "Voll" +hint = "Ziehen zum Verschieben · Pfeiltasten zum Feinjustieren · {key} halten, um nicht einzurasten" +loading_frame = "Bild wird geladen…" +position = "Position" +preview = "Vorschau" +rendering_preview = "Vorschau wird gerendert…" +snap_to_ratios = "An Verhältnissen ausrichten" +style_changed = "Dieser Stil wurde beim Zuschneiden geändert. Brechen Sie ab und öffnen Sie Zuschneiden erneut." +title = "Zuschneiden" + +[editor.panel] +delete = "Löschen" +done = "Fertig" +duration = "Dauer" +edge_in = "Ein" +edge_out = "Aus" +enabled = "Aktiviert" +fade_duration = "Ausblenddauer" +flip_horizontally = "Horizontal spiegeln" +flip_vertically = "Vertikal spiegeln" +name = "Name" +opacity = "Deckkraft" + +[editor.panel.audio] +change = "Ändern" +change_hint = "Tippen, um den Track zu ändern" +fade_in = "Einblenden" +fade_out = "Ausblenden" +fallback_name = "Audio" +title = "Audio {number}" +volume = "Lautstärke" + +[editor.panel.camera3d] +already_still = "Bereits eine feste Einstellung" +amount = "Stärke" +angles = "Winkel" +auto_scene = "Auto-Szene" +auto_scene_tooltip = "Erstellt alle 3D-Einstellungen der Spur neu" +bokeh = "Bokeh" +camera = "Kamera" +delete_tooltip = "Diese Einstellung löschen" +depth_blur = "Tiefenunschärfe" +distance = "Abstand" +drag_to_orbit = "Ziehen zum Kreisen" +ease_in = "Einblenden" +ease_out = "Ausblenden" +end_same_as_start = "Ende · wie Start" +fine_tune = "Feinabstimmung" +focus = "Fokus" +header = "3D-Einstellung · {look} · {duration} s" +hold_opening = "Die Startpose für die ganze Einstellung halten" +lens = "Objektiv" +look_title = "Look" +motion_style = "Bewegungsstil" +moves = "Fahrten" +nothing_to_swap = "Bei einer festen Einstellung gibt es nichts zu tauschen" +play_shot = "Abspielen" +play_shot_tooltip = "Diese Einstellung abspielen" +pose_end = "Ende · {time}" +pose_start = "Start · {time}" +reset_camera = "Kamera zurücksetzen" +roll = "Rollen" +rotate_x = "Drehung X" +rotate_y = "Drehung Y" +shift_x = "Versatz X" +shift_y = "Versatz Y" +shots = { one = "{count} Einstellung", other = "{count} Einstellungen" } +still_shot = "Feste Einstellung" +swap = "Start und Ende tauschen" +timing_advanced = "Timing & Erweitert" +timing_summary = "{easing} · Objektiv {fov}" + +[editor.panel.camera3d.blur] +angle = "Winkel" +falloff = "Verlauf" +focus_size = "Fokusgröße" +focus_x = "Fokus X" +focus_y = "Fokus Y" +position = "Position" +scan = "Scan" +strength = "Stärke" + +[editor.panel.camera3d.easing] +ease_in = "Beschleunigen" +ease_out = "Abbremsen" +linear = "Linear" +smooth = "Weich" + +[editor.panel.camera3d.focus_mode] +directional = "Gerichtet" +radial = "Radial" +tilt_shift = "Tilt-Shift" + +[editor.panel.camera3d.look] +center = "Mitte" +close_up = "Nahaufnahme" +drift_down = "Absinken" +glide_across = "Seitwärts gleiten" +low_angle = "Untersicht" +perspective = "Perspektive" +pull_back = "Rückzug" +rising_sweep = "Aufschwung" +slide_by = "Vorbeigleiten" +spotlight = "Spotlight" +tilt_away = "Wegkippen" +top_down = "Draufsicht" +unfold = "Aufklappen" + +[editor.panel.camera3d.sequence] +product_tour = "Produkttour" +punch_in = "Heranzoomen" +showcase = "Showcase" + +[editor.panel.camera3d.shot] +custom = "Eigene Fahrt" +still = "Feste Einstellung" + +[editor.panel.caption] +title = "Untertitel {number}" + +[editor.panel.font] +system_mono = "System-Mono" +system_sans = "System-Sans" +system_serif = "System-Serif" + +[editor.panel.font_weight] +black = "Schwarz" +bold = "Fett" +custom = "Benutzerdefiniert ({weight})" +extra_bold = "Extrafett" +light = "Leicht" +medium = "Mittel" +regular = "Normal" +semibold = "Halbfett" + +[editor.panel.image] +arrange = "Auf der Leinwand anordnen" +arrange_help = "Ziehen Sie das Bild, um es zu verschieben. Ziehen Sie an einer Ecke, um die Größe zu ändern, oder drehen Sie es mit der Drehung unten." +center = "Auf der Leinwand zentrieren" +lock_aspect = "Seitenverhältnis sperren" +missing = "Die Bilddatei fehlt. Ersetzen Sie sie, um dieses Segment mit seinem Timing und seinen Transformationen wiederherzustellen." +replace = "Bild ersetzen" +rotation = "Drehung" +rounding = "Rundung" + +[editor.panel.keyboard] +title = "Tastatur {number}" + +[editor.panel.mask] +blur = "Unschärfe" +effect = "Effekt" +highlight = "Hervorheben" +outside_darkness = "Abdunklung außen" +pixel_size = "Pixelgröße" +pixelate = "Verpixeln" +sensitive = "Vertraulich" +title = "Maske {number}" + +[editor.panel.scene] +camera_layout = "Kamera-Layout" +camera_position = "Kameraposition" +camera_zoom = "Kamera-Zoom" +screen_position = "Bildschirmposition" +screen_zoom = "Bildschirm-Zoom" +transition = "Übergang" + +[editor.panel.scene.description] +camera_only = "Zeigt nur das Kamerabild" +default = "Zeigt Bildschirm und Kamera" +floating = "Bildschirm und Kamera schweben nebeneinander als abgerundete Karten über dem Hintergrund" +hide_camera = "Zeigt nur die Bildschirmaufnahme" +split_screen = "Bildschirm und Kamera nebeneinander (im Hochformat automatisch übereinander)" + +[editor.panel.scene.mode] +camera_only = "Nur Kamera" +default = "Standard" +floating = "Schwebend" +hide_camera = "Kamera aus" +split_screen = "Geteilt" + +[editor.panel.style] +camera_only_background = "Hintergrund für Nur Kamera" +camera_only_help = "Verwenden Sie eine Szene „Nur Kamera“. Der Rand zeigt den Hintergrund um die Kamera." +camera_only_padding = "Rand für Nur Kamera" +crop_height = "Höhe" +crop_left = "Links" +crop_title = "Bildschirmzuschnitt (Quellpixel)" +crop_top = "Oben" +crop_width = "Breite" +edit_background = "Hintergrund & Rahmen bearbeiten" +edit_camera = "Kamera bearbeiten" +edit_cursor = "Cursor bearbeiten" +help = "Überschreibungen gelten nur während dieses Segments. Aktivieren Sie eine Gruppe, um ihre globalen Einstellungen zu übernehmen." +reset_crop = "Zuschnitt zurücksetzen" + +[editor.panel.text_animation] +bounce = "Sprung" +fade = "Blende" +letters = "Buchstaben" +none = "Keine" +pop = "Pop" +slide_down = "Nach unten" +slide_left = "Nach links" +slide_right = "Nach rechts" +slide_up = "Nach oben" +tracking = "Laufweite" +typewriter = "Schreibmaschine" +wipe = "Wischen" +words = "Wörter" +zoom = "Zoom" + +[editor.panel.text_background] +box_shape = "Box" +highlight = "Marker" +none = "Kein" +pill = "Pille" + +[editor.panel.text_layout] +fullscreen = "Vollbild" +overlay = "Overlay" + +[editor.panel.text_preset.group] +all = "Alle" +callouts = "Hinweise" +code = "Code" +lower_thirds = "Bauchbinden" +statements = "Aussagen" +titles = "Titel" + +[editor.panel.text_preset.name] +caption = "Beschriftung" +cinematic = "Kino" +code = "Code" +gradient = "Verlauf" +headline = "Schlagzeile" +highlight = "Marker" +kicker = "Dachzeile" +label = "Label" +lower_third = "Bauchbinde" +name_tag = "Namensschild" +neon = "Neon" +quote = "Zitat" +stat = "Große Zahl" +sticker = "Sticker" +title = "Titel" +typewriter = "Schreibmaschine" + +[editor.panel.text_preset.sample] +caption = "Mit Cap aufgenommen" +cinematic = "Kapitel eins" +gradient = "Schöner Text" +headline = "Schneller liefern" +highlight = "das Wichtigste" +kicker = "Neue Funktion" +label = "Profi-Tipp" +name_tag = "Richie · Gründer" +neon = "Glanzauftritt" +quote = "„Mühelos soll es wirken“" +sticker = "Bumm!" +title = "Das ist Cap" +typewriter = "wird getippt…" + +[editor.panel.text_segment] +animation = "Animation" +background = "Hintergrund" +color = "Farbe" +font = "Schrift" +font_size = "Größe" +fullscreen_help = "Hält das Video an, solange der Text angezeigt wird, und setzt es danach an derselben Stelle fort." +glow = "Leuchten" +gradient = "Verlauf" +layout = "Layout" +letter_spacing = "Zeichenabstand" +line_height = "Zeilenhöhe" +look = "Aussehen" +outline = "Kontur" +screen_transition = "Bildschirmübergang" +shadow = "Schatten" +style = "Stil" +title = "Text {number}" + +[editor.panel.timing] +end = "Ende" +start = "Start" +title = "Timing" +to = "bis" + +[editor.panel.zoom] +amount = "Zoomstärke" +auto = "Auto" +auto_help = "Der automatische Zoom folgt Ihrem Cursor über den Bildschirm." +auto_locked = "Der Auto-Modus braucht die Cursor-Erfassung. Aktivieren Sie „Separate Cursor-Erfassung (Studio)“ unter Einstellungen → Allgemein." +how_it_works = "Wie funktioniert das?" +manual = "Manuell" +manual_help = "Der manuelle Zoom bleibt auf der Stelle, die Sie unten wählen." +mixed = "Gemischt" +mode = "Zoom-Modus" +positions_mixed = "Die Segmente zoomen auf unterschiedliche Stellen. Ziehen Sie, um alle auf dieselbe Stelle zu setzen." +title = "Zoom {number}" + +[editor.sidebar] +desktop_import_failed = "Ihr Desktop-Hintergrundbild konnte nicht importiert werden" +importing_image = "Bild wird importiert…" + +[editor.sidebar.animated_gradient] +add_colour = "Farbe hinzufügen" +colours = "Farben" +delete = "Löschen" +delete_failed = "Dieser Verlauf konnte nicht gelöscht werden. Versuchen Sie es erneut." +fine_tune = "Feinabstimmung" +limit_reached = { one = "Sie können bis zu {count} Verlauf speichern. Löschen Sie einen, um einen weiteren hinzuzufügen.", other = "Sie können bis zu {count} Verläufe speichern. Löschen Sie einen, um einen weiteren hinzuzufügen." } +motion = "Bewegung" +name_placeholder = "Name für diesen Verlauf" +name_required = "Geben Sie einen Namen für diesen Verlauf ein." +presets = "Vorlagen" +randomize = "Zufällig" +remember_failed = "Ihre Einstellungen für animierte Verläufe konnten nicht gespeichert werden." +remove_colour = "Farbe entfernen" +reset_tooltip = "Feinabstimmung zurücksetzen" +save_failed = "Dieser Verlauf konnte nicht gespeichert werden. Versuchen Sie es erneut." +save_tooltip = "Aktuellen Verlauf speichern" +saved = "Gespeichert" +still = "Still" + +[editor.sidebar.animated_gradient.control] +contrast = "Kontrast" +curvature = "Krümmung" +detail = "Detail" +direction = "Richtung" +exposure = "Belichtung" +flow_scale = "Flussgröße" +flow_strength = "Flussstärke" +grain_amount = "Körnungsmenge" +grain_size = "Körnungsgröße" +highlights = "Lichter" +motion_speed = "Bewegungstempo" +relief = "Relief" +ripple_size = "Wellengröße" +shading = "Schattierung" +vibrance = "Dynamik" + +[editor.sidebar.animated_gradient.group] +animation = "Animation" +colour = "Farbe" +flow = "Fluss" +lighting = "Licht" +texture = "Textur" + +[editor.sidebar.background] +title = "Hintergrund" + +[editor.sidebar.border] +color = "Rahmenfarbe" +opacity = "Rahmendeckkraft" +title = "Rahmen" +width = "Rahmenbreite" + +[editor.sidebar.corner] +rounded = "Abgerundet" +squircle = "Squircle" +title = "Eckenstil" + +[editor.sidebar.cursor] +circle = "Kreis" +click_ripple = "Klickwelle" +color = "Farbe" +default = "Standard" +description_circle = "Ersetzt alle Cursorformen durch einen Kreis." +description_default = "Behält die Cursorformen und das Aussehen aus Ihrer Aufnahme bei." +description_macos = "Klassisches macOS-Aussehen. Eigene Cursor behalten ihre aufgenommene Form." +description_tahoe = "macOS-Tahoe-Aussehen. Eigene Cursor behalten ihre aufgenommene Form." +description_windows = "Windows-Aussehen. Eigene Cursor behalten ihre aufgenommene Form." +duration = "Dauer" +recorded = "Aufgenommene Cursor" +recorded_tooltip = "Mit diesem Cursor aufgenommen" +ripple_size = "Größe" +strength = "Stärke" + +[editor.sidebar.desktop] +import = "Desktop-Hintergrund importieren" +importing = "Wird importiert..." +prompt = "Das Hintergrundbild Ihres Desktops verwenden" +reimport = "Neu importieren" + +[editor.sidebar.gradient] +angle = "Winkel" +from = "Von" +grain_scale = "Körnungsgröße" +noise = "Rauschen" +to = "Bis" + +[editor.sidebar.image] +prompt = "Klicken zum Auswählen oder Bild hierher ziehen" + +[editor.sidebar.layout] +background_blur = "Hintergrundunschärfe" +custom_position = "Eigene Bildschirmposition (auf der Leinwand verschoben)" +motion_blur = "Bewegungsunschärfe" +padding = "Rand" +rounded_corners = "Abgerundete Ecken" +title = "Layout" + +[editor.sidebar.notch] +description = "Zeichnet eine MacBook-Notch über die Aufnahme. Aufnahmen auf einem Mac mit Notch verwenden ihre eigenen Maße; andernfalls beginnen Sie mit der Größe unten und passen sie an." +height = "Notch-Höhe" +position = "Notch-Position" +title = "MacBook-Notch" +width = "Notch-Breite" + +[editor.sidebar.shadow] +advanced = "Erweiterte Schatteneinstellungen" +blur = "Unschärfe" +opacity = "Deckkraft" +shadow_size = "Größe" +title = "Schatten" + +[editor.sidebar.source] +animated = "Animiert" +color = "Farbe" +desktop = "Desktop" +gradient = "Verlauf" +image = "Bild" +none = "Keiner" +wallpaper = "Hintergrund" + +[editor.sidebar.style_group] +back = "Zurück zum Stil" +background = "Hintergrund & Rahmen" +camera = "Kamera" +cursor = "Cursor" +editing_note = "Nur dieses Segment wird bearbeitet. Die globalen Einstellungen bleiben unverändert." +title = "Stil {number} · {group}" + +[editor.sidebar.tab] +audio = "Audio" +background = "Hintergrund" +camera = "Kamera" +captions = "Untertitel" +cursor = "Cursor" +keyboard = "Tastatur" + +[editor.sidebar.wallpaper_theme] +blue = "Blau" +cities = "Städte" +dark = "Dunkel" +orange = "Orange" +purple = "Lila" diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index c4415d07108..3434277baec 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -588,3 +588,521 @@ click_or_press = "Click here, or press" get_started = "Get Started" space_key = "Space" tagline = "The open source screen sharing app for everyone." + +# Editor sidebar, segment panels, colour correction, audio library and crop dialog. + +[editor.audio.library] +add_hint = "Add audio, music or other sounds to your video" +add_title = "Add audio" +change_hint = "Pick a different track for this segment" +change_title = "Change audio" +done = "Done" +import = "Import file" + +[editor.color] +apply_to_cursor = "Apply to cursor" +fine_tune = "Fine-tune colors" +title = "Color Correction" + +[editor.color.preset.cinematic] +description = "Teal shadows and orange highlights with light grain" +name = "Cinematic" + +[editor.color.preset.dreamy] +description = "Soft, airy pastels with lifted blacks" +name = "Dreamy" + +[editor.color.preset.frost] +description = "Cool, crisp tones with muted color" +name = "Frost" + +[editor.color.preset.golden] +description = "Warm golden-hour glow" +name = "Golden" + +[editor.color.preset.midnight] +description = "Dark, moody teal with subdued color" +name = "Midnight" + +[editor.color.preset.noir] +description = "High-contrast black and white with heavy grain" +name = "Noir" + +[editor.color.preset.none] +description = "Original colors, no grade applied" +name = "None" + +[editor.color.preset.vintage] +description = "Faded warm film look with soft contrast" +name = "Vintage" + +[editor.color.preset.vivid] +description = "Punchy saturation and contrast boost" +name = "Vivid" + +[editor.color.slider] +contrast = "Contrast" +exposure = "Exposure" +fade = "Fade" +grain = "Grain" +saturation = "Saturation" +split_tone = "Split Tone" +strength = "Strength" +temperature = "Temperature" +tint = "Tint" +vignette = "Vignette" + +[editor.crop] +aspect_ratio = "Aspect ratio" +center = "Center" +dimensions = "Size" +editing_style = { text = "Editing Style {number} only", context = "{number} is the style segment's position, starting at 1." } +editing_style_override = { text = "Editing Style {number} only · Saving enables its background override", context = "{number} is the style segment's position, starting at 1." } +free = "Free" +full = "Full" +hint = { text = "Drag to move · Arrow keys nudge · Hold {key} to skip snapping", context = "{key} is drawn as a Shift key badge; keep it where the sentence needs it." } +loading_frame = "Loading frame…" +position = "Position" +preview = "Preview" +rendering_preview = "Rendering preview…" +snap_to_ratios = "Snap to ratios" +style_changed = "This Style changed while cropping. Cancel and reopen Crop." +title = "Crop" + +[editor.panel] +delete = "Delete" +done = "Done" +duration = "Duration" +edge_in = "In" +edge_out = "Out" +enabled = "Enabled" +fade_duration = "Fade Duration" +flip_horizontally = "Flip horizontally" +flip_vertically = "Flip vertically" +name = "Name" +opacity = "Opacity" + +[editor.panel.audio] +change = "Change" +change_hint = "Tap to change track" +fade_in = "Fade In" +fade_out = "Fade Out" +fallback_name = "Audio" +title = "Audio {number}" +volume = "Volume" + +[editor.panel.camera3d] +already_still = "Already a still shot" +amount = "Amount" +angles = "Angles" +auto_scene = "Auto scene" +auto_scene_tooltip = "Rebuilds every 3D shot on the track" +bokeh = "Bokeh" +camera = "Camera" +delete_tooltip = "Delete this shot" +depth_blur = "Depth blur" +distance = "Distance" +drag_to_orbit = "Drag to orbit" +ease_in = "Ease in" +ease_out = "Ease out" +end_same_as_start = "End · same as start" +fine_tune = "Fine-tune" +focus = "Focus" +header = { text = "3D shot · {look} · {duration}s", context = "{look} is the shot's look name; {duration} is seconds with one decimal, such as 4.0." } +hold_opening = "Hold the opening pose for the whole shot" +lens = "Lens" +look_title = "Look" +motion_style = "Motion style" +moves = "Moves" +nothing_to_swap = "Nothing to swap on a still shot" +play_shot = "Play shot" +play_shot_tooltip = "Play this shot" +pose_end = { text = "End · {time}", context = "{time} is a clock such as 0:04.0." } +pose_start = { text = "Start · {time}", context = "{time} is a clock such as 0:04.0." } +reset_camera = "Reset camera" +roll = "Roll" +rotate_x = "Rotate X" +rotate_y = "Rotate Y" +shift_x = "Shift X" +shift_y = "Shift Y" +shots = { one = "{count} shot", other = "{count} shots", context = "How many shots a ready-made 3D sequence lays out." } +still_shot = "Still shot" +swap = "Swap start and end" +timing_advanced = "Timing & advanced" +timing_summary = { text = "{easing} · Lens {fov}", context = "{easing} is the motion style name; {fov} is the lens angle in degrees, a whole number." } + +[editor.panel.camera3d.blur] +angle = "Angle" +falloff = "Falloff" +focus_size = "Focus size" +focus_x = "Focus X" +focus_y = "Focus Y" +position = "Position" +scan = "Scan" +strength = "Strength" + +[editor.panel.camera3d.easing] +ease_in = "Ease in" +ease_out = "Ease out" +linear = "Linear" +smooth = "Smooth" + +[editor.panel.camera3d.focus_mode] +directional = "Directional" +radial = "Radial" +tilt_shift = "Tilt shift" + +[editor.panel.camera3d.look] +center = "Center" +close_up = "Close up" +drift_down = "Drift down" +glide_across = "Glide across" +low_angle = "Low angle" +perspective = "Perspective" +pull_back = "Pull back" +rising_sweep = "Rising sweep" +slide_by = "Slide by" +spotlight = "Spotlight" +tilt_away = "Tilt away" +top_down = "Top down" +unfold = "Unfold" + +[editor.panel.camera3d.sequence] +product_tour = "Product tour" +punch_in = "Punch in" +showcase = "Showcase" + +[editor.panel.camera3d.shot] +custom = "Custom move" +still = "Still shot" + +[editor.panel.caption] +title = "Caption {number}" + +[editor.panel.font] +system_mono = "System Mono" +system_sans = "System Sans" +system_serif = "System Serif" + +[editor.panel.font_weight] +black = "Black" +bold = "Bold" +custom = { text = "Custom ({weight})", context = "{weight} is a numeric font weight such as 450." } +extra_bold = "Extra Bold" +light = "Light" +medium = "Medium" +regular = "Regular" +semibold = "Semibold" + +[editor.panel.image] +arrange = "Arrange on canvas" +arrange_help = "Drag the image to move it. Pull a corner to resize, or use the rotation control below to turn it." +center = "Center on canvas" +lock_aspect = "Lock aspect ratio" +missing = "Image file is missing. Replace it to restore this segment while keeping its timing and transforms." +replace = "Replace image" +rotation = "Rotation" +rounding = "Rounding" + +[editor.panel.keyboard] +title = "Keyboard {number}" + +[editor.panel.mask] +blur = "Blur" +effect = "Effect" +highlight = "Highlight" +outside_darkness = "Outside Darkness" +pixel_size = "Pixel Size" +pixelate = "Pixelate" +sensitive = "Sensitive" +title = "Mask {number}" + +[editor.panel.scene] +camera_layout = "Camera Layout" +camera_position = "Camera Position" +camera_zoom = "Camera Zoom" +screen_position = "Screen Position" +screen_zoom = "Screen Zoom" +transition = "Transition" + +[editor.panel.scene.description] +camera_only = "Shows only the camera feed" +default = "Shows both screen and camera" +floating = "Screen and camera float side by side as rounded cards over the background" +hide_camera = "Shows only the screen recording" +split_screen = "Screen and camera side by side (auto-stacks in portrait)" + +[editor.panel.scene.mode] +camera_only = "Camera Only" +default = "Default" +floating = "Floating" +hide_camera = "Hide Camera" +split_screen = "Split Screen" + +[editor.panel.style] +camera_only_background = "Camera Only background" +camera_only_help = "Use a Camera Only scene. Padding reveals the background around the camera." +camera_only_padding = "Camera Only padding" +crop_height = "Height" +crop_left = "Left" +crop_title = "Screen crop (source pixels)" +crop_top = "Top" +crop_width = "Width" +edit_background = "Edit Background & frame" +edit_camera = "Edit Camera" +edit_cursor = "Edit Cursor" +help = "Overrides apply only during this segment. Enable a group to copy its global settings." +reset_crop = "Reset crop" + +[editor.panel.text_animation] +bounce = "Bounce" +fade = "Fade" +letters = "Letters" +none = "None" +pop = "Pop" +slide_down = "Slide down" +slide_left = "Slide left" +slide_right = "Slide right" +slide_up = "Slide up" +tracking = "Tracking" +typewriter = "Typewriter" +wipe = "Wipe" +words = "Words" +zoom = "Zoom" + +[editor.panel.text_background] +box_shape = "Box" +highlight = "Highlight" +none = "None" +pill = "Pill" + +[editor.panel.text_layout] +fullscreen = "Fullscreen" +overlay = "Overlay" + +[editor.panel.text_preset.group] +all = "All" +callouts = "Callouts" +code = "Code" +lower_thirds = "Lower thirds" +statements = "Statements" +titles = "Titles" + +[editor.panel.text_preset.name] +caption = "Caption" +cinematic = "Cinematic" +code = "Code" +gradient = "Gradient" +headline = "Headline" +highlight = "Highlight" +kicker = "Kicker" +label = "Label" +lower_third = "Lower third" +name_tag = "Name tag" +neon = "Neon" +quote = "Quote" +stat = "Big stat" +sticker = "Sticker" +title = "Title" +typewriter = "Typewriter" + +[editor.panel.text_preset.sample] +caption = "Recorded with Cap" +cinematic = "Chapter one" +gradient = "Beautiful text" +headline = "Ship faster" +highlight = "the important part" +kicker = "New feature" +label = "Pro tip" +name_tag = { text = "Richie · Founder", context = "Richie is a person's name and stays as it is." } +neon = "Glow up" +quote = "“Make it feel effortless”" +sticker = "Boom!" +title = { text = "Introducing Cap", context = "Sample text a new text segment takes when this preset is applied. Cap is the product name." } +typewriter = "typing it out…" + +[editor.panel.text_segment] +animation = "Animation" +background = "Background" +color = "Color" +font = "Font" +font_size = "Size" +fullscreen_help = "Pauses the video while the text is shown, then resumes where it left off." +glow = "Glow" +gradient = "Gradient" +layout = "Layout" +letter_spacing = "Letter spacing" +line_height = "Line height" +look = "Look" +outline = "Outline" +screen_transition = "Screen transition" +shadow = "Shadow" +style = "Style" +title = "Text {number}" + +[editor.panel.timing] +end = "End" +start = "Start" +title = "Timing" +to = { text = "to", context = "Sits between the start and end time boxes." } + +[editor.panel.zoom] +amount = "Zoom Amount" +auto = "Auto" +auto_help = "Automatic zoom follows your cursor around the screen." +auto_locked = "Auto mode needs cursor capture. Enable \"Custom cursor capture (Studio)\" in Settings → General." +how_it_works = "How does it work?" +manual = "Manual" +manual_help = "Manual zoom stays on a fixed spot you pick below." +mixed = "Mixed" +mode = "Zoom Mode" +positions_mixed = "Segments zoom into different spots. Drag to move them all to the same one." +title = { text = "Zoom {number}", context = "{number} is the segment's position on the track, starting at 1." } + +[editor.sidebar] +desktop_import_failed = { text = "Couldn't import your desktop wallpaper", context = "Alert title. The alert body is a technical error message in English." } +importing_image = "Importing image…" + +[editor.sidebar.animated_gradient] +add_colour = "Add a colour" +colours = "Colours" +delete = "Delete" +delete_failed = "Could not delete this gradient. Try again." +fine_tune = "Fine-tune" +limit_reached = { one = "You can save up to {count} gradient. Delete one to add another.", other = "You can save up to {count} gradients. Delete one to add another." } +motion = "Motion" +name_placeholder = "Name this gradient" +name_required = "Enter a name for this gradient." +presets = "Presets" +randomize = "Randomize" +remember_failed = "Could not remember your animated gradient settings." +remove_colour = "Remove colour" +reset_tooltip = "Reset fine-tune settings" +save_failed = "Could not save this gradient. Try again." +save_tooltip = "Save the current gradient" +saved = "Saved" +still = { text = "Still", context = "Motion speed readout when the speed is zero." } + +[editor.sidebar.animated_gradient.control] +contrast = "Contrast" +curvature = "Curvature" +detail = "Detail" +direction = "Direction" +exposure = "Exposure" +flow_scale = "Flow Scale" +flow_strength = "Flow Strength" +grain_amount = "Grain Amount" +grain_size = "Grain Size" +highlights = "Highlights" +motion_speed = "Motion Speed" +relief = "Relief" +ripple_size = "Ripple Size" +shading = "Shading" +vibrance = "Vibrance" + +[editor.sidebar.animated_gradient.group] +animation = "Animation" +colour = "Colour" +flow = "Flow" +lighting = "Lighting" +texture = "Texture" + +[editor.sidebar.background] +title = "Background" + +[editor.sidebar.border] +color = "Border Color" +opacity = "Border Opacity" +title = "Border" +width = "Border Width" + +[editor.sidebar.corner] +rounded = "Rounded" +squircle = "Squircle" +title = "Corner Style" + +[editor.sidebar.cursor] +circle = "Circle" +click_ripple = "Click Ripple" +color = "Color" +default = "Default" +description_circle = "Replaces all cursor shapes with a circle." +description_default = "Keeps the cursor shapes and appearance from your recording." +description_macos = "Classic macOS appearance. Custom cursors keep their recorded shape." +description_tahoe = "macOS Tahoe appearance. Custom cursors keep their recorded shape." +description_windows = "Windows appearance. Custom cursors keep their recorded shape." +duration = "Duration" +recorded = "Recorded cursors" +recorded_tooltip = "Recorded with this cursor" +ripple_size = "Size" +strength = "Strength" + +[editor.sidebar.desktop] +import = "Import desktop background" +importing = "Importing..." +prompt = "Use the wallpaper from your desktop" +reimport = "Re-import" + +[editor.sidebar.gradient] +angle = "Angle" +from = "From" +grain_scale = "Grain Scale" +noise = "Noise" +to = "To" + +[editor.sidebar.image] +prompt = "Click to select or drag and drop image" + +[editor.sidebar.layout] +background_blur = "Background Blur" +custom_position = "Custom screen position (dragged on canvas)" +motion_blur = "Motion Blur" +padding = "Padding" +rounded_corners = "Rounded Corners" +title = "Layout" + +[editor.sidebar.notch] +description = "Draws a MacBook notch over the recording. Recordings made on a Mac with a notch use their own measurements; otherwise start from the size below and adjust to match." +height = "Notch Height" +position = "Notch Position" +title = "MacBook notch" +width = "Notch Width" + +[editor.sidebar.shadow] +advanced = "Advanced shadow settings" +blur = "Blur" +opacity = "Opacity" +shadow_size = "Size" +title = "Shadow" + +[editor.sidebar.source] +animated = "Animated" +color = "Color" +desktop = "Desktop" +gradient = "Gradient" +image = "Image" +none = "None" +wallpaper = "Wallpaper" + +[editor.sidebar.style_group] +back = "Back to Style" +background = "Background & frame" +camera = "Camera" +cursor = "Cursor" +editing_note = "Editing this segment only. Global settings are unchanged." +title = { text = "Style {number} · {group}", context = "{number} is the style segment's position, starting at 1; {group} is Background & frame, Camera or Cursor." } + +[editor.sidebar.tab] +audio = "Audio" +background = "Background" +camera = "Camera" +captions = "Captions" +cursor = "Cursor" +keyboard = "Keyboard" + +[editor.sidebar.wallpaper_theme] +blue = "Blue" +cities = "Cities" +dark = "Dark" +orange = "Orange" +purple = "Purple" diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index 00c4c9b74f7..6e0116652f0 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -586,3 +586,521 @@ click_or_press = "Haz clic aquí o pulsa" get_started = "Empezar" space_key = "Espacio" tagline = "La app de código abierto para compartir pantalla, para todo el mundo." + +# Editor sidebar, segment panels, colour correction, audio library and crop dialog. + +[editor.audio.library] +add_hint = "Añade audio, música u otros sonidos a tu vídeo" +add_title = "Añadir audio" +change_hint = "Elige otra pista para este segmento" +change_title = "Cambiar audio" +done = "Listo" +import = "Importar archivo" + +[editor.color] +apply_to_cursor = "Aplicar al cursor" +fine_tune = "Ajustar colores" +title = "Corrección de color" + +[editor.color.preset.cinematic] +description = "Sombras verde azuladas y luces anaranjadas con grano suave" +name = "Cinemático" + +[editor.color.preset.dreamy] +description = "Pasteles suaves y ligeros con negros levantados" +name = "Onírico" + +[editor.color.preset.frost] +description = "Tonos fríos y nítidos con color apagado" +name = "Escarcha" + +[editor.color.preset.golden] +description = "Brillo cálido de la hora dorada" +name = "Dorado" + +[editor.color.preset.midnight] +description = "Verde azulado oscuro y sombrío con color apagado" +name = "Medianoche" + +[editor.color.preset.noir] +description = "Blanco y negro de alto contraste con mucho grano" +name = "Noir" + +[editor.color.preset.none] +description = "Colores originales, sin corrección" +name = "Ninguno" + +[editor.color.preset.vintage] +description = "Aspecto de película cálida y desvaída con contraste suave" +name = "Vintage" + +[editor.color.preset.vivid] +description = "Saturación y contraste potenciados" +name = "Intenso" + +[editor.color.slider] +contrast = "Contraste" +exposure = "Exposición" +fade = "Desvanecido" +grain = "Grano" +saturation = "Saturación" +split_tone = "Virado dividido" +strength = "Intensidad" +temperature = "Temperatura" +tint = "Tinte" +vignette = "Viñeta" + +[editor.crop] +aspect_ratio = "Relación de aspecto" +center = "Centrar" +dimensions = "Tamaño" +editing_style = "Editando solo el estilo {number}" +editing_style_override = "Editando solo el estilo {number} · Al guardar se activa su fondo propio" +free = "Libre" +full = "Completo" +hint = "Arrastra para mover · Flechas para ajustar · Mantén {key} para no ajustar" +loading_frame = "Cargando fotograma…" +position = "Posición" +preview = "Vista previa" +rendering_preview = "Generando vista previa…" +snap_to_ratios = "Ajustar a proporciones" +style_changed = "Este estilo cambió mientras recortabas. Cancela y vuelve a abrir Recortar." +title = "Recortar" + +[editor.panel] +delete = "Eliminar" +done = "Listo" +duration = "Duración" +edge_in = "Entrada" +edge_out = "Salida" +enabled = "Activado" +fade_duration = "Duración del fundido" +flip_horizontally = "Voltear horizontalmente" +flip_vertically = "Voltear verticalmente" +name = "Nombre" +opacity = "Opacidad" + +[editor.panel.audio] +change = "Cambiar" +change_hint = "Toca para cambiar de pista" +fade_in = "Fundido de entrada" +fade_out = "Fundido de salida" +fallback_name = "Audio" +title = "Audio {number}" +volume = "Volumen" + +[editor.panel.camera3d] +already_still = "Ya es una toma fija" +amount = "Cantidad" +angles = "Ángulos" +auto_scene = "Escena automática" +auto_scene_tooltip = "Rehace todas las tomas 3D de la pista" +bokeh = "Bokeh" +camera = "Cámara" +delete_tooltip = "Eliminar esta toma" +depth_blur = "Desenfoque de profundidad" +distance = "Distancia" +drag_to_orbit = "Arrastra para orbitar" +ease_in = "Entrada suave" +ease_out = "Salida suave" +end_same_as_start = "Fin · igual al inicio" +fine_tune = "Ajuste fino" +focus = "Enfoque" +header = "Toma 3D · {look} · {duration} s" +hold_opening = "Mantener la pose inicial durante toda la toma" +lens = "Lente" +look_title = "Toma" +motion_style = "Estilo de movimiento" +moves = "Movimientos" +nothing_to_swap = "No hay nada que intercambiar en una toma fija" +play_shot = "Reproducir" +play_shot_tooltip = "Reproducir esta toma" +pose_end = "Fin · {time}" +pose_start = "Inicio · {time}" +reset_camera = "Restablecer cámara" +roll = "Giro" +rotate_x = "Rotar X" +rotate_y = "Rotar Y" +shift_x = "Desplazar X" +shift_y = "Desplazar Y" +shots = { one = "{count} toma", other = "{count} tomas" } +still_shot = "Toma fija" +swap = "Intercambiar inicio y fin" +timing_advanced = "Tiempo y avanzado" +timing_summary = "{easing} · Lente {fov}" + +[editor.panel.camera3d.blur] +angle = "Ángulo" +falloff = "Caída" +focus_size = "Tamaño foco" +focus_x = "Foco X" +focus_y = "Foco Y" +position = "Posición" +scan = "Barrido" +strength = "Intensidad" + +[editor.panel.camera3d.easing] +ease_in = "Aceleración" +ease_out = "Desaceleración" +linear = "Lineal" +smooth = "Suave" + +[editor.panel.camera3d.focus_mode] +directional = "Direccional" +radial = "Radial" +tilt_shift = "Tilt-shift" + +[editor.panel.camera3d.look] +center = "Centro" +close_up = "Primer plano" +drift_down = "Descenso" +glide_across = "Deslizar" +low_angle = "Contrapicado" +perspective = "Perspectiva" +pull_back = "Alejar" +rising_sweep = "Barrido ascendente" +slide_by = "Pasar" +spotlight = "Foco" +tilt_away = "Inclinar" +top_down = "Cenital" +unfold = "Desplegar" + +[editor.panel.camera3d.sequence] +product_tour = "Recorrido del producto" +punch_in = "Acercar" +showcase = "Escaparate" + +[editor.panel.camera3d.shot] +custom = "Movimiento personalizado" +still = "Toma fija" + +[editor.panel.caption] +title = "Subtítulo {number}" + +[editor.panel.font] +system_mono = "Mono del sistema" +system_sans = "Sans del sistema" +system_serif = "Serif del sistema" + +[editor.panel.font_weight] +black = "Black" +bold = "Negrita" +custom = "Personalizado ({weight})" +extra_bold = "Extranegrita" +light = "Ligera" +medium = "Media" +regular = "Normal" +semibold = "Seminegrita" + +[editor.panel.image] +arrange = "Colocar en el lienzo" +arrange_help = "Arrastra la imagen para moverla. Tira de una esquina para cambiar su tamaño o usa el control de rotación de abajo para girarla." +center = "Centrar en el lienzo" +lock_aspect = "Bloquear relación de aspecto" +missing = "Falta el archivo de imagen. Reemplázalo para recuperar este segmento conservando su tiempo y sus transformaciones." +replace = "Reemplazar imagen" +rotation = "Rotación" +rounding = "Redondeo" + +[editor.panel.keyboard] +title = "Teclado {number}" + +[editor.panel.mask] +blur = "Desenfoque" +effect = "Efecto" +highlight = "Resaltar" +outside_darkness = "Oscuridad exterior" +pixel_size = "Tamaño de píxel" +pixelate = "Pixelar" +sensitive = "Sensible" +title = "Máscara {number}" + +[editor.panel.scene] +camera_layout = "Diseño de la cámara" +camera_position = "Posición de cámara" +camera_zoom = "Zoom de cámara" +screen_position = "Posición de pantalla" +screen_zoom = "Zoom de pantalla" +transition = "Transición" + +[editor.panel.scene.description] +camera_only = "Muestra solo la cámara" +default = "Muestra la pantalla y la cámara" +floating = "La pantalla y la cámara flotan lado a lado como tarjetas redondeadas sobre el fondo" +hide_camera = "Muestra solo la grabación de pantalla" +split_screen = "Pantalla y cámara lado a lado (se apilan en vertical)" + +[editor.panel.scene.mode] +camera_only = "Solo cámara" +default = "Predeterminado" +floating = "Flotante" +hide_camera = "Ocultar cámara" +split_screen = "Pantalla dividida" + +[editor.panel.style] +camera_only_background = "Fondo de solo cámara" +camera_only_help = "Usa una escena de solo cámara. El margen muestra el fondo alrededor de la cámara." +camera_only_padding = "Margen de solo cámara" +crop_height = "Alto" +crop_left = "Izquierda" +crop_title = "Recorte de pantalla (píxeles de origen)" +crop_top = "Arriba" +crop_width = "Ancho" +edit_background = "Editar fondo y marco" +edit_camera = "Editar cámara" +edit_cursor = "Editar cursor" +help = "Los cambios solo se aplican durante este segmento. Activa un grupo para copiar sus ajustes generales." +reset_crop = "Restablecer recorte" + +[editor.panel.text_animation] +bounce = "Rebote" +fade = "Fundido" +letters = "Letras" +none = "Ninguna" +pop = "Pop" +slide_down = "Hacia abajo" +slide_left = "A la izquierda" +slide_right = "A la derecha" +slide_up = "Hacia arriba" +tracking = "Espaciado" +typewriter = "Máquina de escribir" +wipe = "Barrido" +words = "Palabras" +zoom = "Zoom" + +[editor.panel.text_background] +box_shape = "Caja" +highlight = "Resaltado" +none = "Ninguno" +pill = "Píldora" + +[editor.panel.text_layout] +fullscreen = "Pantalla completa" +overlay = "Superpuesto" + +[editor.panel.text_preset.group] +all = "Todos" +callouts = "Llamadas" +code = "Código" +lower_thirds = "Rótulos" +statements = "Mensajes" +titles = "Títulos" + +[editor.panel.text_preset.name] +caption = "Leyenda" +cinematic = "Cinemático" +code = "Código" +gradient = "Degradado" +headline = "Titular" +highlight = "Resaltado" +kicker = "Antetítulo" +label = "Etiqueta" +lower_third = "Rótulo" +name_tag = "Etiqueta" +neon = "Neón" +quote = "Cita" +stat = "Cifra grande" +sticker = "Pegatina" +title = "Título" +typewriter = "Máquina de escribir" + +[editor.panel.text_preset.sample] +caption = "Grabado con Cap" +cinematic = "Capítulo uno" +gradient = "Texto bonito" +headline = "Lanza más rápido" +highlight = "la parte importante" +kicker = "Novedad" +label = "Consejo" +name_tag = "Richie · Fundador" +neon = "Brilla" +quote = "«Que parezca fácil»" +sticker = "¡Bum!" +title = "Te presentamos Cap" +typewriter = "escribiendo…" + +[editor.panel.text_segment] +animation = "Animación" +background = "Fondo" +color = "Color" +font = "Fuente" +font_size = "Tamaño" +fullscreen_help = "Pausa el vídeo mientras se muestra el texto y luego continúa donde lo dejó." +glow = "Resplandor" +gradient = "Degradado" +layout = "Diseño" +letter_spacing = "Espaciado" +line_height = "Interlineado" +look = "Aspecto" +outline = "Contorno" +screen_transition = "Transición de pantalla" +shadow = "Sombra" +style = "Estilo" +title = "Texto {number}" + +[editor.panel.timing] +end = "Fin" +start = "Inicio" +title = "Tiempo" +to = "a" + +[editor.panel.zoom] +amount = "Nivel de zoom" +auto = "Auto" +auto_help = "El zoom automático sigue tu cursor por la pantalla." +auto_locked = "El modo automático necesita capturar el cursor. Activa «Captura del cursor por separado (Studio)» en Ajustes → General." +how_it_works = "¿Cómo funciona?" +manual = "Manual" +manual_help = "El zoom manual se queda en el punto fijo que elijas abajo." +mixed = "Mixto" +mode = "Modo de zoom" +positions_mixed = "Los segmentos hacen zoom en puntos distintos. Arrastra para moverlos todos al mismo." +title = "Zoom {number}" + +[editor.sidebar] +desktop_import_failed = "No se pudo importar tu fondo de escritorio" +importing_image = "Importando imagen…" + +[editor.sidebar.animated_gradient] +add_colour = "Añadir un color" +colours = "Colores" +delete = "Eliminar" +delete_failed = "No se pudo eliminar este degradado. Inténtalo de nuevo." +fine_tune = "Ajuste fino" +limit_reached = { one = "Puedes guardar hasta {count} degradado. Elimina uno para añadir otro.", other = "Puedes guardar hasta {count} degradados. Elimina uno para añadir otro." } +motion = "Movimiento" +name_placeholder = "Ponle nombre a este degradado" +name_required = "Escribe un nombre para este degradado." +presets = "Ajustes predefinidos" +randomize = "Aleatorio" +remember_failed = "No se pudieron recordar tus ajustes de degradado animado." +remove_colour = "Quitar color" +reset_tooltip = "Restablecer el ajuste fino" +save_failed = "No se pudo guardar este degradado. Inténtalo de nuevo." +save_tooltip = "Guardar el degradado actual" +saved = "Guardados" +still = "Quieto" + +[editor.sidebar.animated_gradient.control] +contrast = "Contraste" +curvature = "Curvatura" +detail = "Detalle" +direction = "Dirección" +exposure = "Exposición" +flow_scale = "Escala del flujo" +flow_strength = "Fuerza del flujo" +grain_amount = "Cantidad de grano" +grain_size = "Tamaño del grano" +highlights = "Luces" +motion_speed = "Velocidad del movimiento" +relief = "Relieve" +ripple_size = "Tamaño de las ondas" +shading = "Sombreado" +vibrance = "Intensidad" + +[editor.sidebar.animated_gradient.group] +animation = "Animación" +colour = "Color" +flow = "Flujo" +lighting = "Iluminación" +texture = "Textura" + +[editor.sidebar.background] +title = "Fondo" + +[editor.sidebar.border] +color = "Color del borde" +opacity = "Opacidad del borde" +title = "Borde" +width = "Grosor del borde" + +[editor.sidebar.corner] +rounded = "Redondeado" +squircle = "Squircle" +title = "Estilo de esquinas" + +[editor.sidebar.cursor] +circle = "Círculo" +click_ripple = "Onda al hacer clic" +color = "Color" +default = "Predeterminado" +description_circle = "Sustituye todas las formas del cursor por un círculo." +description_default = "Mantiene las formas y el aspecto del cursor de tu grabación." +description_macos = "Aspecto clásico de macOS. Los cursores personalizados mantienen su forma grabada." +description_tahoe = "Aspecto de macOS Tahoe. Los cursores personalizados mantienen su forma grabada." +description_windows = "Aspecto de Windows. Los cursores personalizados mantienen su forma grabada." +duration = "Duración" +recorded = "Cursores grabados" +recorded_tooltip = "Grabado con este cursor" +ripple_size = "Tamaño" +strength = "Intensidad" + +[editor.sidebar.desktop] +import = "Importar fondo de escritorio" +importing = "Importando..." +prompt = "Usa el fondo de tu escritorio" +reimport = "Volver a importar" + +[editor.sidebar.gradient] +angle = "Ángulo" +from = "Desde" +grain_scale = "Tamaño del grano" +noise = "Ruido" +to = "Hasta" + +[editor.sidebar.image] +prompt = "Haz clic para elegir o arrastra y suelta una imagen" + +[editor.sidebar.layout] +background_blur = "Desenfoque del fondo" +custom_position = "Posición de pantalla personalizada (arrastrada en el lienzo)" +motion_blur = "Desenfoque de movimiento" +padding = "Margen" +rounded_corners = "Esquinas redondeadas" +title = "Diseño" + +[editor.sidebar.notch] +description = "Dibuja una muesca de MacBook sobre la grabación. Las grabaciones hechas en un Mac con muesca usan sus propias medidas; si no, empieza con el tamaño de abajo y ajústalo." +height = "Alto de la muesca" +position = "Posición de la muesca" +title = "Muesca del MacBook" +width = "Ancho de la muesca" + +[editor.sidebar.shadow] +advanced = "Ajustes avanzados de sombra" +blur = "Desenfoque" +opacity = "Opacidad" +shadow_size = "Tamaño" +title = "Sombra" + +[editor.sidebar.source] +animated = "Animado" +color = "Color" +desktop = "Escritorio" +gradient = "Degradado" +image = "Imagen" +none = "Ninguno" +wallpaper = "Fondo" + +[editor.sidebar.style_group] +back = "Volver al estilo" +background = "Fondo y marco" +camera = "Cámara" +cursor = "Cursor" +editing_note = "Solo se edita este segmento. Los ajustes generales no cambian." +title = "Estilo {number} · {group}" + +[editor.sidebar.tab] +audio = "Audio" +background = "Fondo" +camera = "Cámara" +captions = "Subtítulos" +cursor = "Cursor" +keyboard = "Teclado" + +[editor.sidebar.wallpaper_theme] +blue = "Azul" +cities = "Ciudades" +dark = "Oscuro" +orange = "Naranja" +purple = "Morado" diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index e70141f4c4e..4326cbe4021 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -586,3 +586,521 @@ click_or_press = "Cliquez ici ou appuyez sur" get_started = "Commencer" space_key = "Espace" tagline = "L'app open source de partage d'écran pour tout le monde." + +# Editor sidebar, segment panels, colour correction, audio library and crop dialog. + +[editor.audio.library] +add_hint = "Ajoutez de l'audio, de la musique ou d'autres sons à votre vidéo" +add_title = "Ajouter de l'audio" +change_hint = "Choisissez une autre piste pour ce segment" +change_title = "Changer l'audio" +done = "Terminé" +import = "Importer un fichier" + +[editor.color] +apply_to_cursor = "Appliquer au curseur" +fine_tune = "Ajuster les couleurs" +title = "Correction colorimétrique" + +[editor.color.preset.cinematic] +description = "Ombres bleu canard et hautes lumières orangées avec un léger grain" +name = "Cinéma" + +[editor.color.preset.dreamy] +description = "Pastels doux et aériens aux noirs relevés" +name = "Onirique" + +[editor.color.preset.frost] +description = "Tons froids et nets aux couleurs atténuées" +name = "Givre" + +[editor.color.preset.golden] +description = "Lueur chaude de l'heure dorée" +name = "Doré" + +[editor.color.preset.midnight] +description = "Bleu canard sombre et intense aux couleurs sourdes" +name = "Minuit" + +[editor.color.preset.noir] +description = "Noir et blanc très contrasté avec un grain prononcé" +name = "Noir" + +[editor.color.preset.none] +description = "Couleurs d'origine, sans étalonnage" +name = "Aucun" + +[editor.color.preset.vintage] +description = "Aspect pellicule chaude et délavée au contraste doux" +name = "Vintage" + +[editor.color.preset.vivid] +description = "Saturation et contraste renforcés" +name = "Éclatant" + +[editor.color.slider] +contrast = "Contraste" +exposure = "Exposition" +fade = "Délavé" +grain = "Grain" +saturation = "Saturation" +split_tone = "Virage partiel" +strength = "Intensité" +temperature = "Température" +tint = "Teinte" +vignette = "Vignettage" + +[editor.crop] +aspect_ratio = "Proportions" +center = "Centrer" +dimensions = "Taille" +editing_style = "Modification du style {number} uniquement" +editing_style_override = "Modification du style {number} uniquement · L'enregistrement active son arrière-plan personnalisé" +free = "Libre" +full = "Entier" +hint = "Glisser pour déplacer · Flèches pour ajuster · Maintenez {key} pour ne pas aligner" +loading_frame = "Chargement de l'image…" +position = "Position" +preview = "Aperçu" +rendering_preview = "Rendu de l'aperçu…" +snap_to_ratios = "Aligner sur les ratios" +style_changed = "Ce style a changé pendant le recadrage. Annulez puis rouvrez Recadrer." +title = "Recadrer" + +[editor.panel] +delete = "Supprimer" +done = "Terminé" +duration = "Durée" +edge_in = "Entrée" +edge_out = "Sortie" +enabled = "Activé" +fade_duration = "Durée du fondu" +flip_horizontally = "Retourner horizontalement" +flip_vertically = "Retourner verticalement" +name = "Nom" +opacity = "Opacité" + +[editor.panel.audio] +change = "Changer" +change_hint = "Touchez pour changer de piste" +fade_in = "Fondu d'entrée" +fade_out = "Fondu de sortie" +fallback_name = "Audio" +title = "Audio {number}" +volume = "Volume" + +[editor.panel.camera3d] +already_still = "C'est déjà un plan fixe" +amount = "Intensité" +angles = "Angles" +auto_scene = "Scène auto" +auto_scene_tooltip = "Recrée tous les plans 3D de la piste" +bokeh = "Bokeh" +camera = "Caméra" +delete_tooltip = "Supprimer ce plan" +depth_blur = "Flou de profondeur" +distance = "Distance" +drag_to_orbit = "Glisser pour orbiter" +ease_in = "Entrée douce" +ease_out = "Sortie douce" +end_same_as_start = "Fin · identique au début" +fine_tune = "Ajuster" +focus = "Mise au point" +header = "Plan 3D · {look} · {duration} s" +hold_opening = "Garder la pose de départ pendant tout le plan" +lens = "Objectif" +look_title = "Plan" +motion_style = "Style de mouvement" +moves = "Mouvements" +nothing_to_swap = "Rien à inverser sur un plan fixe" +play_shot = "Lire" +play_shot_tooltip = "Lire ce plan" +pose_end = "Fin · {time}" +pose_start = "Début · {time}" +reset_camera = "Réinitialiser la caméra" +roll = "Roulis" +rotate_x = "Rotation X" +rotate_y = "Rotation Y" +shift_x = "Décalage X" +shift_y = "Décalage Y" +shots = { one = "{count} plan", other = "{count} plans" } +still_shot = "Plan fixe" +swap = "Inverser le début et la fin" +timing_advanced = "Minutage et avancé" +timing_summary = "{easing} · Objectif {fov}" + +[editor.panel.camera3d.blur] +angle = "Angle" +falloff = "Transition" +focus_size = "Taille nette" +focus_x = "Point X" +focus_y = "Point Y" +position = "Position" +scan = "Balayage" +strength = "Intensité" + +[editor.panel.camera3d.easing] +ease_in = "Accélération" +ease_out = "Décélération" +linear = "Linéaire" +smooth = "Doux" + +[editor.panel.camera3d.focus_mode] +directional = "Directionnel" +radial = "Radial" +tilt_shift = "Tilt-shift" + +[editor.panel.camera3d.look] +center = "Centre" +close_up = "Gros plan" +drift_down = "Descente" +glide_across = "Glissé latéral" +low_angle = "Contre-plongée" +perspective = "Perspective" +pull_back = "Recul" +rising_sweep = "Envolée" +slide_by = "Passage" +spotlight = "Projecteur" +tilt_away = "Bascule" +top_down = "Plongée" +unfold = "Dépli" + +[editor.panel.camera3d.sequence] +product_tour = "Visite produit" +punch_in = "Zoom avant" +showcase = "Vitrine" + +[editor.panel.camera3d.shot] +custom = "Mouvement personnalisé" +still = "Plan fixe" + +[editor.panel.caption] +title = "Sous-titre {number}" + +[editor.panel.font] +system_mono = "Mono système" +system_sans = "Sans système" +system_serif = "Serif système" + +[editor.panel.font_weight] +black = "Noir" +bold = "Gras" +custom = "Personnalisé ({weight})" +extra_bold = "Extra-gras" +light = "Léger" +medium = "Moyen" +regular = "Normal" +semibold = "Demi-gras" + +[editor.panel.image] +arrange = "Placer sur le canevas" +arrange_help = "Faites glisser l'image pour la déplacer. Tirez un coin pour la redimensionner, ou utilisez la rotation ci-dessous pour la faire pivoter." +center = "Centrer sur le canevas" +lock_aspect = "Verrouiller les proportions" +missing = "Le fichier image est introuvable. Remplacez-le pour restaurer ce segment en conservant son minutage et ses transformations." +replace = "Remplacer l'image" +rotation = "Rotation" +rounding = "Arrondi" + +[editor.panel.keyboard] +title = "Clavier {number}" + +[editor.panel.mask] +blur = "Flou" +effect = "Effet" +highlight = "Mise en avant" +outside_darkness = "Assombrissement extérieur" +pixel_size = "Taille des pixels" +pixelate = "Pixeliser" +sensitive = "Sensible" +title = "Masque {number}" + +[editor.panel.scene] +camera_layout = "Disposition de la caméra" +camera_position = "Position de la caméra" +camera_zoom = "Zoom de la caméra" +screen_position = "Position de l'écran" +screen_zoom = "Zoom de l'écran" +transition = "Transition" + +[editor.panel.scene.description] +camera_only = "Affiche uniquement la caméra" +default = "Affiche l'écran et la caméra" +floating = "L'écran et la caméra flottent côte à côte en cartes arrondies au-dessus de l'arrière-plan" +hide_camera = "Affiche uniquement l'enregistrement d'écran" +split_screen = "Écran et caméra côte à côte (empilés automatiquement en portrait)" + +[editor.panel.scene.mode] +camera_only = "Caméra seule" +default = "Par défaut" +floating = "Flottant" +hide_camera = "Masquer la caméra" +split_screen = "Écran partagé" + +[editor.panel.style] +camera_only_background = "Arrière-plan caméra seule" +camera_only_help = "Utilisez une scène Caméra seule. La marge laisse voir l'arrière-plan autour de la caméra." +camera_only_padding = "Marge caméra seule" +crop_height = "Hauteur" +crop_left = "Gauche" +crop_title = "Recadrage de l'écran (pixels source)" +crop_top = "Haut" +crop_width = "Largeur" +edit_background = "Modifier l'arrière-plan et le cadre" +edit_camera = "Modifier la caméra" +edit_cursor = "Modifier le curseur" +help = "Les remplacements ne s'appliquent que pendant ce segment. Activez un groupe pour copier ses paramètres globaux." +reset_crop = "Réinitialiser le recadrage" + +[editor.panel.text_animation] +bounce = "Rebond" +fade = "Fondu" +letters = "Lettres" +none = "Aucune" +pop = "Pop" +slide_down = "Vers le bas" +slide_left = "Vers la gauche" +slide_right = "Vers la droite" +slide_up = "Vers le haut" +tracking = "Interlettrage" +typewriter = "Machine à écrire" +wipe = "Balayage" +words = "Mots" +zoom = "Zoom" + +[editor.panel.text_background] +box_shape = "Boîte" +highlight = "Surligné" +none = "Aucun" +pill = "Pilule" + +[editor.panel.text_layout] +fullscreen = "Plein écran" +overlay = "Superposé" + +[editor.panel.text_preset.group] +all = "Tous" +callouts = "Accroches" +code = "Code" +lower_thirds = "Bandeaux" +statements = "Messages" +titles = "Titres" + +[editor.panel.text_preset.name] +caption = "Légende" +cinematic = "Cinéma" +code = "Code" +gradient = "Dégradé" +headline = "Gros titre" +highlight = "Surligné" +kicker = "Surtitre" +label = "Étiquette" +lower_third = "Bandeau" +name_tag = "Badge nom" +neon = "Néon" +quote = "Citation" +stat = "Gros chiffre" +sticker = "Sticker" +title = "Titre" +typewriter = "Machine à écrire" + +[editor.panel.text_preset.sample] +caption = "Enregistré avec Cap" +cinematic = "Chapitre un" +gradient = "Un beau texte" +headline = "Livrez plus vite" +highlight = "le point important" +kicker = "Nouveauté" +label = "Astuce" +name_tag = "Richie · Fondateur" +neon = "Éclat" +quote = "« Que tout paraisse facile »" +sticker = "Boum !" +title = "Voici Cap" +typewriter = "en train d'écrire…" + +[editor.panel.text_segment] +animation = "Animation" +background = "Arrière-plan" +color = "Couleur" +font = "Police" +font_size = "Taille" +fullscreen_help = "Met la vidéo en pause pendant l'affichage du texte, puis reprend là où elle s'était arrêtée." +glow = "Lueur" +gradient = "Dégradé" +layout = "Disposition" +letter_spacing = "Espacement" +line_height = "Interligne" +look = "Apparence" +outline = "Contour" +screen_transition = "Transition d'écran" +shadow = "Ombre" +style = "Style" +title = "Texte {number}" + +[editor.panel.timing] +end = "Fin" +start = "Début" +title = "Minutage" +to = "à" + +[editor.panel.zoom] +amount = "Niveau de zoom" +auto = "Auto" +auto_help = "Le zoom automatique suit votre curseur à l'écran." +auto_locked = "Le mode auto nécessite la capture du curseur. Activez « Capture séparée du curseur (Studio) » dans Paramètres → Général." +how_it_works = "Comment ça marche ?" +manual = "Manuel" +manual_help = "Le zoom manuel reste sur le point fixe que vous choisissez ci-dessous." +mixed = "Variable" +mode = "Mode de zoom" +positions_mixed = "Les segments zooment sur des points différents. Faites glisser pour tous les placer au même endroit." +title = "Zoom {number}" + +[editor.sidebar] +desktop_import_failed = "Impossible d'importer votre fond d'écran" +importing_image = "Importation de l'image…" + +[editor.sidebar.animated_gradient] +add_colour = "Ajouter une couleur" +colours = "Couleurs" +delete = "Supprimer" +delete_failed = "Impossible de supprimer ce dégradé. Réessayez." +fine_tune = "Ajuster" +limit_reached = { one = "Vous pouvez enregistrer jusqu'à {count} dégradé. Supprimez-en un pour en ajouter un autre.", other = "Vous pouvez enregistrer jusqu'à {count} dégradés. Supprimez-en un pour en ajouter un autre." } +motion = "Mouvement" +name_placeholder = "Nommez ce dégradé" +name_required = "Saisissez un nom pour ce dégradé." +presets = "Préréglages" +randomize = "Aléatoire" +remember_failed = "Impossible de mémoriser vos réglages de dégradé animé." +remove_colour = "Retirer la couleur" +reset_tooltip = "Réinitialiser les ajustements" +save_failed = "Impossible d'enregistrer ce dégradé. Réessayez." +save_tooltip = "Enregistrer le dégradé actuel" +saved = "Enregistrés" +still = "Immobile" + +[editor.sidebar.animated_gradient.control] +contrast = "Contraste" +curvature = "Courbure" +detail = "Détail" +direction = "Direction" +exposure = "Exposition" +flow_scale = "Échelle du flux" +flow_strength = "Force du flux" +grain_amount = "Quantité de grain" +grain_size = "Taille du grain" +highlights = "Hautes lumières" +motion_speed = "Vitesse du mouvement" +relief = "Relief" +ripple_size = "Taille des ondulations" +shading = "Ombrage" +vibrance = "Vibrance" + +[editor.sidebar.animated_gradient.group] +animation = "Animation" +colour = "Couleur" +flow = "Flux" +lighting = "Éclairage" +texture = "Texture" + +[editor.sidebar.background] +title = "Arrière-plan" + +[editor.sidebar.border] +color = "Couleur de la bordure" +opacity = "Opacité de la bordure" +title = "Bordure" +width = "Épaisseur de la bordure" + +[editor.sidebar.corner] +rounded = "Arrondi" +squircle = "Squircle" +title = "Style des coins" + +[editor.sidebar.cursor] +circle = "Cercle" +click_ripple = "Onde au clic" +color = "Couleur" +default = "Par défaut" +description_circle = "Remplace toutes les formes de curseur par un cercle." +description_default = "Conserve les formes et l'apparence du curseur de votre enregistrement." +description_macos = "Apparence macOS classique. Les curseurs personnalisés gardent leur forme enregistrée." +description_tahoe = "Apparence macOS Tahoe. Les curseurs personnalisés gardent leur forme enregistrée." +description_windows = "Apparence Windows. Les curseurs personnalisés gardent leur forme enregistrée." +duration = "Durée" +recorded = "Curseurs enregistrés" +recorded_tooltip = "Enregistré avec ce curseur" +ripple_size = "Taille" +strength = "Intensité" + +[editor.sidebar.desktop] +import = "Importer le fond du bureau" +importing = "Importation..." +prompt = "Utiliser le fond d'écran de votre bureau" +reimport = "Réimporter" + +[editor.sidebar.gradient] +angle = "Angle" +from = "De" +grain_scale = "Taille du grain" +noise = "Bruit" +to = "À" + +[editor.sidebar.image] +prompt = "Cliquez pour choisir ou glissez-déposez une image" + +[editor.sidebar.layout] +background_blur = "Flou de l'arrière-plan" +custom_position = "Position d'écran personnalisée (déplacée sur le canevas)" +motion_blur = "Flou de mouvement" +padding = "Marge" +rounded_corners = "Coins arrondis" +title = "Disposition" + +[editor.sidebar.notch] +description = "Dessine une encoche de MacBook sur l'enregistrement. Les enregistrements faits sur un Mac à encoche utilisent leurs propres mesures ; sinon, partez de la taille ci-dessous et ajustez-la." +height = "Hauteur de l'encoche" +position = "Position de l'encoche" +title = "Encoche du MacBook" +width = "Largeur de l'encoche" + +[editor.sidebar.shadow] +advanced = "Réglages avancés de l'ombre" +blur = "Flou" +opacity = "Opacité" +shadow_size = "Taille" +title = "Ombre" + +[editor.sidebar.source] +animated = "Animé" +color = "Couleur" +desktop = "Bureau" +gradient = "Dégradé" +image = "Image" +none = "Aucun" +wallpaper = "Fond d'écran" + +[editor.sidebar.style_group] +back = "Retour au style" +background = "Arrière-plan et cadre" +camera = "Caméra" +cursor = "Curseur" +editing_note = "Seul ce segment est modifié. Les paramètres globaux restent inchangés." +title = "Style {number} · {group}" + +[editor.sidebar.tab] +audio = "Audio" +background = "Arrière-plan" +camera = "Caméra" +captions = "Sous-titres" +cursor = "Curseur" +keyboard = "Clavier" + +[editor.sidebar.wallpaper_theme] +blue = "Bleu" +cities = "Villes" +dark = "Sombre" +orange = "Orange" +purple = "Violet" diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index 0ee072f4c59..d11e1863f20 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -586,3 +586,521 @@ click_or_press = "यहाँ क्लिक करें, या दबाए get_started = "शुरू करें" space_key = "स्पेस" tagline = "सबके लिए ओपन सोर्स स्क्रीन शेयरिंग ऐप।" + +# Editor sidebar, segment panels, colour correction, audio library and crop dialog. + +[editor.audio.library] +add_hint = "अपने वीडियो में ऑडियो, संगीत या दूसरी आवाज़ें जोड़ें" +add_title = "ऑडियो जोड़ें" +change_hint = "इस सेगमेंट के लिए कोई दूसरा ट्रैक चुनें" +change_title = "ऑडियो बदलें" +done = "हो गया" +import = "फ़ाइल इंपोर्ट करें" + +[editor.color] +apply_to_cursor = "कर्सर पर लागू करें" +fine_tune = "रंग बारीकी से सेट करें" +title = "कलर करेक्शन" + +[editor.color.preset.cinematic] +description = "हल्के ग्रेन के साथ टील छाया और नारंगी हाइलाइट्स" +name = "सिनेमैटिक" + +[editor.color.preset.dreamy] +description = "उठे हुए काले रंग के साथ हल्के, मुलायम पेस्टल" +name = "ड्रीमी" + +[editor.color.preset.frost] +description = "हल्के रंगों के साथ ठंडे, साफ़ टोन" +name = "फ़्रॉस्ट" + +[editor.color.preset.golden] +description = "गोल्डन आवर की गर्म चमक" +name = "सुनहरा" + +[editor.color.preset.midnight] +description = "हल्के रंगों के साथ गहरा, मूडी टील" +name = "मिडनाइट" + +[editor.color.preset.noir] +description = "ज़्यादा ग्रेन के साथ हाई-कंट्रास्ट ब्लैक एंड व्हाइट" +name = "नॉयर" + +[editor.color.preset.none] +description = "असली रंग, कोई ग्रेड नहीं" +name = "कोई नहीं" + +[editor.color.preset.vintage] +description = "हल्के कंट्रास्ट के साथ फीका गर्म फ़िल्म लुक" +name = "विंटेज" + +[editor.color.preset.vivid] +description = "दमदार सैचुरेशन और ज़्यादा कंट्रास्ट" +name = "चटकीला" + +[editor.color.slider] +contrast = "कंट्रास्ट" +exposure = "एक्सपोज़र" +fade = "फ़ेड" +grain = "ग्रेन" +saturation = "सैचुरेशन" +split_tone = "स्प्लिट टोन" +strength = "तीव्रता" +temperature = "टेम्परेचर" +tint = "टिंट" +vignette = "विनेट" + +[editor.crop] +aspect_ratio = "आस्पेक्ट रेशियो" +center = "बीच में" +dimensions = "आकार" +editing_style = "सिर्फ़ स्टाइल {number} एडिट हो रही है" +editing_style_override = "सिर्फ़ स्टाइल {number} एडिट हो रही है · सेव करने पर इसका बैकग्राउंड ओवरराइड चालू हो जाएगा" +free = "फ़्री" +full = "पूरा" +hint = "खिसकाने के लिए ड्रैग करें · तीर कुंजियों से थोड़ा खिसकाएँ · स्नैप रोकने के लिए {key} दबाए रखें" +loading_frame = "फ़्रेम लोड हो रहा है…" +position = "पोज़िशन" +preview = "प्रीव्यू" +rendering_preview = "प्रीव्यू रेंडर हो रहा है…" +snap_to_ratios = "रेशियो पर स्नैप करें" +style_changed = "क्रॉप करते समय यह स्टाइल बदल गई। रद्द करें और क्रॉप फिर से खोलें।" +title = "क्रॉप" + +[editor.panel] +delete = "हटाएँ" +done = "हो गया" +duration = "अवधि" +edge_in = "इन" +edge_out = "आउट" +enabled = "चालू" +fade_duration = "फ़ेड अवधि" +flip_horizontally = "क्षैतिज रूप से पलटें" +flip_vertically = "लंबवत रूप से पलटें" +name = "नाम" +opacity = "अपारदर्शिता" + +[editor.panel.audio] +change = "बदलें" +change_hint = "ट्रैक बदलने के लिए टैप करें" +fade_in = "फ़ेड इन" +fade_out = "फ़ेड आउट" +fallback_name = "ऑडियो" +title = "ऑडियो {number}" +volume = "वॉल्यूम" + +[editor.panel.camera3d] +already_still = "पहले से स्थिर शॉट है" +amount = "मात्रा" +angles = "एंगल" +auto_scene = "ऑटो सीन" +auto_scene_tooltip = "ट्रैक के सभी 3D शॉट फिर से बनाता है" +bokeh = "बोकेह" +camera = "कैमरा" +delete_tooltip = "यह शॉट हटाएँ" +depth_blur = "डेप्थ ब्लर" +distance = "दूरी" +drag_to_orbit = "घुमाने के लिए ड्रैग करें" +ease_in = "ईज़ इन" +ease_out = "ईज़ आउट" +end_same_as_start = "अंत · शुरुआत जैसा" +fine_tune = "बारीकी से सेट करें" +focus = "फ़ोकस" +header = "3D शॉट · {look} · {duration}s" +hold_opening = "पूरे शॉट में शुरुआती पोज़ बनाए रखें" +lens = "लेंस" +look_title = "लुक" +motion_style = "मोशन स्टाइल" +moves = "मूव" +nothing_to_swap = "स्थिर शॉट में बदलने को कुछ नहीं है" +play_shot = "चलाएँ" +play_shot_tooltip = "यह शॉट चलाएँ" +pose_end = "अंत · {time}" +pose_start = "शुरू · {time}" +reset_camera = "कैमरा रीसेट करें" +roll = "रोल" +rotate_x = "घुमाव X" +rotate_y = "घुमाव Y" +shift_x = "शिफ़्ट X" +shift_y = "शिफ़्ट Y" +shots = { one = "{count} शॉट", other = "{count} शॉट" } +still_shot = "स्थिर शॉट" +swap = "शुरुआत और अंत बदलें" +timing_advanced = "टाइमिंग और एडवांस्ड" +timing_summary = "{easing} · लेंस {fov}" + +[editor.panel.camera3d.blur] +angle = "कोण" +falloff = "फ़ॉलऑफ़" +focus_size = "फ़ोकस आकार" +focus_x = "फ़ोकस X" +focus_y = "फ़ोकस Y" +position = "पोज़िशन" +scan = "स्कैन" +strength = "तीव्रता" + +[editor.panel.camera3d.easing] +ease_in = "ईज़ इन" +ease_out = "ईज़ आउट" +linear = "लीनियर" +smooth = "स्मूद" + +[editor.panel.camera3d.focus_mode] +directional = "दिशात्मक" +radial = "रेडियल" +tilt_shift = "टिल्ट शिफ़्ट" + +[editor.panel.camera3d.look] +center = "केंद्र" +close_up = "क्लोज़-अप" +drift_down = "नीचे बहाव" +glide_across = "आर-पार ग्लाइड" +low_angle = "लो एंगल" +perspective = "पर्सपेक्टिव" +pull_back = "पीछे खींचें" +rising_sweep = "उठता स्वीप" +slide_by = "स्लाइड बाय" +spotlight = "स्पॉटलाइट" +tilt_away = "झुककर दूर" +top_down = "ऊपर से" +unfold = "खुलना" + +[editor.panel.camera3d.sequence] +product_tour = "प्रोडक्ट टूर" +punch_in = "पंच इन" +showcase = "शोकेस" + +[editor.panel.camera3d.shot] +custom = "कस्टम मूव" +still = "स्थिर शॉट" + +[editor.panel.caption] +title = "कैप्शन {number}" + +[editor.panel.font] +system_mono = "सिस्टम Mono" +system_sans = "सिस्टम Sans" +system_serif = "सिस्टम Serif" + +[editor.panel.font_weight] +black = "ब्लैक" +bold = "बोल्ड" +custom = "कस्टम ({weight})" +extra_bold = "एक्स्ट्रा बोल्ड" +light = "लाइट" +medium = "मीडियम" +regular = "रेगुलर" +semibold = "सेमीबोल्ड" + +[editor.panel.image] +arrange = "कैनवास पर व्यवस्थित करें" +arrange_help = "इमेज को खिसकाने के लिए ड्रैग करें। आकार बदलने के लिए कोना खींचें, या घुमाने के लिए नीचे दिए गए रोटेशन कंट्रोल का इस्तेमाल करें।" +center = "कैनवास के बीच में रखें" +lock_aspect = "आस्पेक्ट रेशियो लॉक करें" +missing = "इमेज फ़ाइल नहीं मिली। इसे बदलें, ताकि यह सेगमेंट अपनी टाइमिंग और बदलावों के साथ वापस आ जाए।" +replace = "इमेज बदलें" +rotation = "रोटेशन" +rounding = "गोलाई" + +[editor.panel.keyboard] +title = "कीबोर्ड {number}" + +[editor.panel.mask] +blur = "ब्लर" +effect = "इफ़ेक्ट" +highlight = "हाइलाइट" +outside_darkness = "बाहरी अंधेरा" +pixel_size = "पिक्सल आकार" +pixelate = "पिक्सलेट" +sensitive = "संवेदनशील" +title = "मास्क {number}" + +[editor.panel.scene] +camera_layout = "कैमरा लेआउट" +camera_position = "कैमरा पोज़िशन" +camera_zoom = "कैमरा ज़ूम" +screen_position = "स्क्रीन पोज़िशन" +screen_zoom = "स्क्रीन ज़ूम" +transition = "ट्रांज़िशन" + +[editor.panel.scene.description] +camera_only = "सिर्फ़ कैमरा फ़ीड दिखाता है" +default = "स्क्रीन और कैमरा दोनों दिखाता है" +floating = "स्क्रीन और कैमरा बैकग्राउंड के ऊपर गोल कोनों वाले कार्ड की तरह साथ-साथ तैरते हैं" +hide_camera = "सिर्फ़ स्क्रीन रिकॉर्डिंग दिखाता है" +split_screen = "स्क्रीन और कैमरा साथ-साथ (पोर्ट्रेट में अपने आप ऊपर-नीचे)" + +[editor.panel.scene.mode] +camera_only = "सिर्फ़ कैमरा" +default = "डिफ़ॉल्ट" +floating = "फ़्लोटिंग" +hide_camera = "कैमरा छिपाएँ" +split_screen = "स्प्लिट स्क्रीन" + +[editor.panel.style] +camera_only_background = "सिर्फ़ कैमरा बैकग्राउंड" +camera_only_help = "सिर्फ़ कैमरा वाला सीन इस्तेमाल करें। पैडिंग से कैमरे के आस-पास बैकग्राउंड दिखता है।" +camera_only_padding = "सिर्फ़ कैमरा पैडिंग" +crop_height = "ऊँचाई" +crop_left = "बाएँ" +crop_title = "स्क्रीन क्रॉप (सोर्स पिक्सल)" +crop_top = "ऊपर" +crop_width = "चौड़ाई" +edit_background = "बैकग्राउंड और फ़्रेम एडिट करें" +edit_camera = "कैमरा एडिट करें" +edit_cursor = "कर्सर एडिट करें" +help = "बदलाव सिर्फ़ इस सेगमेंट के दौरान लागू होते हैं। किसी ग्रुप की ग्लोबल सेटिंग्स कॉपी करने के लिए उसे चालू करें।" +reset_crop = "क्रॉप रीसेट करें" + +[editor.panel.text_animation] +bounce = "बाउंस" +fade = "फ़ेड" +letters = "अक्षर" +none = "कोई नहीं" +pop = "पॉप" +slide_down = "नीचे स्लाइड" +slide_left = "बाएँ स्लाइड" +slide_right = "दाएँ स्लाइड" +slide_up = "ऊपर स्लाइड" +tracking = "ट्रैकिंग" +typewriter = "टाइपराइटर" +wipe = "वाइप" +words = "शब्द" +zoom = "ज़ूम" + +[editor.panel.text_background] +box_shape = "बॉक्स" +highlight = "हाइलाइट" +none = "कोई नहीं" +pill = "पिल" + +[editor.panel.text_layout] +fullscreen = "फ़ुलस्क्रीन" +overlay = "ओवरले" + +[editor.panel.text_preset.group] +all = "सभी" +callouts = "कॉलआउट" +code = "कोड" +lower_thirds = "लोअर थर्ड" +statements = "स्टेटमेंट" +titles = "टाइटल" + +[editor.panel.text_preset.name] +caption = "कैप्शन" +cinematic = "सिनेमैटिक" +code = "कोड" +gradient = "ग्रेडिएंट" +headline = "हेडलाइन" +highlight = "हाइलाइट" +kicker = "किकर" +label = "लेबल" +lower_third = "लोअर थर्ड" +name_tag = "नेम टैग" +neon = "नियॉन" +quote = "कोट" +stat = "बड़ा आंकड़ा" +sticker = "स्टिकर" +title = "टाइटल" +typewriter = "टाइपराइटर" + +[editor.panel.text_preset.sample] +caption = "Cap से रिकॉर्ड किया गया" +cinematic = "पहला अध्याय" +gradient = "सुंदर टेक्स्ट" +headline = "तेज़ी से शिप करें" +highlight = "ज़रूरी हिस्सा" +kicker = "नया फ़ीचर" +label = "प्रो टिप" +name_tag = "Richie · संस्थापक" +neon = "चमक उठो" +quote = "“इसे सहज बनाइए”" +sticker = "धमाका!" +title = "पेश है Cap" +typewriter = "टाइप हो रहा है…" + +[editor.panel.text_segment] +animation = "एनिमेशन" +background = "बैकग्राउंड" +color = "रंग" +font = "फ़ॉन्ट" +font_size = "आकार" +fullscreen_help = "टेक्स्ट दिखने तक वीडियो रुका रहता है, फिर वहीं से दोबारा चलता है।" +glow = "चमक" +gradient = "ग्रेडिएंट" +layout = "लेआउट" +letter_spacing = "अक्षर अंतर" +line_height = "लाइन ऊँचाई" +look = "लुक" +outline = "आउटलाइन" +screen_transition = "स्क्रीन ट्रांज़िशन" +shadow = "छाया" +style = "स्टाइल" +title = "टेक्स्ट {number}" + +[editor.panel.timing] +end = "अंत" +start = "शुरू" +title = "टाइमिंग" +to = "से" + +[editor.panel.zoom] +amount = "ज़ूम मात्रा" +auto = "ऑटो" +auto_help = "ऑटोमैटिक ज़ूम स्क्रीन पर आपके कर्सर के साथ चलता है।" +auto_locked = "ऑटो मोड के लिए कर्सर कैप्चर ज़रूरी है। सेटिंग्स → सामान्य में \"कर्सर को अलग से कैप्चर करें (Studio)\" चालू करें।" +how_it_works = "यह कैसे काम करता है?" +manual = "मैनुअल" +manual_help = "मैनुअल ज़ूम नीचे चुनी गई एक तय जगह पर रहता है।" +mixed = "मिश्रित" +mode = "ज़ूम मोड" +positions_mixed = "सेगमेंट अलग-अलग जगहों पर ज़ूम करते हैं। सभी को एक ही जगह ले जाने के लिए ड्रैग करें।" +title = "ज़ूम {number}" + +[editor.sidebar] +desktop_import_failed = "आपका डेस्कटॉप वॉलपेपर इंपोर्ट नहीं हो सका" +importing_image = "इमेज इंपोर्ट हो रही है…" + +[editor.sidebar.animated_gradient] +add_colour = "रंग जोड़ें" +colours = "रंग" +delete = "हटाएँ" +delete_failed = "यह ग्रेडिएंट हटाया नहीं जा सका। फिर से कोशिश करें।" +fine_tune = "बारीकी से सेट करें" +limit_reached = { one = "आप ज़्यादा से ज़्यादा {count} ग्रेडिएंट सेव कर सकते हैं। नया जोड़ने के लिए एक हटाएँ।", other = "आप ज़्यादा से ज़्यादा {count} ग्रेडिएंट सेव कर सकते हैं। नया जोड़ने के लिए एक हटाएँ।" } +motion = "मोशन" +name_placeholder = "इस ग्रेडिएंट का नाम रखें" +name_required = "इस ग्रेडिएंट का नाम लिखें।" +presets = "प्रीसेट" +randomize = "रैंडम" +remember_failed = "आपकी एनिमेटेड ग्रेडिएंट सेटिंग्स याद नहीं रखी जा सकीं।" +remove_colour = "रंग हटाएँ" +reset_tooltip = "बारीक सेटिंग्स रीसेट करें" +save_failed = "यह ग्रेडिएंट सेव नहीं हो सका। फिर से कोशिश करें।" +save_tooltip = "मौजूदा ग्रेडिएंट सेव करें" +saved = "सेव किए गए" +still = "स्थिर" + +[editor.sidebar.animated_gradient.control] +contrast = "कंट्रास्ट" +curvature = "वक्रता" +detail = "डिटेल" +direction = "दिशा" +exposure = "एक्सपोज़र" +flow_scale = "फ़्लो स्केल" +flow_strength = "फ़्लो की ताकत" +grain_amount = "ग्रेन मात्रा" +grain_size = "ग्रेन आकार" +highlights = "हाइलाइट्स" +motion_speed = "मोशन स्पीड" +relief = "उभार" +ripple_size = "रिपल आकार" +shading = "शेडिंग" +vibrance = "वाइब्रेंस" + +[editor.sidebar.animated_gradient.group] +animation = "एनिमेशन" +colour = "रंग" +flow = "फ़्लो" +lighting = "लाइटिंग" +texture = "टेक्सचर" + +[editor.sidebar.background] +title = "बैकग्राउंड" + +[editor.sidebar.border] +color = "बॉर्डर रंग" +opacity = "बॉर्डर अपारदर्शिता" +title = "बॉर्डर" +width = "बॉर्डर चौड़ाई" + +[editor.sidebar.corner] +rounded = "गोल" +squircle = "स्क्वर्कल" +title = "कोने की स्टाइल" + +[editor.sidebar.cursor] +circle = "गोला" +click_ripple = "क्लिक रिपल" +color = "रंग" +default = "डिफ़ॉल्ट" +description_circle = "सभी कर्सर आकारों को गोले से बदल देता है।" +description_default = "आपकी रिकॉर्डिंग के कर्सर का आकार और रूप वैसा ही रखता है।" +description_macos = "क्लासिक macOS रूप। कस्टम कर्सर का रिकॉर्ड किया गया आकार बना रहता है।" +description_tahoe = "macOS Tahoe रूप। कस्टम कर्सर का रिकॉर्ड किया गया आकार बना रहता है।" +description_windows = "Windows रूप। कस्टम कर्सर का रिकॉर्ड किया गया आकार बना रहता है।" +duration = "अवधि" +recorded = "रिकॉर्ड किए गए कर्सर" +recorded_tooltip = "इस कर्सर से रिकॉर्ड किया गया" +ripple_size = "आकार" +strength = "तीव्रता" + +[editor.sidebar.desktop] +import = "डेस्कटॉप बैकग्राउंड इंपोर्ट करें" +importing = "इंपोर्ट हो रहा है..." +prompt = "अपने डेस्कटॉप का वॉलपेपर इस्तेमाल करें" +reimport = "फिर से इंपोर्ट करें" + +[editor.sidebar.gradient] +angle = "कोण" +from = "से" +grain_scale = "ग्रेन स्केल" +noise = "नॉइज़" +to = "तक" + +[editor.sidebar.image] +prompt = "चुनने के लिए क्लिक करें या इमेज ड्रैग करके छोड़ें" + +[editor.sidebar.layout] +background_blur = "बैकग्राउंड ब्लर" +custom_position = "कस्टम स्क्रीन पोज़िशन (कैनवास पर ड्रैग की गई)" +motion_blur = "मोशन ब्लर" +padding = "पैडिंग" +rounded_corners = "गोल कोने" +title = "लेआउट" + +[editor.sidebar.notch] +description = "रिकॉर्डिंग के ऊपर MacBook नॉच बनाता है। नॉच वाले Mac पर बनी रिकॉर्डिंग अपने माप इस्तेमाल करती हैं; वरना नीचे दिए आकार से शुरू करें और मिलान होने तक बदलें।" +height = "नॉच ऊँचाई" +position = "नॉच पोज़िशन" +title = "MacBook नॉच" +width = "नॉच चौड़ाई" + +[editor.sidebar.shadow] +advanced = "छाया की एडवांस्ड सेटिंग्स" +blur = "ब्लर" +opacity = "अपारदर्शिता" +shadow_size = "आकार" +title = "छाया" + +[editor.sidebar.source] +animated = "एनिमेटेड" +color = "रंग" +desktop = "डेस्कटॉप" +gradient = "ग्रेडिएंट" +image = "इमेज" +none = "कोई नहीं" +wallpaper = "वॉलपेपर" + +[editor.sidebar.style_group] +back = "स्टाइल पर वापस जाएँ" +background = "बैकग्राउंड और फ़्रेम" +camera = "कैमरा" +cursor = "कर्सर" +editing_note = "सिर्फ़ यह सेगमेंट एडिट हो रहा है। ग्लोबल सेटिंग्स नहीं बदलतीं।" +title = "स्टाइल {number} · {group}" + +[editor.sidebar.tab] +audio = "ऑडियो" +background = "बैकग्राउंड" +camera = "कैमरा" +captions = "कैप्शन" +cursor = "कर्सर" +keyboard = "कीबोर्ड" + +[editor.sidebar.wallpaper_theme] +blue = "नीला" +cities = "शहर" +dark = "डार्क" +orange = "नारंगी" +purple = "बैंगनी" diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 44dd3f99308..d224e8b0dd7 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -586,3 +586,521 @@ click_or_press = "ここをクリックするか、次のキーを押す:" get_started = "はじめる" space_key = "スペース" tagline = "誰もが使えるオープンソースの画面共有アプリ。" + +# Editor sidebar, segment panels, colour correction, audio library and crop dialog. + +[editor.audio.library] +add_hint = "動画にオーディオや音楽などのサウンドを追加" +add_title = "オーディオを追加" +change_hint = "このセグメントに別のトラックを選ぶ" +change_title = "オーディオを変更" +done = "完了" +import = "ファイルを読み込む" + +[editor.color] +apply_to_cursor = "カーソルにも適用" +fine_tune = "色を微調整" +title = "色補正" + +[editor.color.preset.cinematic] +description = "ティールの影とオレンジのハイライトに軽い粒子" +name = "シネマティック" + +[editor.color.preset.dreamy] +description = "黒を持ち上げた柔らかく軽やかなパステル" +name = "ドリーミー" + +[editor.color.preset.frost] +description = "落ち着いた色のクールで澄んだトーン" +name = "フロスト" + +[editor.color.preset.golden] +description = "暖かいゴールデンアワーの光" +name = "ゴールデン" + +[editor.color.preset.midnight] +description = "落ち着いた色合いの暗くムーディーなティール" +name = "ミッドナイト" + +[editor.color.preset.noir] +description = "強い粒子のハイコントラストな白黒" +name = "ノワール" + +[editor.color.preset.none] +description = "元の色のまま、補正なし" +name = "なし" + +[editor.color.preset.vintage] +description = "柔らかいコントラストの色あせた暖かいフィルム調" +name = "ヴィンテージ" + +[editor.color.preset.vivid] +description = "彩度とコントラストを力強く強調" +name = "ビビッド" + +[editor.color.slider] +contrast = "コントラスト" +exposure = "露出" +fade = "フェード" +grain = "粒子" +saturation = "彩度" +split_tone = "スプリットトーン" +strength = "強さ" +temperature = "色温度" +tint = "色かぶり" +vignette = "周辺減光" + +[editor.crop] +aspect_ratio = "縦横比" +center = "中央" +dimensions = "サイズ" +editing_style = "スタイル{number}のみを編集中" +editing_style_override = "スタイル{number}のみを編集中 · 保存すると背景の上書きがオンになります" +free = "自由" +full = "全体" +hint = "ドラッグで移動 · 矢印キーで微調整 · {key}を押したままでスナップしない" +loading_frame = "フレームを読み込み中…" +position = "位置" +preview = "プレビュー" +rendering_preview = "プレビューを描画中…" +snap_to_ratios = "比率にスナップ" +style_changed = "切り抜き中にこのスタイルが変更されました。キャンセルして切り抜きを開き直してください。" +title = "切り抜き" + +[editor.panel] +delete = "削除" +done = "完了" +duration = "長さ" +edge_in = "イン" +edge_out = "アウト" +enabled = "有効" +fade_duration = "フェードの長さ" +flip_horizontally = "左右反転" +flip_vertically = "上下反転" +name = "名前" +opacity = "不透明度" + +[editor.panel.audio] +change = "変更" +change_hint = "タップしてトラックを変更" +fade_in = "フェードイン" +fade_out = "フェードアウト" +fallback_name = "オーディオ" +title = "オーディオ{number}" +volume = "音量" + +[editor.panel.camera3d] +already_still = "すでに静止ショットです" +amount = "量" +angles = "アングル" +auto_scene = "自動シーン" +auto_scene_tooltip = "トラック上のすべての3Dショットを作り直します" +bokeh = "ボケ" +camera = "カメラ" +delete_tooltip = "このショットを削除" +depth_blur = "被写界深度ぼかし" +distance = "距離" +drag_to_orbit = "ドラッグで回転" +ease_in = "イーズイン" +ease_out = "イーズアウト" +end_same_as_start = "終了 · 開始と同じ" +fine_tune = "微調整" +focus = "フォーカス" +header = "3Dショット · {look} · {duration}秒" +hold_opening = "ショット全体で開始時のポーズを保持" +lens = "レンズ" +look_title = "ルック" +motion_style = "動きのスタイル" +moves = "動き" +nothing_to_swap = "静止ショットでは入れ替えるものがありません" +play_shot = "再生" +play_shot_tooltip = "このショットを再生" +pose_end = "終了 · {time}" +pose_start = "開始 · {time}" +reset_camera = "カメラをリセット" +roll = "ロール" +rotate_x = "回転X" +rotate_y = "回転Y" +shift_x = "シフトX" +shift_y = "シフトY" +shots = { other = "{count}ショット" } +still_shot = "静止ショット" +swap = "開始と終了を入れ替える" +timing_advanced = "タイミングと詳細" +timing_summary = "{easing} · レンズ{fov}" + +[editor.panel.camera3d.blur] +angle = "角度" +falloff = "減衰" +focus_size = "焦点サイズ" +focus_x = "焦点X" +focus_y = "焦点Y" +position = "位置" +scan = "スキャン" +strength = "強さ" + +[editor.panel.camera3d.easing] +ease_in = "イーズイン" +ease_out = "イーズアウト" +linear = "リニア" +smooth = "スムーズ" + +[editor.panel.camera3d.focus_mode] +directional = "方向" +radial = "放射" +tilt_shift = "ティルトシフト" + +[editor.panel.camera3d.look] +center = "中央" +close_up = "クローズアップ" +drift_down = "ゆっくり下降" +glide_across = "横に滑る" +low_angle = "ローアングル" +perspective = "パース" +pull_back = "引き" +rising_sweep = "上昇スイープ" +slide_by = "スライド" +spotlight = "スポットライト" +tilt_away = "傾けて離す" +top_down = "真上から" +unfold = "展開" + +[editor.panel.camera3d.sequence] +product_tour = "製品ツアー" +punch_in = "パンチイン" +showcase = "ショーケース" + +[editor.panel.camera3d.shot] +custom = "カスタムの動き" +still = "静止ショット" + +[editor.panel.caption] +title = "字幕{number}" + +[editor.panel.font] +system_mono = "システムMono" +system_sans = "システムSans" +system_serif = "システムSerif" + +[editor.panel.font_weight] +black = "ブラック" +bold = "太字" +custom = "カスタム({weight})" +extra_bold = "極太" +light = "細字" +medium = "中字" +regular = "標準" +semibold = "やや太字" + +[editor.panel.image] +arrange = "キャンバス上で配置" +arrange_help = "画像をドラッグして移動します。角を引っ張るとサイズを変更でき、下の回転コントロールで回転できます。" +center = "キャンバスの中央に配置" +lock_aspect = "縦横比を固定" +missing = "画像ファイルが見つかりません。置き換えると、タイミングと変形を保ったままこのセグメントを復元できます。" +replace = "画像を置き換え" +rotation = "回転" +rounding = "角丸" + +[editor.panel.keyboard] +title = "キーボード{number}" + +[editor.panel.mask] +blur = "ぼかし" +effect = "エフェクト" +highlight = "強調" +outside_darkness = "外側の暗さ" +pixel_size = "ピクセルサイズ" +pixelate = "モザイク" +sensitive = "機密" +title = "マスク{number}" + +[editor.panel.scene] +camera_layout = "カメラのレイアウト" +camera_position = "カメラの位置" +camera_zoom = "カメラのズーム" +screen_position = "画面の位置" +screen_zoom = "画面のズーム" +transition = "トランジション" + +[editor.panel.scene.description] +camera_only = "カメラの映像のみを表示" +default = "画面とカメラの両方を表示" +floating = "画面とカメラが角丸のカードとして背景の上に並んで浮かびます" +hide_camera = "画面の録画のみを表示" +split_screen = "画面とカメラを横に並べて表示(縦向きでは自動で縦に並びます)" + +[editor.panel.scene.mode] +camera_only = "カメラのみ" +default = "デフォルト" +floating = "フローティング" +hide_camera = "カメラを隠す" +split_screen = "分割画面" + +[editor.panel.style] +camera_only_background = "カメラのみの背景" +camera_only_help = "カメラのみのシーンを使ってください。余白を付けるとカメラの周りに背景が見えます。" +camera_only_padding = "カメラのみの余白" +crop_height = "高さ" +crop_left = "左" +crop_title = "画面の切り抜き(元のピクセル)" +crop_top = "上" +crop_width = "幅" +edit_background = "背景とフレームを編集" +edit_camera = "カメラを編集" +edit_cursor = "カーソルを編集" +help = "上書きはこのセグメントの間だけ適用されます。グループをオンにすると全体設定がコピーされます。" +reset_crop = "切り抜きをリセット" + +[editor.panel.text_animation] +bounce = "バウンス" +fade = "フェード" +letters = "文字ごと" +none = "なし" +pop = "ポップ" +slide_down = "下へスライド" +slide_left = "左へスライド" +slide_right = "右へスライド" +slide_up = "上へスライド" +tracking = "字間" +typewriter = "タイプライター" +wipe = "ワイプ" +words = "単語ごと" +zoom = "ズーム" + +[editor.panel.text_background] +box_shape = "ボックス" +highlight = "ハイライト" +none = "なし" +pill = "ピル" + +[editor.panel.text_layout] +fullscreen = "全画面" +overlay = "オーバーレイ" + +[editor.panel.text_preset.group] +all = "すべて" +callouts = "コールアウト" +code = "コード" +lower_thirds = "テロップ" +statements = "ステートメント" +titles = "タイトル" + +[editor.panel.text_preset.name] +caption = "キャプション" +cinematic = "シネマティック" +code = "コード" +gradient = "グラデーション" +headline = "見出し" +highlight = "ハイライト" +kicker = "キッカー" +label = "ラベル" +lower_third = "テロップ" +name_tag = "名札" +neon = "ネオン" +quote = "引用" +stat = "大きな数字" +sticker = "ステッカー" +title = "タイトル" +typewriter = "タイプライター" + +[editor.panel.text_preset.sample] +caption = "Capで録画" +cinematic = "第一章" +gradient = "美しいテキスト" +headline = "もっと速くリリース" +highlight = "大事なところ" +kicker = "新機能" +label = "プロのヒント" +name_tag = "Richie · 創業者" +neon = "グローアップ" +quote = "「軽やかに感じさせて」" +sticker = "ドーン!" +title = "Capのご紹介" +typewriter = "タイプ中…" + +[editor.panel.text_segment] +animation = "アニメーション" +background = "背景" +color = "色" +font = "フォント" +font_size = "サイズ" +fullscreen_help = "テキストの表示中は動画を一時停止し、その後止めた位置から再開します。" +glow = "グロー" +gradient = "グラデーション" +layout = "レイアウト" +letter_spacing = "字間" +line_height = "行の高さ" +look = "見た目" +outline = "アウトライン" +screen_transition = "画面の切り替え" +shadow = "影" +style = "スタイル" +title = "テキスト{number}" + +[editor.panel.timing] +end = "終了" +start = "開始" +title = "タイミング" +to = "〜" + +[editor.panel.zoom] +amount = "ズーム量" +auto = "自動" +auto_help = "自動ズームは画面上のカーソルを追いかけます。" +auto_locked = "自動モードにはカーソルのキャプチャが必要です。「設定 → 一般」で「カーソルの個別キャプチャ(Studio)」をオンにしてください。" +how_it_works = "仕組み" +manual = "手動" +manual_help = "手動ズームは下で選んだ位置に固定されます。" +mixed = "混在" +mode = "ズームモード" +positions_mixed = "セグメントごとにズーム位置が異なります。ドラッグするとすべて同じ位置に移動します。" +title = "ズーム{number}" + +[editor.sidebar] +desktop_import_failed = "デスクトップの壁紙を読み込めませんでした" +importing_image = "画像を読み込み中…" + +[editor.sidebar.animated_gradient] +add_colour = "色を追加" +colours = "色" +delete = "削除" +delete_failed = "このグラデーションを削除できませんでした。もう一度お試しください。" +fine_tune = "微調整" +limit_reached = { other = "保存できるグラデーションは{count}個までです。追加するには1つ削除してください。" } +motion = "動き" +name_placeholder = "このグラデーションの名前" +name_required = "このグラデーションの名前を入力してください。" +presets = "プリセット" +randomize = "ランダム" +remember_failed = "アニメーショングラデーションの設定を保存できませんでした。" +remove_colour = "色を削除" +reset_tooltip = "微調整をリセット" +save_failed = "このグラデーションを保存できませんでした。もう一度お試しください。" +save_tooltip = "現在のグラデーションを保存" +saved = "保存済み" +still = "静止" + +[editor.sidebar.animated_gradient.control] +contrast = "コントラスト" +curvature = "曲率" +detail = "ディテール" +direction = "方向" +exposure = "露出" +flow_scale = "流れの大きさ" +flow_strength = "流れの強さ" +grain_amount = "粒子の量" +grain_size = "粒子の大きさ" +highlights = "ハイライト" +motion_speed = "動きの速さ" +relief = "起伏" +ripple_size = "波紋の大きさ" +shading = "陰影" +vibrance = "自然な彩度" + +[editor.sidebar.animated_gradient.group] +animation = "アニメーション" +colour = "色" +flow = "流れ" +lighting = "ライティング" +texture = "テクスチャ" + +[editor.sidebar.background] +title = "背景" + +[editor.sidebar.border] +color = "枠線の色" +opacity = "枠線の不透明度" +title = "枠線" +width = "枠線の太さ" + +[editor.sidebar.corner] +rounded = "角丸" +squircle = "スクワークル" +title = "角のスタイル" + +[editor.sidebar.cursor] +circle = "円" +click_ripple = "クリックの波紋" +color = "色" +default = "デフォルト" +description_circle = "すべてのカーソルを円に置き換えます。" +description_default = "録画時のカーソルの形と見た目をそのまま使います。" +description_macos = "従来のmacOSの見た目です。カスタムカーソルは録画時の形のままです。" +description_tahoe = "macOS Tahoeの見た目です。カスタムカーソルは録画時の形のままです。" +description_windows = "Windowsの見た目です。カスタムカーソルは録画時の形のままです。" +duration = "長さ" +recorded = "録画したカーソル" +recorded_tooltip = "このカーソルで録画" +ripple_size = "サイズ" +strength = "強さ" + +[editor.sidebar.desktop] +import = "デスクトップの背景を読み込む" +importing = "読み込み中..." +prompt = "デスクトップの壁紙を使う" +reimport = "再読み込み" + +[editor.sidebar.gradient] +angle = "角度" +from = "開始" +grain_scale = "粒子の大きさ" +noise = "ノイズ" +to = "終了" + +[editor.sidebar.image] +prompt = "クリックして選択するか画像をドラッグ&ドロップ" + +[editor.sidebar.layout] +background_blur = "背景のぼかし" +custom_position = "カスタムの画面位置(キャンバスでドラッグ)" +motion_blur = "モーションブラー" +padding = "余白" +rounded_corners = "角丸" +title = "レイアウト" + +[editor.sidebar.notch] +description = "録画の上にMacBookのノッチを描きます。ノッチのあるMacで録画した場合はその寸法を使います。それ以外の場合は下のサイズから始めて、合うように調整してください。" +height = "ノッチの高さ" +position = "ノッチの位置" +title = "MacBookのノッチ" +width = "ノッチの幅" + +[editor.sidebar.shadow] +advanced = "影の詳細設定" +blur = "ぼかし" +opacity = "不透明度" +shadow_size = "サイズ" +title = "影" + +[editor.sidebar.source] +animated = "アニメ" +color = "色" +desktop = "デスクトップ" +gradient = "グラデ" +image = "画像" +none = "なし" +wallpaper = "壁紙" + +[editor.sidebar.style_group] +back = "スタイルに戻る" +background = "背景とフレーム" +camera = "カメラ" +cursor = "カーソル" +editing_note = "このセグメントのみを編集しています。全体設定は変わりません。" +title = "スタイル{number} · {group}" + +[editor.sidebar.tab] +audio = "オーディオ" +background = "背景" +camera = "カメラ" +captions = "字幕" +cursor = "カーソル" +keyboard = "キーボード" + +[editor.sidebar.wallpaper_theme] +blue = "ブルー" +cities = "都市" +dark = "ダーク" +orange = "オレンジ" +purple = "パープル" diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index faaf888950e..3ca660c52cb 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -586,3 +586,521 @@ click_or_press = "点击此处,或按下" get_started = "开始使用" space_key = "空格" tagline = "人人可用的开源屏幕分享应用。" + +# Editor sidebar, segment panels, colour correction, audio library and crop dialog. + +[editor.audio.library] +add_hint = "为视频添加音频、音乐或其他声音" +add_title = "添加音频" +change_hint = "为此片段选择其他音轨" +change_title = "更换音频" +done = "完成" +import = "导入文件" + +[editor.color] +apply_to_cursor = "应用到光标" +fine_tune = "微调颜色" +title = "色彩校正" + +[editor.color.preset.cinematic] +description = "青绿色阴影与橙色高光,带轻微颗粒" +name = "电影感" + +[editor.color.preset.dreamy] +description = "柔和轻盈的粉彩,黑色提亮" +name = "梦幻" + +[editor.color.preset.frost] +description = "清冷通透的色调,颜色柔和" +name = "冰霜" + +[editor.color.preset.golden] +description = "温暖的黄金时刻光晕" +name = "金色" + +[editor.color.preset.midnight] +description = "深沉忧郁的青绿色,颜色低调" +name = "午夜" + +[editor.color.preset.noir] +description = "高对比度黑白,带浓重颗粒" +name = "黑色电影" + +[editor.color.preset.none] +description = "原始颜色,未调色" +name = "无" + +[editor.color.preset.vintage] +description = "褪色暖调胶片感,对比柔和" +name = "复古" + +[editor.color.preset.vivid] +description = "饱和度与对比度大幅提升" +name = "鲜艳" + +[editor.color.slider] +contrast = "对比度" +exposure = "曝光" +fade = "褪色" +grain = "颗粒" +saturation = "饱和度" +split_tone = "分离色调" +strength = "强度" +temperature = "色温" +tint = "色调" +vignette = "暗角" + +[editor.crop] +aspect_ratio = "宽高比" +center = "居中" +dimensions = "大小" +editing_style = "仅编辑样式 {number}" +editing_style_override = "仅编辑样式 {number} · 保存后将启用其背景覆盖" +free = "自由" +full = "全部" +hint = "拖动以移动 · 方向键微调 · 按住 {key} 跳过吸附" +loading_frame = "正在加载画面…" +position = "位置" +preview = "预览" +rendering_preview = "正在渲染预览…" +snap_to_ratios = "吸附到比例" +style_changed = "裁剪期间此样式已更改。请取消并重新打开裁剪。" +title = "裁剪" + +[editor.panel] +delete = "删除" +done = "完成" +duration = "时长" +edge_in = "入场" +edge_out = "出场" +enabled = "已启用" +fade_duration = "淡化时长" +flip_horizontally = "水平翻转" +flip_vertically = "垂直翻转" +name = "名称" +opacity = "不透明度" + +[editor.panel.audio] +change = "更换" +change_hint = "点按以更换音轨" +fade_in = "淡入" +fade_out = "淡出" +fallback_name = "音频" +title = "音频 {number}" +volume = "音量" + +[editor.panel.camera3d] +already_still = "已是静止镜头" +amount = "强度" +angles = "角度" +auto_scene = "自动场景" +auto_scene_tooltip = "重新生成轨道上的所有 3D 镜头" +bokeh = "焦外光斑" +camera = "摄像机" +delete_tooltip = "删除此镜头" +depth_blur = "景深模糊" +distance = "距离" +drag_to_orbit = "拖动以环绕" +ease_in = "缓入" +ease_out = "缓出" +end_same_as_start = "结束 · 与开始相同" +fine_tune = "微调" +focus = "焦点" +header = "3D 镜头 · {look} · {duration} 秒" +hold_opening = "整个镜头保持起始姿态" +lens = "镜头" +look_title = "镜头" +motion_style = "运动方式" +moves = "运镜" +nothing_to_swap = "静止镜头无需交换" +play_shot = "播放" +play_shot_tooltip = "播放此镜头" +pose_end = "结束 · {time}" +pose_start = "开始 · {time}" +reset_camera = "重置摄像机" +roll = "滚转" +rotate_x = "旋转 X" +rotate_y = "旋转 Y" +shift_x = "平移 X" +shift_y = "平移 Y" +shots = { other = "{count} 个镜头" } +still_shot = "静止镜头" +swap = "交换开始和结束" +timing_advanced = "时间与高级" +timing_summary = "{easing} · 镜头 {fov}" + +[editor.panel.camera3d.blur] +angle = "角度" +falloff = "衰减" +focus_size = "焦点大小" +focus_x = "焦点 X" +focus_y = "焦点 Y" +position = "位置" +scan = "扫描" +strength = "强度" + +[editor.panel.camera3d.easing] +ease_in = "缓入" +ease_out = "缓出" +linear = "线性" +smooth = "平滑" + +[editor.panel.camera3d.focus_mode] +directional = "定向" +radial = "径向" +tilt_shift = "移轴" + +[editor.panel.camera3d.look] +center = "居中" +close_up = "特写" +drift_down = "缓缓下移" +glide_across = "横向滑过" +low_angle = "低角度" +perspective = "透视" +pull_back = "后拉" +rising_sweep = "上升扫过" +slide_by = "滑过" +spotlight = "聚光" +tilt_away = "倾斜远离" +top_down = "俯视" +unfold = "展开" + +[editor.panel.camera3d.sequence] +product_tour = "产品导览" +punch_in = "推近" +showcase = "展示" + +[editor.panel.camera3d.shot] +custom = "自定义运镜" +still = "静止镜头" + +[editor.panel.caption] +title = "字幕 {number}" + +[editor.panel.font] +system_mono = "系统等宽" +system_sans = "系统无衬线" +system_serif = "系统衬线" + +[editor.panel.font_weight] +black = "黑体" +bold = "粗体" +custom = "自定义({weight})" +extra_bold = "特粗" +light = "细体" +medium = "中等" +regular = "常规" +semibold = "半粗体" + +[editor.panel.image] +arrange = "在画布上排列" +arrange_help = "拖动图片即可移动。拖动角可调整大小,或使用下方的旋转控件进行旋转。" +center = "在画布上居中" +lock_aspect = "锁定宽高比" +missing = "图片文件缺失。替换图片即可恢复此片段,并保留其时间和变换。" +replace = "替换图片" +rotation = "旋转" +rounding = "圆角" + +[editor.panel.keyboard] +title = "键盘 {number}" + +[editor.panel.mask] +blur = "模糊" +effect = "效果" +highlight = "突出显示" +outside_darkness = "外部暗度" +pixel_size = "像素大小" +pixelate = "像素化" +sensitive = "敏感内容" +title = "遮罩 {number}" + +[editor.panel.scene] +camera_layout = "摄像头布局" +camera_position = "摄像头位置" +camera_zoom = "摄像头缩放" +screen_position = "屏幕位置" +screen_zoom = "屏幕缩放" +transition = "过渡" + +[editor.panel.scene.description] +camera_only = "仅显示摄像头画面" +default = "同时显示屏幕和摄像头" +floating = "屏幕和摄像头以圆角卡片的形式并排浮在背景上" +hide_camera = "仅显示屏幕录制" +split_screen = "屏幕和摄像头并排显示(竖屏时自动上下排列)" + +[editor.panel.scene.mode] +camera_only = "仅摄像头" +default = "默认" +floating = "浮动" +hide_camera = "隐藏摄像头" +split_screen = "分屏" + +[editor.panel.style] +camera_only_background = "仅摄像头背景" +camera_only_help = "请使用仅摄像头场景。边距会显示摄像头周围的背景。" +camera_only_padding = "仅摄像头边距" +crop_height = "高度" +crop_left = "左" +crop_title = "屏幕裁剪(源像素)" +crop_top = "上" +crop_width = "宽度" +edit_background = "编辑背景和边框" +edit_camera = "编辑摄像头" +edit_cursor = "编辑光标" +help = "覆盖设置仅在此片段期间生效。启用一个分组即可复制其全局设置。" +reset_crop = "重置裁剪" + +[editor.panel.text_animation] +bounce = "弹跳" +fade = "淡入淡出" +letters = "逐字" +none = "无" +pop = "弹出" +slide_down = "向下滑入" +slide_left = "向左滑入" +slide_right = "向右滑入" +slide_up = "向上滑入" +tracking = "字距" +typewriter = "打字机" +wipe = "擦除" +words = "逐词" +zoom = "缩放" + +[editor.panel.text_background] +box_shape = "方框" +highlight = "高亮" +none = "无" +pill = "胶囊" + +[editor.panel.text_layout] +fullscreen = "全屏" +overlay = "叠加" + +[editor.panel.text_preset.group] +all = "全部" +callouts = "标注" +code = "代码" +lower_thirds = "下三分之一字幕" +statements = "要点" +titles = "标题" + +[editor.panel.text_preset.name] +caption = "说明文字" +cinematic = "电影感" +code = "代码" +gradient = "渐变" +headline = "大标题" +highlight = "高亮" +kicker = "引题" +label = "标签" +lower_third = "下三分之一" +name_tag = "姓名牌" +neon = "霓虹" +quote = "引言" +stat = "大数字" +sticker = "贴纸" +title = "标题" +typewriter = "打字机" + +[editor.panel.text_preset.sample] +caption = "使用 Cap 录制" +cinematic = "第一章" +gradient = "精美文字" +headline = "更快发布" +highlight = "重点在这里" +kicker = "新功能" +label = "小贴士" +name_tag = "Richie · 创始人" +neon = "闪耀登场" +quote = "“让一切毫不费力”" +sticker = "砰!" +title = "隆重推出 Cap" +typewriter = "逐字输入中…" + +[editor.panel.text_segment] +animation = "动画" +background = "背景" +color = "颜色" +font = "字体" +font_size = "大小" +fullscreen_help = "显示文字时暂停视频,之后从暂停处继续播放。" +glow = "发光" +gradient = "渐变" +layout = "布局" +letter_spacing = "字间距" +line_height = "行高" +look = "外观" +outline = "描边" +screen_transition = "屏幕过渡" +shadow = "阴影" +style = "样式" +title = "文本 {number}" + +[editor.panel.timing] +end = "结束" +start = "开始" +title = "时间" +to = "至" + +[editor.panel.zoom] +amount = "缩放倍数" +auto = "自动" +auto_help = "自动缩放会跟随光标在屏幕上移动。" +auto_locked = "自动模式需要捕获光标。请在“设置 → 通用”中开启“单独捕获光标(Studio)”。" +how_it_works = "这是如何工作的?" +manual = "手动" +manual_help = "手动缩放会固定在你在下方选择的位置。" +mixed = "不一致" +mode = "缩放模式" +positions_mixed = "这些片段缩放到不同位置。拖动可将它们全部移到同一位置。" +title = "缩放 {number}" + +[editor.sidebar] +desktop_import_failed = "无法导入桌面壁纸" +importing_image = "正在导入图片…" + +[editor.sidebar.animated_gradient] +add_colour = "添加颜色" +colours = "颜色" +delete = "删除" +delete_failed = "无法删除此渐变。请重试。" +fine_tune = "微调" +limit_reached = { other = "最多可保存 {count} 个渐变。请删除一个后再添加。" } +motion = "运动" +name_placeholder = "为此渐变命名" +name_required = "请输入此渐变的名称。" +presets = "预设" +randomize = "随机" +remember_failed = "无法记住你的动态渐变设置。" +remove_colour = "移除颜色" +reset_tooltip = "重置微调设置" +save_failed = "无法保存此渐变。请重试。" +save_tooltip = "保存当前渐变" +saved = "已保存" +still = "静止" + +[editor.sidebar.animated_gradient.control] +contrast = "对比度" +curvature = "弯曲度" +detail = "细节" +direction = "方向" +exposure = "曝光" +flow_scale = "流动尺度" +flow_strength = "流动强度" +grain_amount = "颗粒数量" +grain_size = "颗粒大小" +highlights = "高光" +motion_speed = "运动速度" +relief = "浮雕" +ripple_size = "波纹大小" +shading = "阴影" +vibrance = "自然饱和度" + +[editor.sidebar.animated_gradient.group] +animation = "动画" +colour = "颜色" +flow = "流动" +lighting = "光照" +texture = "纹理" + +[editor.sidebar.background] +title = "背景" + +[editor.sidebar.border] +color = "边框颜色" +opacity = "边框不透明度" +title = "边框" +width = "边框宽度" + +[editor.sidebar.corner] +rounded = "圆角" +squircle = "超椭圆" +title = "圆角样式" + +[editor.sidebar.cursor] +circle = "圆形" +click_ripple = "点击波纹" +color = "颜色" +default = "默认" +description_circle = "将所有光标形状替换为圆形。" +description_default = "保留录制时的光标形状和外观。" +description_macos = "经典 macOS 外观。自定义光标保留录制时的形状。" +description_tahoe = "macOS Tahoe 外观。自定义光标保留录制时的形状。" +description_windows = "Windows 外观。自定义光标保留录制时的形状。" +duration = "时长" +recorded = "录制时的光标" +recorded_tooltip = "使用此光标录制" +ripple_size = "大小" +strength = "强度" + +[editor.sidebar.desktop] +import = "导入桌面背景" +importing = "正在导入..." +prompt = "使用桌面上的壁纸" +reimport = "重新导入" + +[editor.sidebar.gradient] +angle = "角度" +from = "起始" +grain_scale = "颗粒大小" +noise = "噪点" +to = "结束" + +[editor.sidebar.image] +prompt = "点击选择或拖放图片" + +[editor.sidebar.layout] +background_blur = "背景模糊" +custom_position = "自定义屏幕位置(在画布上拖动)" +motion_blur = "运动模糊" +padding = "边距" +rounded_corners = "圆角" +title = "布局" + +[editor.sidebar.notch] +description = "在录制画面上绘制 MacBook 刘海。在带刘海的 Mac 上录制的内容会使用其自身的尺寸;否则请从下方的尺寸开始,再调整到匹配为止。" +height = "刘海高度" +position = "刘海位置" +title = "MacBook 刘海" +width = "刘海宽度" + +[editor.sidebar.shadow] +advanced = "高级阴影设置" +blur = "模糊" +opacity = "不透明度" +shadow_size = "大小" +title = "阴影" + +[editor.sidebar.source] +animated = "动态" +color = "颜色" +desktop = "桌面" +gradient = "渐变" +image = "图片" +none = "无" +wallpaper = "壁纸" + +[editor.sidebar.style_group] +back = "返回样式" +background = "背景和边框" +camera = "摄像头" +cursor = "光标" +editing_note = "仅编辑此片段。全局设置不变。" +title = "样式 {number} · {group}" + +[editor.sidebar.tab] +audio = "音频" +background = "背景" +camera = "摄像头" +captions = "字幕" +cursor = "光标" +keyboard = "键盘" + +[editor.sidebar.wallpaper_theme] +blue = "蓝色" +cities = "城市" +dark = "深色" +orange = "橙色" +purple = "紫色" diff --git a/apps/desktop-gpui/src/editor_audio.rs b/apps/desktop-gpui/src/editor_audio.rs index 3279b2efe9a..fad14dccf3f 100644 --- a/apps/desktop-gpui/src/editor_audio.rs +++ b/apps/desktop-gpui/src/editor_audio.rs @@ -7,7 +7,7 @@ use gpui::{ SharedString, StatefulInteractiveElement, Styled, div, prelude::FluentBuilder, px, svg, }; -use crate::{editor_window::EditorWindow, theme::Theme, ui}; +use crate::{editor_window::EditorWindow, i18n::tr, theme::Theme, ui}; pub const AUDIO_LIBRARY: &[(&str, &str)] = &[ ("lofi-beats-mirostar", "Lofi Beats"), @@ -189,7 +189,7 @@ impl EditorWindow { ui::EditorButton::plain(&theme, "audio-library-close") .left_icon("icons/check.svg") .icon_size(px(16.)) - .label("Done") + .label(tr::editor::audio::library::done()) .on_click(cx.listener(|this, _, _, cx| { this.audio_picker = None; cx.notify(); @@ -200,9 +200,9 @@ impl EditorWindow { .text_size(px(14.)) .text_color(Hsla::from(theme.gray_10)) .child(if is_replace { - "Change audio" + tr::editor::audio::library::change_title() } else { - "Add audio" + tr::editor::audio::library::add_title() }), ), ) @@ -211,15 +211,15 @@ impl EditorWindow { .text_size(px(12.)) .text_color(Hsla::from(theme.gray_10)) .child(if is_replace { - "Pick a different track for this segment" + tr::editor::audio::library::change_hint() } else { - "Add audio, music or other sounds to your video" + tr::editor::audio::library::add_hint() }), ) .child( ui::EditorButton::plain(&theme, "audio-library-import") .left_icon("icons/import.svg") - .label("Import file") + .label(tr::editor::audio::library::import()) .disabled(importing) .on_click(cx.listener(|this, _, window, cx| { this.import_audio_from_picker(window, cx); diff --git a/apps/desktop-gpui/src/editor_color.rs b/apps/desktop-gpui/src/editor_color.rs index 03311d6cd94..62fb4bcc69c 100644 --- a/apps/desktop-gpui/src/editor_color.rs +++ b/apps/desktop-gpui/src/editor_color.rs @@ -45,7 +45,7 @@ use gpui::{ prelude::FluentBuilder, px, }; -use crate::{editor_window::EditorWindow, ui}; +use crate::{editor_window::EditorWindow, i18n::tr, ui}; // --------------------------------------------------------------------------- // The catalogue @@ -103,8 +103,8 @@ pub const IDENTITY: GradeValues = GradeValues { pub struct ColorPreset { pub id: &'static str, - pub label: &'static str, - pub description: &'static str, + pub label: fn() -> &'static str, + pub description: fn() -> &'static str, pub values: GradeValues, pub filter: &'static [Filter], pub overlay: Option, @@ -118,16 +118,16 @@ const fn rgba(r: u8, g: u8, b: u8, a: f32) -> [f32; 4] { pub static COLOR_CORRECTION_PRESETS: &[ColorPreset] = &[ ColorPreset { id: PRESET_NONE, - label: "None", - description: "Original colors, no grade applied", + label: tr::editor::color::preset::none::name, + description: tr::editor::color::preset::none::description, values: IDENTITY, filter: &[], overlay: None, }, ColorPreset { id: "cinematic", - label: "Cinematic", - description: "Teal shadows and orange highlights with light grain", + label: tr::editor::color::preset::cinematic::name, + description: tr::editor::color::preset::cinematic::description, values: GradeValues { contrast: 0.12, saturation: 0.06, @@ -147,8 +147,8 @@ pub static COLOR_CORRECTION_PRESETS: &[ColorPreset] = &[ }, ColorPreset { id: "noir", - label: "Noir", - description: "High-contrast black and white with heavy grain", + label: tr::editor::color::preset::noir::name, + description: tr::editor::color::preset::noir::description, values: GradeValues { exposure: 0.04, contrast: 0.3, @@ -168,8 +168,8 @@ pub static COLOR_CORRECTION_PRESETS: &[ColorPreset] = &[ }, ColorPreset { id: "vintage", - label: "Vintage", - description: "Faded warm film look with soft contrast", + label: tr::editor::color::preset::vintage::name, + description: tr::editor::color::preset::vintage::description, values: GradeValues { contrast: -0.06, saturation: -0.18, @@ -191,8 +191,8 @@ pub static COLOR_CORRECTION_PRESETS: &[ColorPreset] = &[ }, ColorPreset { id: "frost", - label: "Frost", - description: "Cool, crisp tones with muted color", + label: tr::editor::color::preset::frost::name, + description: tr::editor::color::preset::frost::description, values: GradeValues { contrast: 0.08, saturation: -0.08, @@ -215,8 +215,8 @@ pub static COLOR_CORRECTION_PRESETS: &[ColorPreset] = &[ }, ColorPreset { id: "golden", - label: "Golden", - description: "Warm golden-hour glow", + label: tr::editor::color::preset::golden::name, + description: tr::editor::color::preset::golden::description, values: GradeValues { exposure: 0.06, contrast: 0.06, @@ -241,8 +241,8 @@ pub static COLOR_CORRECTION_PRESETS: &[ColorPreset] = &[ }, ColorPreset { id: "midnight", - label: "Midnight", - description: "Dark, moody teal with subdued color", + label: tr::editor::color::preset::midnight::name, + description: tr::editor::color::preset::midnight::description, values: GradeValues { exposure: -0.08, contrast: 0.16, @@ -267,8 +267,8 @@ pub static COLOR_CORRECTION_PRESETS: &[ColorPreset] = &[ }, ColorPreset { id: "vivid", - label: "Vivid", - description: "Punchy saturation and contrast boost", + label: tr::editor::color::preset::vivid::name, + description: tr::editor::color::preset::vivid::description, values: GradeValues { exposure: 0.02, contrast: 0.14, @@ -281,8 +281,8 @@ pub static COLOR_CORRECTION_PRESETS: &[ColorPreset] = &[ }, ColorPreset { id: "dreamy", - label: "Dreamy", - description: "Soft, airy pastels with lifted blacks", + label: tr::editor::color::preset::dreamy::name, + description: tr::editor::color::preset::dreamy::description, values: GradeValues { exposure: 0.08, contrast: -0.14, @@ -360,16 +360,16 @@ impl GradeSlider { pub fn label(self) -> &'static str { match self { - Self::Grain => "Grain", - Self::Intensity => "Strength", - Self::Exposure => "Exposure", - Self::Contrast => "Contrast", - Self::Saturation => "Saturation", - Self::Temperature => "Temperature", - Self::Tint => "Tint", - Self::Fade => "Fade", - Self::SplitTone => "Split Tone", - Self::Vignette => "Vignette", + Self::Grain => tr::editor::color::slider::grain(), + Self::Intensity => tr::editor::color::slider::strength(), + Self::Exposure => tr::editor::color::slider::exposure(), + Self::Contrast => tr::editor::color::slider::contrast(), + Self::Saturation => tr::editor::color::slider::saturation(), + Self::Temperature => tr::editor::color::slider::temperature(), + Self::Tint => tr::editor::color::slider::tint(), + Self::Fade => tr::editor::color::slider::fade(), + Self::SplitTone => tr::editor::color::slider::split_tone(), + Self::Vignette => tr::editor::color::slider::vignette(), } } @@ -611,10 +611,10 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.gray_12)) - .child(preset.label), + .child((preset.label)()), ) .tooltip({ - let description = SharedString::from(preset.description); + let description = SharedString::from((preset.description)()); move |_window, cx| { ui::Tooltip::new(&theme, description.clone()).view(cx) } @@ -629,9 +629,9 @@ impl EditorWindow { .flex() .flex_col() .gap(px(14.)) - .child(ui::Field::section(&theme, "Color Correction").child(tiles)) + .child(ui::Field::section(&theme, tr::editor::color::title()).child(tiles)) .child(self.slider_field( - "Grain", + GradeSlider::Grain.label(), crate::editor_sidebar::SliderKey::Grade(target, GradeSlider::Grain), "%", cx, @@ -641,7 +641,7 @@ impl EditorWindow { if target == GradeTarget::Screen { let grade_cursor = self.project.color_correction.grade_cursor; section = section.child( - ui::Field::inline(&theme, "Apply to cursor").value( + ui::Field::inline(&theme, tr::editor::color::apply_to_cursor()).value( ui::Toggle::plain(&theme, "grade-cursor", grade_cursor) .on_click(cx.listener(move |this, _, window, cx| { this.edit_project("grade-cursor", window, cx, |project| { @@ -664,7 +664,7 @@ impl EditorWindow { .child(crate::editor_sidebar::disclosure_row( &theme, SharedString::from(format!("grade-adjust-{}", target.key())), - "Fine-tune colors", + tr::editor::color::fine_tune(), open.is_open(), cx.listener(move |this, _, window, cx| { let next = !this.sidebar.grade_open(target).is_open(); diff --git a/apps/desktop-gpui/src/editor_crop.rs b/apps/desktop-gpui/src/editor_crop.rs index ec1f860dad1..ee6600ea9b5 100644 --- a/apps/desktop-gpui/src/editor_crop.rs +++ b/apps/desktop-gpui/src/editor_crop.rs @@ -45,6 +45,7 @@ use std::collections::BTreeSet; use crate::{ editor_window::{EDITOR_PREVIEW_FPS, EditorWindow, letterbox}, + i18n::tr, ui, }; use cap_project::{Crop, XY}; @@ -1472,17 +1473,32 @@ impl CropField { /// groups are simply adjacent -- documented in the README. The check marks and /// the keyboard contract are the real thing. pub fn crop_menu_items(aspect: Option, snap: bool) -> Vec { - let mut items = vec![ui::MenuItem::new("Free", aspect.is_none())]; + let mut items = vec![ui::MenuItem::new( + tr::editor::crop::free(), + aspect.is_none(), + )]; items.extend(COMMON_RATIOS.map(|ratio| { ui::MenuItem::new( SharedString::from(format!("{}:{}", ratio.0, ratio.1)), aspect == Some(ratio), ) })); - items.push(ui::MenuItem::new("Snap to ratios", snap)); + items.push(ui::MenuItem::new(tr::editor::crop::snap_to_ratios(), snap)); items } +/// The hint line under the stage, split around the Shift key badge: the +/// translated sentence places `{key}` wherever its grammar needs it. +pub(crate) fn crop_hint_parts() -> (String, String) { + const KEY: char = '\u{0}'; + let hint = tr::editor::crop::hint(KEY); + let (before, after) = hint.split_once(KEY).unwrap_or((hint.as_str(), "")); + ( + before.trim_end().to_string(), + after.trim_start().to_string(), + ) +} + /// What row `index` of [`crop_menu_items`] does. pub enum CropMenuChoice { Aspect(Option), @@ -1639,7 +1655,7 @@ impl EditorWindow { if let Some(target) = &state.style_target && !target.matches(&self.project) { - state.error = Some("This Style changed while cropping. Cancel and reopen Crop.".into()); + state.error = Some(tr::editor::crop::style_changed().into()); self.crop = Some(state); self.publish_project(); cx.notify(); @@ -2437,14 +2453,14 @@ impl EditorWindow { .text_size(px(14.)) .font_weight(FontWeight::SEMIBOLD) .text_color(Hsla::from(theme.editor.text_1)) - .child("Crop"), + .child(tr::editor::crop::title()), ) .child( ui::EditorButton::plain(&theme, "crop-close") .left_icon("icons/x.svg") .icon_size(px(12.)) .width(px(28.)) - .tooltip(&theme, "Close") + .tooltip(&theme, tr::common::close()) .on_click( cx.listener(|this, _, window, cx| this.cancel_crop("close", window, cx)), ), @@ -2549,40 +2565,46 @@ impl EditorWindow { .p(px(INSPECTOR_PADDING)) .rounded(px(12.)) .bg(Hsla::from(editor.card_2)) - .child(label("Preview")) + .child(label(tr::editor::crop::preview())) .child(self.render_crop_preview(px(preview_width), px(preview_height))) .child(hairline()) - .child(label("Aspect ratio")) + .child(label(tr::editor::crop::aspect_ratio())) .child(chips) - .child(row().child(row_label("Snap to ratios")).child( - ui::Toggle::plain(&theme, "crop-snap", snap).on_click(cx.listener( - |this, _, _window, cx| { - this.crop_snap_to_ratio = !this.crop_snap_to_ratio; - cx.notify(); - }, - )), - )) + .child( + row() + .child(row_label(tr::editor::crop::snap_to_ratios())) + .child( + ui::Toggle::plain(&theme, "crop-snap", snap).on_click(cx.listener( + |this, _, _window, cx| { + this.crop_snap_to_ratio = !this.crop_snap_to_ratio; + cx.notify(); + }, + )), + ), + ) .child(hairline()) .child( - row().child(row_label("Size")).child( - div() - .flex() - .flex_row() - .items_center() - .gap(px(6.)) - .child(self.render_crop_field(CropField::Width)) - .child( - div() - .w(px(8.)) - .text_size(px(12.)) - .text_color(Hsla::from(editor.text_3)) - .child("×"), - ) - .child(self.render_crop_field(CropField::Height)), - ), + row() + .child(row_label(tr::editor::crop::dimensions())) + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(6.)) + .child(self.render_crop_field(CropField::Width)) + .child( + div() + .w(px(8.)) + .text_size(px(12.)) + .text_color(Hsla::from(editor.text_3)) + .child("×"), + ) + .child(self.render_crop_field(CropField::Height)), + ), ) .child( - row().child(row_label("Position")).child( + row().child(row_label(tr::editor::crop::position())).child( div() .flex() .flex_row() @@ -2601,7 +2623,7 @@ impl EditorWindow { .gap(px(6.)) .child(self.render_crop_action( "crop-center", - "Center", + tr::editor::crop::center(), centered, cx.listener(|this, _, window, cx| { if let Some(state) = this.crop.as_mut() { @@ -2624,7 +2646,7 @@ impl EditorWindow { )) .child(self.render_crop_action( "crop-full", - "Full", + tr::editor::crop::full(), full, cx.listener(|this, _, window, cx| { if let Some(state) = this.crop.as_mut() { @@ -2636,7 +2658,7 @@ impl EditorWindow { )) .child(self.render_crop_action( "crop-reset", - "Reset", + tr::common::reset(), untouched, cx.listener(|this, _, window, cx| { if let Some(state) = this.crop.as_mut() { @@ -2662,7 +2684,7 @@ impl EditorWindow { let editor = self.theme.editor; let label = match ratio { Some((a, b)) => SharedString::from(format!("{a}:{b}")), - None => SharedString::from("Free"), + None => SharedString::from(tr::editor::crop::free()), }; div() .id(("crop-ratio", index)) @@ -2909,7 +2931,7 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_2)) - .child("Loading frame…") + .child(tr::editor::crop::loading_frame()) })) .into_any_element() } @@ -2954,7 +2976,7 @@ impl EditorWindow { .text_size(px(11.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(editor.text_3)) - .child("Rendering preview…") + .child(tr::editor::crop::rendering_preview()) .into_any_element(), ), }) @@ -2964,6 +2986,7 @@ impl EditorWindow { /// The one-line hint under the stage. fn render_crop_hint(&self) -> AnyElement { let editor = self.theme.editor; + let (before, after) = crop_hint_parts(); div() .flex() .flex_row() @@ -2975,7 +2998,7 @@ impl EditorWindow { .pb(px(DIALOG_PADDING_X)) .text_size(px(12.)) .text_color(Hsla::from(editor.text_3)) - .child("Drag to move · Arrow keys nudge · Hold") + .when(!before.is_empty(), |hint| hint.child(before)) .child( div() .px(px(4.)) @@ -2984,7 +3007,7 @@ impl EditorWindow { .text_color(Hsla::from(editor.text_2)) .child("⇧"), ) - .child("to skip snapping") + .when(!after.is_empty(), |hint| hint.child(after)) .into_any_element() } @@ -2997,12 +3020,9 @@ impl EditorWindow { (Some(error), _) => Some((SharedString::from(error.clone()), Hsla::from(theme.red_11))), (None, Some(target)) => Some(( SharedString::from(if target.opting_in { - format!( - "Editing Style {} only · Saving enables its background override", - target.index + 1 - ) + tr::editor::crop::editing_style_override(target.index + 1) } else { - format!("Editing Style {} only", target.index + 1) + tr::editor::crop::editing_style(target.index + 1) }), Hsla::from(editor.text_2), )), @@ -3038,7 +3058,7 @@ impl EditorWindow { .gap(px(8.)) .child( ui::EditorButton::plain(&theme, "crop-cancel") - .label("Cancel") + .label(tr::common::cancel()) .on_click(cx.listener(|this, _, window, cx| { this.cancel_crop("cancel", window, cx) })), @@ -3100,7 +3120,7 @@ impl EditorWindow { .h(px(1.)) .bg(gpui::hsla(0., 0., 1., 0.22)), ) - .child("Save") + .child(tr::common::save()) .into_any_element() } diff --git a/apps/desktop-gpui/src/editor_panels.rs b/apps/desktop-gpui/src/editor_panels.rs index 1c773856704..e64dc294d2d 100644 --- a/apps/desktop-gpui/src/editor_panels.rs +++ b/apps/desktop-gpui/src/editor_panels.rs @@ -42,6 +42,7 @@ use crate::{ editor_tabs::{OffsetKind, SidebarMenu}, editor_timeline::TrackKind, editor_window::EditorWindow, + i18n::tr, ui, }; @@ -58,49 +59,88 @@ pub const MIN_VOLUME_DB: f32 = -30.; pub const MAX_VOLUME_DB: f32 = 12.; /// `TEXT_SEGMENT_WEIGHT_OPTIONS` (`text-style.tsx:42-50`). -pub const TEXT_SEGMENT_WEIGHTS: [(f32, &str); 7] = [ - (300., "Light"), - (400., "Regular"), - (500., "Medium"), - (600., "Semibold"), - (700., "Bold"), - (800., "Extra Bold"), - (900., "Black"), +pub const TEXT_SEGMENT_WEIGHTS: [(f32, fn() -> &'static str); 7] = [ + (300., tr::editor::panel::font_weight::light), + (400., tr::editor::panel::font_weight::regular), + (500., tr::editor::panel::font_weight::medium), + (600., tr::editor::panel::font_weight::semibold), + (700., tr::editor::panel::font_weight::bold), + (800., tr::editor::panel::font_weight::extra_bold), + (900., tr::editor::panel::font_weight::black), ]; /// `TEXT_ANIMATION_OPTIONS` (`text-style.tsx:52-62`), in the renderer's own /// variant order. -pub const TEXT_ANIMATIONS: [(TextAnimation, &str); 14] = [ - (TextAnimation::None, "None"), - (TextAnimation::Fade, "Fade"), - (TextAnimation::SlideUp, "Slide up"), - (TextAnimation::SlideDown, "Slide down"), - (TextAnimation::SlideLeft, "Slide left"), - (TextAnimation::SlideRight, "Slide right"), - (TextAnimation::Pop, "Pop"), - (TextAnimation::Zoom, "Zoom"), - (TextAnimation::Bounce, "Bounce"), - (TextAnimation::Wipe, "Wipe"), - (TextAnimation::Words, "Words"), - (TextAnimation::Letters, "Letters"), - (TextAnimation::Tracking, "Tracking"), - (TextAnimation::Typewriter, "Typewriter"), +pub const TEXT_ANIMATIONS: [(TextAnimation, fn() -> &'static str); 14] = [ + (TextAnimation::None, tr::editor::panel::text_animation::none), + (TextAnimation::Fade, tr::editor::panel::text_animation::fade), + ( + TextAnimation::SlideUp, + tr::editor::panel::text_animation::slide_up, + ), + ( + TextAnimation::SlideDown, + tr::editor::panel::text_animation::slide_down, + ), + ( + TextAnimation::SlideLeft, + tr::editor::panel::text_animation::slide_left, + ), + ( + TextAnimation::SlideRight, + tr::editor::panel::text_animation::slide_right, + ), + (TextAnimation::Pop, tr::editor::panel::text_animation::pop), + (TextAnimation::Zoom, tr::editor::panel::text_animation::zoom), + ( + TextAnimation::Bounce, + tr::editor::panel::text_animation::bounce, + ), + (TextAnimation::Wipe, tr::editor::panel::text_animation::wipe), + ( + TextAnimation::Words, + tr::editor::panel::text_animation::words, + ), + ( + TextAnimation::Letters, + tr::editor::panel::text_animation::letters, + ), + ( + TextAnimation::Tracking, + tr::editor::panel::text_animation::tracking, + ), + ( + TextAnimation::Typewriter, + tr::editor::panel::text_animation::typewriter, + ), ]; /// `TEXT_BACKGROUND_STYLE_OPTIONS` (`text-style.tsx`). The panel's segmented /// control shows a fourth "None" ahead of these, which is `backgroundColor` /// cleared rather than a style of its own. -pub const TEXT_BACKGROUND_STYLES: [(TextBackgroundStyle, &str); 3] = [ - (TextBackgroundStyle::Box, "Box"), - (TextBackgroundStyle::Pill, "Pill"), - (TextBackgroundStyle::Highlight, "Highlight"), +pub const TEXT_BACKGROUND_STYLES: [(TextBackgroundStyle, fn() -> &'static str); 3] = [ + ( + TextBackgroundStyle::Box, + tr::editor::panel::text_background::box_shape, + ), + ( + TextBackgroundStyle::Pill, + tr::editor::panel::text_background::pill, + ), + ( + TextBackgroundStyle::Highlight, + tr::editor::panel::text_background::highlight, + ), ]; /// `TEXT_LAYOUT_OPTIONS` (`:3583-3590`): the renderer also has `splitLeft` / /// `splitRight`, and the source deliberately exposes only these two. -pub const TEXT_LAYOUTS: [(TextLayout, &str); 2] = [ - (TextLayout::Overlay, "Overlay"), - (TextLayout::Fullscreen, "Fullscreen"), +pub const TEXT_LAYOUTS: [(TextLayout, fn() -> &'static str); 2] = [ + (TextLayout::Overlay, tr::editor::panel::text_layout::overlay), + ( + TextLayout::Fullscreen, + tr::editor::panel::text_layout::fullscreen, + ), ]; /// `TEXT_ALIGN_OPTIONS` (`:3596-3600`). @@ -146,9 +186,10 @@ pub struct TextPresetStyle { /// placement, and it is the one field that moves the box. pub struct TextPreset { pub id: &'static str, + /// Which [`TEXT_PRESET_GROUPS`] chip shows it, by that chip's id. pub group: &'static str, - pub name: &'static str, - pub sample: &'static str, + pub name: fn() -> &'static str, + pub sample: fn() -> &'static str, pub style: TextPresetStyle, pub center: Option>, } @@ -157,9 +198,21 @@ const SANS_STACK: &[&str] = &["Helvetica Neue", "Segoe UI", "Inter", "sans-serif const SERIF_STACK: &[&str] = &["Georgia", "Times New Roman", "serif"]; const MONO_STACK: &[&str] = &["Menlo", "Consolas", "monospace"]; -/// The Style section's chip row, in order. The row draws "All" ahead of these. -pub const TEXT_PRESET_GROUPS: [&str; 5] = - ["Titles", "Lower thirds", "Callouts", "Statements", "Code"]; +/// The Style section's chip row, in order, as `(id, label)`. The row draws +/// "All" ahead of these. +pub const TEXT_PRESET_GROUPS: [(&str, fn() -> &'static str); 5] = [ + ("Titles", tr::editor::panel::text_preset::group::titles), + ( + "Lower thirds", + tr::editor::panel::text_preset::group::lower_thirds, + ), + ("Callouts", tr::editor::panel::text_preset::group::callouts), + ( + "Statements", + tr::editor::panel::text_preset::group::statements, + ), + ("Code", tr::editor::panel::text_preset::group::code), +]; /// The style every preset that does not draw an outline still carries. const PRESET_STROKE: &str = "#000000"; @@ -169,8 +222,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "title", group: "Titles", - name: "Title", - sample: "Introducing Cap", + name: tr::editor::panel::text_preset::name::title, + sample: tr::editor::panel::text_preset::sample::title, center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -198,8 +251,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "headline", group: "Titles", - name: "Headline", - sample: "Ship faster", + name: tr::editor::panel::text_preset::name::headline, + sample: tr::editor::panel::text_preset::sample::headline, center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -227,8 +280,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "cinematic", group: "Titles", - name: "Cinematic", - sample: "Chapter one", + name: tr::editor::panel::text_preset::name::cinematic, + sample: tr::editor::panel::text_preset::sample::cinematic, center: None, style: TextPresetStyle { font_stack: SERIF_STACK, @@ -256,8 +309,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "gradient", group: "Titles", - name: "Gradient", - sample: "Beautiful text", + name: tr::editor::panel::text_preset::name::gradient, + sample: tr::editor::panel::text_preset::sample::gradient, center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -285,8 +338,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "lower-third", group: "Lower thirds", - name: "Lower third", - sample: "Richie McIlroy", + name: tr::editor::panel::text_preset::name::lower_third, + sample: || "Richie McIlroy", center: Some(XY { x: 0.22, y: 0.85 }), style: TextPresetStyle { font_stack: SANS_STACK, @@ -314,8 +367,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "name-tag", group: "Lower thirds", - name: "Name tag", - sample: "Richie \u{b7} Founder", + name: tr::editor::panel::text_preset::name::name_tag, + sample: tr::editor::panel::text_preset::sample::name_tag, center: Some(XY { x: 0.2, y: 0.86 }), style: TextPresetStyle { font_stack: SANS_STACK, @@ -343,8 +396,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "caption", group: "Lower thirds", - name: "Caption", - sample: "Recorded with Cap", + name: tr::editor::panel::text_preset::name::caption, + sample: tr::editor::panel::text_preset::sample::caption, center: Some(XY { x: 0.5, y: 0.88 }), style: TextPresetStyle { font_stack: SANS_STACK, @@ -372,8 +425,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "kicker", group: "Callouts", - name: "Kicker", - sample: "New feature", + name: tr::editor::panel::text_preset::name::kicker, + sample: tr::editor::panel::text_preset::sample::kicker, center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -401,8 +454,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "label", group: "Callouts", - name: "Label", - sample: "Pro tip", + name: tr::editor::panel::text_preset::name::label, + sample: tr::editor::panel::text_preset::sample::label, center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -430,8 +483,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "highlight", group: "Callouts", - name: "Highlight", - sample: "the important part", + name: tr::editor::panel::text_preset::name::highlight, + sample: tr::editor::panel::text_preset::sample::highlight, center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -459,8 +512,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "sticker", group: "Callouts", - name: "Sticker", - sample: "Boom!", + name: tr::editor::panel::text_preset::name::sticker, + sample: tr::editor::panel::text_preset::sample::sticker, center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -488,8 +541,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "neon", group: "Callouts", - name: "Neon", - sample: "Glow up", + name: tr::editor::panel::text_preset::name::neon, + sample: tr::editor::panel::text_preset::sample::neon, center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -517,8 +570,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "stat", group: "Statements", - name: "Big stat", - sample: "128%", + name: tr::editor::panel::text_preset::name::stat, + sample: || "128%", center: None, style: TextPresetStyle { font_stack: SANS_STACK, @@ -546,8 +599,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "quote", group: "Statements", - name: "Quote", - sample: "\u{201c}Make it feel effortless\u{201d}", + name: tr::editor::panel::text_preset::name::quote, + sample: tr::editor::panel::text_preset::sample::quote, center: None, style: TextPresetStyle { font_stack: SERIF_STACK, @@ -575,8 +628,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "code", group: "Code", - name: "Code", - sample: "$ cap record", + name: tr::editor::panel::text_preset::name::code, + sample: || "$ cap record", center: None, style: TextPresetStyle { font_stack: MONO_STACK, @@ -604,8 +657,8 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ TextPreset { id: "typewriter", group: "Code", - name: "Typewriter", - sample: "typing it out\u{2026}", + name: tr::editor::panel::text_preset::name::typewriter, + sample: tr::editor::panel::text_preset::sample::typewriter, center: None, style: TextPresetStyle { font_stack: MONO_STACK, @@ -634,10 +687,10 @@ pub static TEXT_PRESETS: &[TextPreset] = &[ /// `GENERIC_FONT_OPTIONS` (`utils/fonts.ts:15-19`) -- the three families the /// renderer resolves itself, which head the font picker's list. -pub const GENERIC_FONTS: [(&str, &str); 3] = [ - ("sans-serif", "System Sans"), - ("serif", "System Serif"), - ("monospace", "System Mono"), +pub const GENERIC_FONTS: [(&str, fn() -> &'static str); 3] = [ + ("sans-serif", tr::editor::panel::font::system_sans), + ("serif", tr::editor::panel::font::system_serif), + ("monospace", tr::editor::panel::font::system_mono), ]; /// `fontFamilyLabel` (`utils/fonts.ts:27-32`). @@ -645,7 +698,7 @@ pub fn font_family_label(value: &str) -> String { GENERIC_FONTS .iter() .find(|(generic, _)| *generic == value.trim().to_ascii_lowercase()) - .map_or_else(|| value.to_string(), |(_, label)| (*label).to_string()) + .map_or_else(|| value.to_string(), |(_, label)| label().to_string()) } /// `pickFontFamily` (`text-presets.ts:184-201`): the first family of the stack @@ -809,7 +862,7 @@ pub fn apply_text_preset(segment: &mut TextSegment, preset: &TextPreset, install .animation_in_duration .max(style.animation_out_duration); if matches!(segment.content.trim(), "" | "Text") { - segment.content = preset.sample.to_string(); + segment.content = (preset.sample)().to_string(); } if let Some(center) = preset.center { segment.center = center; @@ -870,7 +923,7 @@ pub fn installed_fonts() -> &'static [String] { pub fn font_picker_options() -> Vec<(String, String)> { GENERIC_FONTS .iter() - .map(|(value, label)| ((*value).to_string(), (*label).to_string())) + .map(|(value, label)| ((*value).to_string(), label().to_string())) .chain( installed_fonts() .iter() @@ -905,73 +958,137 @@ pub fn warm_installed_fonts() { } /// The five scene modes and their descriptions (`:6295-6315`, `:6373-6404`). -pub const SCENE_MODES: [(SceneMode, &str, &str, &str); 5] = [ +pub const SCENE_MODES: [(SceneMode, fn() -> &'static str, &str, fn() -> &'static str); 5] = [ ( SceneMode::Default, - "Default", + tr::editor::panel::scene::mode::default, "icons/monitor-outline.svg", - "Shows both screen and camera", + tr::editor::panel::scene::description::default, ), ( SceneMode::CameraOnly, - "Camera Only", + tr::editor::panel::scene::mode::camera_only, "icons/video.svg", - "Shows only the camera feed", + tr::editor::panel::scene::description::camera_only, ), ( SceneMode::HideCamera, - "Hide Camera", + tr::editor::panel::scene::mode::hide_camera, "icons/eye-off.svg", - "Shows only the screen recording", + tr::editor::panel::scene::description::hide_camera, ), ( SceneMode::SplitScreen, - "Split Screen", + tr::editor::panel::scene::mode::split_screen, "icons/columns-2.svg", - "Screen and camera side by side (auto-stacks in portrait)", + tr::editor::panel::scene::description::split_screen, ), ( SceneMode::Floating, - "Floating", + tr::editor::panel::scene::mode::floating, "icons/panel-right.svg", - "Screen and camera float side by side as rounded cards over the background", + tr::editor::panel::scene::description::floating, ), ]; /// `CAMERA3D_BLUR_SLIDERS` (`:4577-4602`): each mode exposes only the /// parameters it reads, in display order. -pub fn camera3d_blur_sliders(mode: Camera3DBlurMode) -> &'static [(Camera3DBlurKey, &'static str)] { +pub fn camera3d_blur_sliders( + mode: Camera3DBlurMode, +) -> &'static [(Camera3DBlurKey, fn() -> &'static str)] { match mode { Camera3DBlurMode::None => &[], Camera3DBlurMode::Radial => &[ - (Camera3DBlurKey::Strength, "Strength"), - (Camera3DBlurKey::FocusX, "Focus X"), - (Camera3DBlurKey::FocusY, "Focus Y"), - (Camera3DBlurKey::FocusSize, "Focus size"), - (Camera3DBlurKey::Falloff, "Falloff"), + ( + Camera3DBlurKey::Strength, + tr::editor::panel::camera3d::blur::strength, + ), + ( + Camera3DBlurKey::FocusX, + tr::editor::panel::camera3d::blur::focus_x, + ), + ( + Camera3DBlurKey::FocusY, + tr::editor::panel::camera3d::blur::focus_y, + ), + ( + Camera3DBlurKey::FocusSize, + tr::editor::panel::camera3d::blur::focus_size, + ), + ( + Camera3DBlurKey::Falloff, + tr::editor::panel::camera3d::blur::falloff, + ), ], Camera3DBlurMode::Directional => &[ - (Camera3DBlurKey::Strength, "Strength"), - (Camera3DBlurKey::Angle, "Angle"), - (Camera3DBlurKey::DirPosition, "Position"), - (Camera3DBlurKey::Falloff, "Falloff"), + ( + Camera3DBlurKey::Strength, + tr::editor::panel::camera3d::blur::strength, + ), + ( + Camera3DBlurKey::Angle, + tr::editor::panel::camera3d::blur::angle, + ), + ( + Camera3DBlurKey::DirPosition, + tr::editor::panel::camera3d::blur::position, + ), + ( + Camera3DBlurKey::Falloff, + tr::editor::panel::camera3d::blur::falloff, + ), ], Camera3DBlurMode::TiltShift => &[ - (Camera3DBlurKey::Strength, "Strength"), - (Camera3DBlurKey::FocusY, "Scan"), - (Camera3DBlurKey::FocusSize, "Focus size"), - (Camera3DBlurKey::Angle, "Angle"), - (Camera3DBlurKey::Falloff, "Falloff"), + ( + Camera3DBlurKey::Strength, + tr::editor::panel::camera3d::blur::strength, + ), + ( + Camera3DBlurKey::FocusY, + tr::editor::panel::camera3d::blur::scan, + ), + ( + Camera3DBlurKey::FocusSize, + tr::editor::panel::camera3d::blur::focus_size, + ), + ( + Camera3DBlurKey::Angle, + tr::editor::panel::camera3d::blur::angle, + ), + ( + Camera3DBlurKey::Falloff, + tr::editor::panel::camera3d::blur::falloff, + ), ], } } /// `MOTION_EASINGS` (`three-d.ts:957-963`). -pub const MOTION_EASINGS: [(&str, &str, [f64; 2], [f64; 2]); 4] = [ - ("linear", "Linear", [0., 0.], [1., 1.]), - ("smooth", "Smooth", [0.65, 0.], [0.35, 1.]), - ("easeIn", "Ease in", [0.32, 0.], [1., 1.]), - ("easeOut", "Ease out", [0., 0.], [0.68, 1.]), +pub const MOTION_EASINGS: [(&str, fn() -> &'static str, [f64; 2], [f64; 2]); 4] = [ + ( + "linear", + tr::editor::panel::camera3d::easing::linear, + [0., 0.], + [1., 1.], + ), + ( + "smooth", + tr::editor::panel::camera3d::easing::smooth, + [0.65, 0.], + [0.35, 1.], + ), + ( + "easeIn", + tr::editor::panel::camera3d::easing::ease_in, + [0.32, 0.], + [1., 1.], + ), + ( + "easeOut", + tr::editor::panel::camera3d::easing::ease_out, + [0., 0.], + [0.68, 1.], + ), ]; /// `CAMERA3D_RESET_POSE` (`three-d.ts:927-937`): a canonical long lens. @@ -1051,7 +1168,7 @@ fn overhead_blur() -> Camera3DBlur { /// drift it plays out over. pub struct AnglePreset { pub id: &'static str, - pub name: &'static str, + pub name: fn() -> &'static str, pub values: Camera3DProperties, /// The end pose. The source spells it as a partial `drift` merged onto the /// opening pose, which is the same thing written out. @@ -1063,7 +1180,7 @@ pub struct AnglePreset { pub static ANGLE_PRESETS: &[AnglePreset] = &[ AnglePreset { id: "spotlight", - name: "Spotlight", + name: tr::editor::panel::camera3d::look::spotlight, values: Camera3DProperties { zoom: 1.35, pan_x: 0.39, @@ -1081,7 +1198,7 @@ pub static ANGLE_PRESETS: &[AnglePreset] = &[ }, AnglePreset { id: "perspective", - name: "Perspective", + name: tr::editor::panel::camera3d::look::perspective, values: Camera3DProperties { tilt_x: -28., tilt_y: 26., @@ -1105,7 +1222,7 @@ pub static ANGLE_PRESETS: &[AnglePreset] = &[ }, AnglePreset { id: "center", - name: "Center", + name: tr::editor::panel::camera3d::look::center, values: Camera3DProperties { zoom: 2., ..DEFAULT_POSE @@ -1119,7 +1236,7 @@ pub static ANGLE_PRESETS: &[AnglePreset] = &[ }, AnglePreset { id: "low-angle", - name: "Low angle", + name: tr::editor::panel::camera3d::look::low_angle, values: Camera3DProperties { tilt_x: -50., tilt_y: 1., @@ -1138,7 +1255,7 @@ pub static ANGLE_PRESETS: &[AnglePreset] = &[ }, AnglePreset { id: "close-up", - name: "Close up", + name: tr::editor::panel::camera3d::look::close_up, values: Camera3DProperties { tilt_x: 26., tilt_y: -22., @@ -1167,7 +1284,7 @@ pub static ANGLE_PRESETS: &[AnglePreset] = &[ /// template: clicking a card is a complete look, not a pose change. pub struct MotionTemplate { pub id: &'static str, - pub name: &'static str, + pub name: fn() -> &'static str, pub from: Camera3DProperties, pub to: Camera3DProperties, pub blur: fn() -> Camera3DBlur, @@ -1177,7 +1294,7 @@ pub struct MotionTemplate { pub static MOTION_TEMPLATES: &[MotionTemplate] = &[ MotionTemplate { id: "glide-across", - name: "Glide across", + name: tr::editor::panel::camera3d::look::glide_across, from: pose(Camera3DProperties { tilt_x: -46.65, tilt_y: 42.49, @@ -1204,7 +1321,7 @@ pub static MOTION_TEMPLATES: &[MotionTemplate] = &[ }, MotionTemplate { id: "drift-down", - name: "Drift down", + name: tr::editor::panel::camera3d::look::drift_down, from: pose(Camera3DProperties { zoom: 0.8, pan_x: 0.536, @@ -1221,7 +1338,7 @@ pub static MOTION_TEMPLATES: &[MotionTemplate] = &[ }, MotionTemplate { id: "rising-sweep", - name: "Rising sweep", + name: tr::editor::panel::camera3d::look::rising_sweep, from: pose(Camera3DProperties { tilt_x: -57.83, tilt_y: -8.7, @@ -1246,7 +1363,7 @@ pub static MOTION_TEMPLATES: &[MotionTemplate] = &[ }, MotionTemplate { id: "pull-back", - name: "Pull back", + name: tr::editor::panel::camera3d::look::pull_back, from: pose(Camera3DProperties { rotate_x: -14., zoom: 0.715, @@ -1261,7 +1378,7 @@ pub static MOTION_TEMPLATES: &[MotionTemplate] = &[ }, MotionTemplate { id: "top-down", - name: "Top down", + name: tr::editor::panel::camera3d::look::top_down, from: pose(Camera3DProperties { tilt_x: 24.8, tilt_y: 17.04, @@ -1289,7 +1406,7 @@ pub static MOTION_TEMPLATES: &[MotionTemplate] = &[ }, MotionTemplate { id: "tilt-away", - name: "Tilt away", + name: tr::editor::panel::camera3d::look::tilt_away, from: pose(Camera3DProperties { rotate_x: -5., zoom: 0.5, @@ -1304,7 +1421,7 @@ pub static MOTION_TEMPLATES: &[MotionTemplate] = &[ }, MotionTemplate { id: "unfold", - name: "Unfold", + name: tr::editor::panel::camera3d::look::unfold, from: pose(Camera3DProperties { rotate_x: -42.96, zoom: 2.05, @@ -1322,7 +1439,7 @@ pub static MOTION_TEMPLATES: &[MotionTemplate] = &[ }, MotionTemplate { id: "slide-by", - name: "Slide by", + name: tr::editor::panel::camera3d::look::slide_by, from: pose(Camera3DProperties { tilt_x: -30.29, tilt_y: 60., @@ -1380,7 +1497,7 @@ pub struct SceneShot { pub struct Camera3DScene { pub id: &'static str, - pub name: &'static str, + pub name: fn() -> &'static str, pub shots: &'static [SceneShot], } @@ -1405,7 +1522,7 @@ fn showcase_push_blur() -> Camera3DBlur { pub static CAMERA3D_SCENES: &[Camera3DScene] = &[ Camera3DScene { id: "showcase", - name: "Showcase", + name: tr::editor::panel::camera3d::sequence::showcase, // Transcribed verbatim from the hand-built reference project: a tight // close-up truck, a fold-down overhead sweep, then a long push in. shots: &[ @@ -1475,7 +1592,7 @@ pub static CAMERA3D_SCENES: &[Camera3DScene] = &[ }, Camera3DScene { id: "product-tour", - name: "Product tour", + name: tr::editor::panel::camera3d::sequence::product_tour, // Reveal, orbit, settle: `unfold`, `perspective`, `center`. shots: &[ SceneShot { @@ -1533,7 +1650,7 @@ pub static CAMERA3D_SCENES: &[Camera3DScene] = &[ }, Camera3DScene { id: "punch-in", - name: "Punch in", + name: tr::editor::panel::camera3d::sequence::punch_in, // Push in, hold on the detail, then release: `spotlight`, `close-up`, // `pull-back`. shots: &[ @@ -1646,10 +1763,19 @@ const CAMERA3D_DEFAULT_BLUR_FALLOFF: f64 = 0.7; /// The Focus segmented control: the three real modes, in the order the source /// lists them. "None" is the toggle, not an option. -const CAMERA3D_FOCUS_MODES: [(Camera3DBlurMode, &str); 3] = [ - (Camera3DBlurMode::Radial, "Radial"), - (Camera3DBlurMode::Directional, "Directional"), - (Camera3DBlurMode::TiltShift, "Tilt shift"), +const CAMERA3D_FOCUS_MODES: [(Camera3DBlurMode, fn() -> &'static str); 3] = [ + ( + Camera3DBlurMode::Radial, + tr::editor::panel::camera3d::focus_mode::radial, + ), + ( + Camera3DBlurMode::Directional, + tr::editor::panel::camera3d::focus_mode::directional, + ), + ( + Camera3DBlurMode::TiltShift, + tr::editor::panel::camera3d::focus_mode::tilt_shift, + ), ]; // --------------------------------------------------------------------------- @@ -2111,7 +2237,7 @@ pub fn match_camera3d_look(segment: &Camera3DSegment) -> Option { .map(|template| Look { kind: LookKind::Move, id: template.id, - name: template.name, + name: (template.name)(), }) .or_else(|| { ANGLE_PRESETS @@ -2123,7 +2249,7 @@ pub fn match_camera3d_look(segment: &Camera3DSegment) -> Option { .map(|preset| Look { kind: LookKind::Angle, id: preset.id, - name: preset.name, + name: (preset.name)(), }) }) } @@ -2134,9 +2260,9 @@ pub fn camera3d_shot_label(segment: &Camera3DSegment) -> &'static str { return look.name; } if poses_equal(&start_pose(segment), &end_pose(segment)) { - "Still shot" + tr::editor::panel::camera3d::shot::still() } else { - "Custom move" + tr::editor::panel::camera3d::shot::custom() } } @@ -3694,7 +3820,7 @@ impl EditorWindow { TEXT_SEGMENT_WEIGHTS .iter() .map(|(weight, label)| { - ui::MenuItem::new(*label, (*weight - current).abs() < f32::EPSILON) + ui::MenuItem::new(label(), (*weight - current).abs() < f32::EPSILON) }) .collect() } @@ -3706,7 +3832,7 @@ impl EditorWindow { MOTION_EASINGS .iter() .enumerate() - .map(|(index, (_, label, _, _))| ui::MenuItem::new(*label, index == current)) + .map(|(index, (_, label, _, _))| ui::MenuItem::new(label(), index == current)) .collect() } _ => Vec::new(), @@ -4212,13 +4338,13 @@ impl EditorWindow { .flex_col() .gap(px(14.)) .child(self.slider_field( - SharedString::from(format!("Zoom {}", index + 1)), + SharedString::from(tr::editor::panel::zoom::title(index + 1)), SliderKey::Panel(PanelSlider::ZoomAmount, index), "x", cx, )) .child( - ui::Field::section(&theme, "Zoom Mode").child( + ui::Field::section(&theme, tr::editor::panel::zoom::mode()).child( div() .flex() .flex_col() @@ -4307,51 +4433,53 @@ impl EditorWindow { .border_color(Hsla::from(theme.editor.line)) .child({ let mut field = self.slider_field( - "Zoom Amount", + tr::editor::panel::zoom::amount(), SliderKey::Panel(PanelSlider::ZoomAmountAll, 0), "x", cx, ); if mixed_amount { - field = field.value(mixed_badge("Mixed")); + field = field.value(mixed_badge(tr::editor::panel::zoom::mixed())); } field }) .child({ - let mut field = ui::Field::section(&theme, "Zoom Mode").child( - div() - .flex() - .flex_col() - .gap(px(16.)) - .child(self.zoom_mode_tabs( - manual && !mixed_mode, - cx, - |this, want_manual, window, cx| { - this.set_all_zoom_modes(want_manual, window, cx); - }, - )) - .children((!mixed_mode).then(|| self.zoom_mode_helper(manual, cx))) - .children((manual && !mixed_mode).then(|| { - div() - .flex() - .flex_col() - .gap(px(6.)) - .child(self.render_pad(PadKey::ZoomMulti, cx)) - .children(positions_mixed.then(|| { - div() - .text_size(px(12.)) - .text_color(Hsla::from(theme.gray_10)) - .child( - "Segments zoom into different spots. Drag \ - to move them all to the same one.", - ) - .into_any_element() - })) - .into_any_element() - })), - ); + let mut field = ui::Field::section(&theme, tr::editor::panel::zoom::mode()) + .child( + div() + .flex() + .flex_col() + .gap(px(16.)) + .child(self.zoom_mode_tabs( + manual && !mixed_mode, + cx, + |this, want_manual, window, cx| { + this.set_all_zoom_modes(want_manual, window, cx); + }, + )) + .children( + (!mixed_mode).then(|| self.zoom_mode_helper(manual, cx)), + ) + .children((manual && !mixed_mode).then(|| { + div() + .flex() + .flex_col() + .gap(px(6.)) + .child(self.render_pad(PadKey::ZoomMulti, cx)) + .children(positions_mixed.then(|| { + div() + .text_size(px(12.)) + .text_color(Hsla::from(theme.gray_10)) + .child( + tr::editor::panel::zoom::positions_mixed(), + ) + .into_any_element() + })) + .into_any_element() + })), + ); if mixed_mode { - field = field.value(mixed_badge("Mixed")); + field = field.value(mixed_badge(tr::editor::panel::zoom::mixed())); } field }), @@ -4381,7 +4509,9 @@ impl EditorWindow { div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_11)) - .child(SharedString::from(format!("Zoom {}", index + 1))), + .child(SharedString::from(tr::editor::panel::zoom::title( + index + 1, + ))), ) .child( div() @@ -4464,18 +4594,27 @@ impl EditorWindow { .border_1() .border_color(Hsla::from(theme.gray_12)) .p(px(1.)) - .child(tab("Auto", "zoom-mode-auto", !manual, false, auto_locked)) - .child(tab("Manual", "zoom-mode-manual", manual, true, false)), + .child(tab( + tr::editor::panel::zoom::auto(), + "zoom-mode-auto", + !manual, + false, + auto_locked, + )) + .child(tab( + tr::editor::panel::zoom::manual(), + "zoom-mode-manual", + manual, + true, + false, + )), ) // The explainer under the tabs (`:5648-5653`, `:6024-6029`). .children(auto_locked.then(|| { div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_11)) - .child( - "Auto mode needs cursor capture. Enable \"Custom cursor capture \ - (Studio)\" in Settings \u{2192} General.", - ) + .child(tr::editor::panel::zoom::auto_locked()) })) .into_any_element() } @@ -4510,7 +4649,7 @@ impl EditorWindow { .size(px(14.)) .text_color(Hsla::from(theme.gray_11)), ) - .child("How does it work?") + .child(tr::editor::panel::zoom::how_it_works()) .child( div().ml_auto().child( svg() @@ -4580,9 +4719,9 @@ impl EditorWindow { .text_size(px(12.)) .text_color(Hsla::from(theme.gray_11)) .child(if manual { - "Manual zoom stays on a fixed spot you pick below." + tr::editor::panel::zoom::manual_help() } else { - "Automatic zoom follows your cursor around the screen." + tr::editor::panel::zoom::auto_help() }), ) .into_any_element(), @@ -4949,9 +5088,9 @@ impl EditorWindow { let sample_size = (style.font_size * 0.2).clamp(11., 22.); let sample_color = style.color.and_then(hex_color).unwrap_or_else(gpui::white); let sample_text: SharedString = if style.uppercase { - SharedString::from(preset.sample.to_uppercase()) + SharedString::from((preset.sample)().to_uppercase()) } else { - SharedString::from(preset.sample) + SharedString::from((preset.sample)()) }; let run = div() .max_w_full() @@ -5027,7 +5166,8 @@ impl EditorWindow { .text_size(px(10.)) .font_weight(FontWeight::MEDIUM) .text_color(with_alpha(gpui::white(), 0.55)) - .child(preset.name), + .truncate() + .child((preset.name)()), ) .on_click(cx.listener(move |this, _, window, cx| { this.apply_text_preset_to(index, id, window, cx); @@ -5037,7 +5177,12 @@ impl EditorWindow { let shown: Vec<&'static TextPreset> = TEXT_PRESETS .iter() - .filter(|preset| group == 0 || TEXT_PRESET_GROUPS.get(group - 1) == Some(&preset.group)) + .filter(|preset| { + group == 0 + || TEXT_PRESET_GROUPS + .get(group - 1) + .is_some_and(|(id, _)| *id == preset.group) + }) .collect(); div() @@ -5051,12 +5196,12 @@ impl EditorWindow { .flex_row() .gap(px(4.)) .flex_wrap() - .child(chip(0, "All", cx)) + .child(chip(0, tr::editor::panel::text_preset::group::all(), cx)) .children( TEXT_PRESET_GROUPS .iter() .enumerate() - .map(|(slot, label)| chip(slot + 1, label, cx)), + .map(|(slot, (_, label))| chip(slot + 1, label(), cx)), ), ) .children(shown.chunks(2).map(|row| { @@ -5214,6 +5359,8 @@ impl EditorWindow { .child(text_animation_depiction(animation, color, accent)) .child( div() + .max_w_full() + .truncate() .text_size(px(10.5)) .font_weight(FontWeight::MEDIUM) .child(label), @@ -5250,7 +5397,7 @@ impl EditorWindow { .iter() .enumerate() .map(|(column, (animation, label))| { - tile(row * 3 + column, *animation, label, cx) + tile(row * 3 + column, *animation, label(), cx) }), ) })) @@ -5283,8 +5430,8 @@ impl EditorWindow { .iter() .find(|(weight, _)| (*weight - segment.font_weight).abs() < f32::EPSILON) .map_or_else( - || SharedString::from(format!("Custom ({})", segment.font_weight)), - |(_, label)| SharedString::from(*label), + || SharedString::from(tr::editor::panel::font_weight::custom(segment.font_weight)), + |(_, label)| SharedString::from(label()), ); let is_out = self.sidebar.text_anim_edge; let edge_animation = if is_out { @@ -5307,109 +5454,109 @@ impl EditorWindow { .gap(px(16.)) // -- A. Text --------------------------------------------------- .child( - ui::Field::section(&theme, SharedString::from(format!("Text {}", index + 1))) - .value( - ui::Toggle::plain( - &theme, - SharedString::from(format!("text-enabled-{index}")), - enabled, - ) - .on_click(cx.listener(move |this, _, window, cx| { - this.edit_text_segment( - "text-enabled", - index, - window, - cx, - move |segment| { - segment.enabled = !enabled; - true - }, - ); - })) - .into_any_element(), + ui::Field::section( + &theme, + SharedString::from(tr::editor::panel::text_segment::title(index + 1)), + ) + .value( + ui::Toggle::plain( + &theme, + SharedString::from(format!("text-enabled-{index}")), + enabled, ) - .child(self.render_text_content_input(index)) - .child( - div() - .flex() - .flex_row() - .items_center() - .gap(px(8.)) - .child(self.text_case_chip( - SharedString::from(format!("text-italic-{index}")), - "I", - true, - italic, - cx.listener(move |this, _, window, cx| { - this.edit_text_segment( - "text-italic", - index, - window, - cx, - move |segment| { - segment.italic = !italic; - true - }, - ); - }), - )) - .child(self.text_case_chip( - SharedString::from(format!("text-uppercase-{index}")), - "AA", - false, - uppercase, - cx.listener(move |this, _, window, cx| { - this.edit_text_segment( - "text-uppercase", - index, - window, - cx, - move |segment| { - segment.uppercase = !uppercase; - true - }, - ); - }), - )) - .child( - div().ml_auto().child( - ui::SegmentedControl::editor( - &theme, - SharedString::from(format!("text-align-{index}")), - TEXT_ALIGNS - .iter() - .map(|(value, icon)| { - ui::SegmentOption::icon(*icon, *value == align) - }) - .collect(), - ) - .on_select(cx.listener( - move |this, choice: &usize, window, cx| { - let Some((value, _)) = TEXT_ALIGNS.get(*choice) else { - return; - }; - let value = *value; - this.edit_text_segment( - "text-align", - index, - window, - cx, - move |segment| { - segment.align = value; - true - }, - ); - }, - )), - ), + .on_click(cx.listener(move |this, _, window, cx| { + this.edit_text_segment("text-enabled", index, window, cx, move |segment| { + segment.enabled = !enabled; + true + }); + })) + .into_any_element(), + ) + .child(self.render_text_content_input(index)) + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .child(self.text_case_chip( + SharedString::from(format!("text-italic-{index}")), + "I", + true, + italic, + cx.listener(move |this, _, window, cx| { + this.edit_text_segment( + "text-italic", + index, + window, + cx, + move |segment| { + segment.italic = !italic; + true + }, + ); + }), + )) + .child(self.text_case_chip( + SharedString::from(format!("text-uppercase-{index}")), + "AA", + false, + uppercase, + cx.listener(move |this, _, window, cx| { + this.edit_text_segment( + "text-uppercase", + index, + window, + cx, + move |segment| { + segment.uppercase = !uppercase; + true + }, + ); + }), + )) + .child( + div().ml_auto().child( + ui::SegmentedControl::editor( + &theme, + SharedString::from(format!("text-align-{index}")), + TEXT_ALIGNS + .iter() + .map(|(value, icon)| { + ui::SegmentOption::icon(*icon, *value == align) + }) + .collect(), + ) + .on_select(cx.listener( + move |this, choice: &usize, window, cx| { + let Some((value, _)) = TEXT_ALIGNS.get(*choice) else { + return; + }; + let value = *value; + this.edit_text_segment( + "text-align", + index, + window, + cx, + move |segment| { + segment.align = value; + true + }, + ); + }, + )), ), - ), + ), + ), ) // -- B. Style -------------------------------------------------- - .child(ui::Field::section(&theme, "Style").child(self.render_text_presets(index, cx))) + .child( + ui::Field::section(&theme, tr::editor::panel::text_segment::style()) + .child(self.render_text_presets(index, cx)), + ) // -- C. Font --------------------------------------------------- .child( - ui::Field::section(&theme, "Font").child( + ui::Field::section(&theme, tr::editor::panel::text_segment::font()).child( div() .flex() .flex_col() @@ -5427,19 +5574,19 @@ impl EditorWindow { cx, )) .child(self.slider_field( - "Size", + tr::editor::panel::text_segment::font_size(), SliderKey::Panel(PanelSlider::TextFontSize, index), "int", cx, )) .child(self.slider_field( - "Line height", + tr::editor::panel::text_segment::line_height(), SliderKey::Panel(PanelSlider::TextLineHeight, index), "", cx, )) .child(self.slider_field( - "Letter spacing", + tr::editor::panel::text_segment::letter_spacing(), SliderKey::Panel(PanelSlider::TextLetterSpacing, index), "px", cx, @@ -5448,61 +5595,67 @@ impl EditorWindow { ) // -- D. Look --------------------------------------------------- .child( - ui::Field::section(&theme, "Look").child( + ui::Field::section(&theme, tr::editor::panel::text_segment::look()).child( div() .flex() .flex_col() .gap(px(8.)) .child( - ui::Field::stacked(&theme, "Color").child(self.render_color_input( - ColorTarget::TextColor(index), - &color, - cx, - )), - ) - .child( - ui::Field::inline(&theme, "Gradient").child( - ui::Toggle::plain( - &theme, - SharedString::from(format!("text-gradient-{index}")), - gradient_color.is_some(), - ) - .on_click(cx.listener( - move |this, _, window, cx| { - this.edit_text_segment( - "text-gradient", - index, - window, - cx, - move |segment| { - segment.gradient_color = - match segment.gradient_color.is_some() { - true => None, - false => Some("#7c9cff".to_string()), - }; - true - }, - ); - }, + ui::Field::stacked(&theme, tr::editor::panel::text_segment::color()) + .child(self.render_color_input( + ColorTarget::TextColor(index), + &color, + cx, )), - ), ) - .children(gradient_color.map(|gradient| { - self.render_color_input(ColorTarget::TextGradient(index), &gradient, cx) - })) .child( - ui::Field::stacked(&theme, "Background").child( - ui::SegmentedControl::editor( - &theme, - SharedString::from(format!("text-background-{index}")), - std::iter::once(ui::SegmentOption::new( - "None", - background_slot == 0, - )) - .chain(TEXT_BACKGROUND_STYLES.iter().enumerate().map( + ui::Field::inline(&theme, tr::editor::panel::text_segment::gradient()) + .child( + ui::Toggle::plain( + &theme, + SharedString::from(format!("text-gradient-{index}")), + gradient_color.is_some(), + ) + .on_click(cx.listener( + move |this, _, window, cx| { + this.edit_text_segment( + "text-gradient", + index, + window, + cx, + move |segment| { + segment.gradient_color = + match segment.gradient_color.is_some() { + true => None, + false => Some("#7c9cff".to_string()), + }; + true + }, + ); + }, + )), + ), + ) + .children(gradient_color.map(|gradient| { + self.render_color_input(ColorTarget::TextGradient(index), &gradient, cx) + })) + .child( + ui::Field::stacked( + &theme, + tr::editor::panel::text_segment::background(), + ) + .child( + ui::SegmentedControl::editor( + &theme, + SharedString::from(format!("text-background-{index}")), + std::iter::once(ui::SegmentOption::new( + tr::editor::panel::text_background::none(), + background_slot == 0, + )) + .chain(TEXT_BACKGROUND_STYLES.iter().enumerate().map( |(slot, (_, label))| { ui::SegmentOption::new( - *label, + label(), background_slot == slot + 1, ) }, @@ -5551,7 +5704,7 @@ impl EditorWindow { ) })) .child(self.slider_field( - "Outline", + tr::editor::panel::text_segment::outline(), SliderKey::Panel(PanelSlider::TextStroke, index), "px", cx, @@ -5564,19 +5717,19 @@ impl EditorWindow { ) })) .child(self.slider_field( - "Shadow", + tr::editor::panel::text_segment::shadow(), SliderKey::Panel(PanelSlider::TextShadow, index), "x100%", cx, )) .child(self.slider_field( - "Glow", + tr::editor::panel::text_segment::glow(), SliderKey::Panel(PanelSlider::TextGlow, index), "x100%", cx, )) .child(self.slider_field( - "Opacity", + tr::editor::panel::opacity(), SliderKey::Panel(PanelSlider::TextOpacity, index), "x100%", cx, @@ -5585,14 +5738,14 @@ impl EditorWindow { ) // -- E. Animation ---------------------------------------------- .child( - ui::Field::section(&theme, "Animation") + ui::Field::section(&theme, tr::editor::panel::text_segment::animation()) .value( ui::SegmentedControl::editor( &theme, SharedString::from(format!("text-anim-edge-{index}")), vec![ - ui::SegmentOption::new("In", !is_out), - ui::SegmentOption::new("Out", is_out), + ui::SegmentOption::new(tr::editor::panel::edge_in(), !is_out), + ui::SegmentOption::new(tr::editor::panel::edge_out(), is_out), ], ) .on_select(cx.listener(move |this, choice: &usize, _window, cx| { @@ -5609,7 +5762,7 @@ impl EditorWindow { .child(self.render_text_animation_tiles(index, cx)) .children((edge_animation != TextAnimation::None).then(|| { self.slider_field( - "Duration", + tr::editor::panel::duration(), SliderKey::Panel( if is_out { PanelSlider::TextAnimOutDuration @@ -5627,7 +5780,7 @@ impl EditorWindow { ) // -- F. Layout ------------------------------------------------- .child( - ui::Field::section(&theme, "Layout").child( + ui::Field::section(&theme, tr::editor::panel::text_segment::layout()).child( div() .flex() .flex_col() @@ -5639,7 +5792,7 @@ impl EditorWindow { TEXT_LAYOUTS .iter() .map(|(value, label)| { - ui::SegmentOption::new(*label, *value == layout) + ui::SegmentOption::new(label(), *value == layout) }) .collect(), ) @@ -5675,15 +5828,12 @@ impl EditorWindow { div() .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_3)) - .child( - "Pauses the video while the text is shown, then resumes \ - where it left off.", - ) + .child(tr::editor::panel::text_segment::fullscreen_help()) .into_any_element() })) .children((layout != TextLayout::Overlay).then(|| { self.slider_field( - "Screen transition", + tr::editor::panel::text_segment::screen_transition(), SliderKey::Panel(PanelSlider::TextLayoutTransition, index), "secs", cx, @@ -5711,8 +5861,11 @@ impl EditorWindow { .flex_col() .gap(px(16.)) .child( - ui::Field::section(&theme, SharedString::from(format!("Caption {}", index + 1))) - .child(self.render_field_input(FieldKey::CaptionText(index), Some(96.))), + ui::Field::section( + &theme, + SharedString::from(tr::editor::panel::caption::title(index + 1)), + ) + .child(self.render_field_input(FieldKey::CaptionText(index), Some(96.))), ) .child(self.timing_field( FieldKey::CaptionStart(index), @@ -5742,7 +5895,7 @@ impl EditorWindow { .child( ui::Field::section( &theme, - SharedString::from(format!("Keyboard {}", index + 1)), + SharedString::from(tr::editor::panel::keyboard::title(index + 1)), ) .child(self.render_field_input(FieldKey::KeyboardText(index), None)), ) @@ -5754,7 +5907,7 @@ impl EditorWindow { cx, )) .child(self.slider_field( - "Fade Duration", + tr::editor::panel::fade_duration(), SliderKey::Panel(PanelSlider::KeyboardFade, index), "", cx, @@ -5799,7 +5952,7 @@ impl EditorWindow { .child(self.render_field_input(key, None)) }; - ui::Field::section(&theme, "Timing") + ui::Field::section(&theme, tr::editor::panel::timing::title()) .child( div() .flex() @@ -5816,16 +5969,20 @@ impl EditorWindow { .flex_row() .gap(px(8.)) .items_start() - .child(box_for("Start", start, start_key)) + .child(box_for( + tr::editor::panel::timing::start(), + start, + start_key, + )) .child( div() .pt(px(40.)) .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.gray_10)) - .child("to"), + .child(tr::editor::panel::timing::to()), ) - .child(box_for("End", end, end_key)), + .child(box_for(tr::editor::panel::timing::end(), end, end_key)), ) .child( div() @@ -5839,7 +5996,7 @@ impl EditorWindow { .bg(crate::editor_sidebar::with_alpha(theme.gray_1, 0.7)) .text_size(px(12.)) .text_color(Hsla::from(theme.gray_11)) - .child("Duration") + .child(tr::editor::panel::duration()) .child( div() .font_weight(FontWeight::MEDIUM) @@ -5868,156 +6025,160 @@ impl EditorWindow { .name .clone() .filter(|name| !name.is_empty()) - .unwrap_or_else(|| "Audio".to_string()); + .unwrap_or_else(|| tr::editor::panel::audio::fallback_name().to_string()); div() .flex() .flex_col() .gap(px(16.)) .child( - ui::Field::section(&theme, SharedString::from(format!("Audio {}", index + 1))) - .child( - div() - .flex() - .flex_col() - .gap(px(12.)) - // The "Tap to change track" row opens the audio - // library panel, which is not part of this rev -- - // see the README's deviation. - .child( - div() - .id(SharedString::from(format!("audio-replace-{index}"))) - .flex() - .flex_row() - .gap(px(12.)) - .items_center() - .p(px(8.)) - .w_full() - .rounded(px(12.)) - .border_1() - .border_color(Hsla::from(theme.editor.line)) - .bg(Hsla::from(theme.gray_2)) - .cursor_pointer() - .tab_index(0) - .on_click(cx.listener(move |this, _, _, cx| { - this.open_audio_replace(index, cx); - })) - .child( - div() - .size(px(40.)) - .flex_none() - .rounded(px(8.)) - .bg(TrackKind::Audio.color()), - ) - .child( - div() - .flex() - .flex_col() - .flex_1() - .min_w_0() - .child( - div() - .text_size(px(14.)) - .font_weight(FontWeight::MEDIUM) - .truncate() - .text_color(Hsla::from(theme.gray_12)) - .child(SharedString::from(name)), - ) - .child( - div() - .text_size(px(12.)) - .text_color(Hsla::from(theme.gray_10)) - .child("Tap to change track"), - ), - ) - .child( - div() - .flex() - .flex_row() - .gap(px(4.)) - .items_center() - .px(px(8.)) - .h(px(28.)) - .flex_none() - .rounded(px(8.)) - .border_1() - .border_color(Hsla::from(theme.editor.line)) - .bg(Hsla::from(theme.gray_1)) - .text_size(px(12.)) - .font_weight(FontWeight::MEDIUM) - .text_color(Hsla::from(theme.gray_11)) - .opacity(0.5) - .child( - svg() - .path("icons/refresh-cw.svg") - .size(px(14.)) - .text_color(Hsla::from(theme.gray_11)), - ) - .child("Change"), - ), - ) - .child( - div() - .flex() - .flex_row() - .gap(px(12.)) - .items_center() - .child(div().flex_1().min_w_0().child( - self.render_field_input(FieldKey::AudioName(index), None), - )) - .child( - div() - .flex() - .flex_col() - .items_center() - .gap(px(8.)) - .child( - div() - .text_size(px(12.)) - .text_color(Hsla::from(theme.gray_11)) - .child("Enabled"), - ) - .child( - ui::Toggle::plain( - &theme, - SharedString::from(format!( - "audio-enabled-{index}" - )), - enabled, - ) - .on_click(cx.listener( - move |this, _, window, cx| { - this.edit_audio_segment( - "audio-enabled", - index, - window, - cx, - move |segment| { - segment.enabled = !enabled; - true - }, - ); - }, + ui::Field::section( + &theme, + SharedString::from(tr::editor::panel::audio::title(index + 1)), + ) + .child( + div() + .flex() + .flex_col() + .gap(px(12.)) + // The "Tap to change track" row opens the audio + // library panel, which is not part of this rev -- + // see the README's deviation. + .child( + div() + .id(SharedString::from(format!("audio-replace-{index}"))) + .flex() + .flex_row() + .gap(px(12.)) + .items_center() + .p(px(8.)) + .w_full() + .rounded(px(12.)) + .border_1() + .border_color(Hsla::from(theme.editor.line)) + .bg(Hsla::from(theme.gray_2)) + .cursor_pointer() + .tab_index(0) + .on_click(cx.listener(move |this, _, _, cx| { + this.open_audio_replace(index, cx); + })) + .child( + div() + .size(px(40.)) + .flex_none() + .rounded(px(8.)) + .bg(TrackKind::Audio.color()), + ) + .child( + div() + .flex() + .flex_col() + .flex_1() + .min_w_0() + .child( + div() + .text_size(px(14.)) + .font_weight(FontWeight::MEDIUM) + .truncate() + .text_color(Hsla::from(theme.gray_12)) + .child(SharedString::from(name)), + ) + .child( + div() + .text_size(px(12.)) + .truncate() + .text_color(Hsla::from(theme.gray_10)) + .child(tr::editor::panel::audio::change_hint()), + ), + ) + .child( + div() + .flex() + .flex_row() + .gap(px(4.)) + .items_center() + .px(px(8.)) + .h(px(28.)) + .flex_none() + .rounded(px(8.)) + .border_1() + .border_color(Hsla::from(theme.editor.line)) + .bg(Hsla::from(theme.gray_1)) + .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) + .text_color(Hsla::from(theme.gray_11)) + .opacity(0.5) + .child( + svg() + .path("icons/refresh-cw.svg") + .size(px(14.)) + .text_color(Hsla::from(theme.gray_11)), + ) + .child(tr::editor::panel::audio::change()), + ), + ) + .child( + div() + .flex() + .flex_row() + .gap(px(12.)) + .items_center() + .child(div().flex_1().min_w_0().child( + self.render_field_input(FieldKey::AudioName(index), None), + )) + .child( + div() + .flex() + .flex_col() + .items_center() + .gap(px(8.)) + .child( + div() + .text_size(px(12.)) + .text_color(Hsla::from(theme.gray_11)) + .child(tr::editor::panel::enabled()), + ) + .child( + ui::Toggle::plain( + &theme, + SharedString::from(format!( + "audio-enabled-{index}" )), + enabled, + ) + .on_click( + cx.listener(move |this, _, window, cx| { + this.edit_audio_segment( + "audio-enabled", + index, + window, + cx, + move |segment| { + segment.enabled = !enabled; + true + }, + ); + }), ), - ), - ), - ), + ), + ), + ), + ), ) .child(self.slider_field( - "Volume", + tr::editor::panel::audio::volume(), SliderKey::Panel(PanelSlider::AudioVolume, index), "db", cx, )) .child(self.slider_field( - "Fade In", + tr::editor::panel::audio::fade_in(), SliderKey::Panel(PanelSlider::AudioFadeIn, index), "s", cx, )) .child(self.slider_field( - "Fade Out", + tr::editor::panel::audio::fade_out(), SliderKey::Panel(PanelSlider::AudioFadeOut, index), "s", cx, @@ -6039,7 +6200,11 @@ impl EditorWindow { let effect = mask_effect(segment); let mut panel = div().flex().flex_col().gap(px(16.)).child( - ui::Field::section(&theme, SharedString::from(format!("Mask {}", index + 1))).child( + ui::Field::section( + &theme, + SharedString::from(tr::editor::panel::mask::title(index + 1)), + ) + .child( div() .flex() .flex_row() @@ -6048,7 +6213,10 @@ impl EditorWindow { .gap(px(16.)) .child(div().flex_1().min_w_0().child(self.radio_row( SharedString::from(format!("mask-kind-{index}")), - vec![("Sensitive", sensitive), ("Highlight", !sensitive)], + vec![ + (tr::editor::panel::mask::sensitive(), sensitive), + (tr::editor::panel::mask::highlight(), !sensitive), + ], cx.listener(move |this, choice: &usize, window, cx| { let want_sensitive = *choice == 0; this.edit_mask_segment( @@ -6086,7 +6254,7 @@ impl EditorWindow { div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_11)) - .child("Enabled"), + .child(tr::editor::panel::enabled()), ) .child( ui::Toggle::plain( @@ -6115,32 +6283,45 @@ impl EditorWindow { if sensitive { panel = panel - .child(ui::Field::section(&theme, "Effect").child(self.radio_row( - SharedString::from(format!("mask-effect-{index}")), - vec![ - ("Blur", effect == MaskEffect::Blur), - ("Pixelate", effect == MaskEffect::Pixelate), - ], - cx.listener(move |this, choice: &usize, window, cx| { - let next = if *choice == 0 { - MaskEffect::Blur - } else { - MaskEffect::Pixelate - }; - this.edit_mask_segment("mask-effect", index, window, cx, move |segment| { - let amount = mask_effect_amount(segment); - segment.pixelation = encode_mask_effect(next, amount); - segment.opacity = 1.; - segment.keyframes.intensity.clear(); - true - }); - }), - ))) + .child( + ui::Field::section(&theme, tr::editor::panel::mask::effect()).child( + self.radio_row( + SharedString::from(format!("mask-effect-{index}")), + vec![ + (tr::editor::panel::mask::blur(), effect == MaskEffect::Blur), + ( + tr::editor::panel::mask::pixelate(), + effect == MaskEffect::Pixelate, + ), + ], + cx.listener(move |this, choice: &usize, window, cx| { + let next = if *choice == 0 { + MaskEffect::Blur + } else { + MaskEffect::Pixelate + }; + this.edit_mask_segment( + "mask-effect", + index, + window, + cx, + move |segment| { + let amount = mask_effect_amount(segment); + segment.pixelation = encode_mask_effect(next, amount); + segment.opacity = 1.; + segment.keyframes.intensity.clear(); + true + }, + ); + }), + ), + ), + ) .child(self.slider_field( if effect == MaskEffect::Blur { - "Blur" + tr::editor::panel::mask::blur() } else { - "Pixel Size" + tr::editor::panel::mask::pixel_size() }, SliderKey::Panel(PanelSlider::MaskAmount, index), "px", @@ -6149,13 +6330,13 @@ impl EditorWindow { } else { panel = panel .child(self.slider_field( - "Outside Darkness", + tr::editor::panel::mask::outside_darkness(), SliderKey::Panel(PanelSlider::MaskDarkness, index), "", cx, )) .child(self.slider_field( - "Fade Duration", + tr::editor::panel::fade_duration(), SliderKey::Panel(PanelSlider::MaskFade, index), "s", cx, @@ -6265,7 +6446,10 @@ impl EditorWindow { .find(|(candidate, ..)| { std::mem::discriminant(candidate) == std::mem::discriminant(&mode) }) - .map_or("Shows both screen and camera", |(_, _, _, text)| *text); + .map_or( + tr::editor::panel::scene::description::default(), + |(_, _, _, text)| text(), + ); let mut panel = div() .flex() @@ -6281,7 +6465,7 @@ impl EditorWindow { .child( ui::EditorButton::plain(&theme, "scene-done") .left_icon("icons/check.svg") - .label("Done") + .label(tr::editor::panel::done()) .on_click( cx.listener(|this, _, _window, cx| this.set_selection(None, cx)), ), @@ -6290,14 +6474,14 @@ impl EditorWindow { ui::EditorButton::plain(&theme, "scene-delete") .danger(&theme) .left_icon("icons/trash.svg") - .label("Delete") + .label(tr::editor::panel::delete()) .on_click(cx.listener(|this, _, window, cx| { this.delete_selected_segments(window, cx) })), ), ) .child( - ui::Field::section(&theme, "Camera Layout").child( + ui::Field::section(&theme, tr::editor::panel::scene::camera_layout()).child( div() .flex() .flex_col() @@ -6346,7 +6530,7 @@ impl EditorWindow { Hsla::from(theme.gray_11) }, )) - .child(*label) + .child(label()) .when(!disabled, |this| { this.on_click(cx.listener( move |this, _, window, cx| { @@ -6376,25 +6560,29 @@ impl EditorWindow { ), ) .child( - ui::Field::section(&theme, "Transition").child( + ui::Field::section(&theme, tr::editor::panel::scene::transition()).child( div() .flex() .flex_col() .gap(px(12.)) - .child(ui::Subfield::plain(&theme, "In").child( - div().flex_1().min_w_0().ml(px(16.)).child(self.slider_flex( - SliderKey::Panel(PanelSlider::SceneTransitionIn, index), - "s2", - cx, - )), - )) - .child(ui::Subfield::plain(&theme, "Out").child( - div().flex_1().min_w_0().ml(px(16.)).child(self.slider_flex( - SliderKey::Panel(PanelSlider::SceneTransitionOut, index), - "s2", - cx, - )), - )), + .child( + ui::Subfield::plain(&theme, tr::editor::panel::edge_in()).child( + div().flex_1().min_w_0().ml(px(16.)).child(self.slider_flex( + SliderKey::Panel(PanelSlider::SceneTransitionIn, index), + "s2", + cx, + )), + ), + ) + .child( + ui::Subfield::plain(&theme, tr::editor::panel::edge_out()).child( + div().flex_1().min_w_0().ml(px(16.)).child(self.slider_flex( + SliderKey::Panel(PanelSlider::SceneTransitionOut, index), + "s2", + cx, + )), + ), + ), ), ); @@ -6402,24 +6590,24 @@ impl EditorWindow { panel = panel .child(dashed_divider(Hsla::from(theme.editor.line))) .child(self.slider_field( - "Screen Zoom", + tr::editor::panel::scene::screen_zoom(), SliderKey::Panel(PanelSlider::SceneScreenZoom, index), "", cx, )) .child( - ui::Field::stacked(&theme, "Screen Position") + ui::Field::stacked(&theme, tr::editor::panel::scene::screen_position()) .child(self.render_pad(PadKey::SceneScreen(index), cx)), ) .child(dashed_divider(Hsla::from(theme.editor.line))) .child(self.slider_field( - "Camera Zoom", + tr::editor::panel::scene::camera_zoom(), SliderKey::Panel(PanelSlider::SceneCameraZoom, index), "", cx, )) .child( - ui::Field::stacked(&theme, "Camera Position") + ui::Field::stacked(&theme, tr::editor::panel::scene::camera_position()) .child(self.render_pad(PadKey::SceneCamera(index), cx)), ); } @@ -6535,7 +6723,7 @@ impl EditorWindow { ) -> AnyElement { let theme = self.theme; let id = look.id; - let name = look.name; + let name = (look.name)(); let pose = look.from; div() .id(SharedString::from(format!("c3d-look-{index}-{id}"))) @@ -6672,13 +6860,13 @@ impl EditorWindow { .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_1)) .truncate() - .child(scene.name), + .child((scene.name)()), ) .child( div() .text_size(px(10.)) .text_color(Hsla::from(theme.editor.text_2)) - .child(format!("{shots} shots")), + .child(tr::editor::panel::camera3d::shots(shots)), ) .child(div().flex().flex_row().gap(px(3.)).pt(px(3.)).children( (0..shots).map(|_| { @@ -6698,23 +6886,32 @@ impl EditorWindow { .flex_col() .gap(px(10.)) .child( - self.camera3d_group_label("Look").child( - ui::SegmentedControl::editor( - &theme, - "camera3d-look-tab", - vec![ - ui::SegmentOption::new("Moves", !angles), - ui::SegmentOption::new("Angles", angles), - ], - ) - .text_size(px(11.)) - .item_height(px(21.)) - .item_padding(px(9.), px(0.)) - .on_select(cx.listener(|this, choice: &usize, _, cx| { - this.sidebar.camera3d_angles = *choice == 1; - cx.notify(); - })), - ), + self.camera3d_group_label(tr::editor::panel::camera3d::look_title()) + .child( + ui::SegmentedControl::editor( + &theme, + "camera3d-look-tab", + vec![ + ui::SegmentOption::new( + tr::editor::panel::camera3d::moves(), + !angles, + ), + ui::SegmentOption::new( + tr::editor::panel::camera3d::angles(), + angles, + ), + ], + ) + .text_size(px(11.)) + .item_height(px(21.)) + .item_padding(px(9.), px(0.)) + .on_select(cx.listener( + |this, choice: &usize, _, cx| { + this.sidebar.camera3d_angles = *choice == 1; + cx.notify(); + }, + )), + ), ) .child(grid) .child(sequences) @@ -6804,7 +7001,7 @@ impl EditorWindow { .text_center() .text_size(px(10.)) .text_color(Hsla::from(theme.editor.text_2)) - .child("Drag to orbit"), + .child(tr::editor::panel::camera3d::drag_to_orbit()), ) .on_mouse_down( gpui::MouseButton::Left, @@ -6870,7 +7067,13 @@ impl EditorWindow { .gap(px(8.)) .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_2)) - .child(div().w(px(CAMERA3D_ROW_LABEL)).flex_none().child(label)) + .child( + div() + .w(px(CAMERA3D_ROW_LABEL)) + .flex_none() + .truncate() + .child(label), + ) .child( div() .flex_1() @@ -6930,13 +7133,11 @@ impl EditorWindow { // the hold it is. let dim = still && end; let caption = if dim { - "End \u{b7} same as start".to_string() + tr::editor::panel::camera3d::end_same_as_start().to_string() + } else if end { + tr::editor::panel::camera3d::pose_end(format_time(time)) } else { - format!( - "{} \u{b7} {}", - if end { "End" } else { "Start" }, - format_time(time) - ) + tr::editor::panel::camera3d::pose_start(format_time(time)) }; div() @@ -7053,9 +7254,9 @@ impl EditorWindow { "icons/arrow-left-right.svg", None, if still { - "Nothing to swap on a still shot" + tr::editor::panel::camera3d::nothing_to_swap() } else { - "Swap start and end" + tr::editor::panel::camera3d::swap() }, false, ) @@ -7083,11 +7284,11 @@ impl EditorWindow { self.camera3d_action( "camera3d-still", "icons/pause.svg", - Some("Still shot"), + Some(tr::editor::panel::camera3d::still_shot()), if still { - "Already a still shot" + tr::editor::panel::camera3d::already_still() } else { - "Hold the opening pose for the whole shot" + tr::editor::panel::camera3d::hold_opening() }, still, ) @@ -7122,14 +7323,15 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_2)) - .child("Auto scene"), + .child(tr::editor::panel::camera3d::auto_scene()), ) .child(div().flex_1()) .child(self.render_camera3d_count_pills("panel", count, maximum, cx)) .id("camera3d-auto-row") .tooltip_show_delay(ui::TOOLTIP_SHOW_DELAY) .tooltip(move |_window, cx| { - ui::Tooltip::new(&theme, "Rebuilds every 3D shot on the track").view(cx) + ui::Tooltip::new(&theme, tr::editor::panel::camera3d::auto_scene_tooltip()) + .view(cx) }) .into_any_element(), ) @@ -7207,7 +7409,7 @@ impl EditorWindow { let blur = segment.blur; let blur_on = blur.mode != Camera3DBlurMode::None; let easing_index = motion_easing(segment); - let easing_label = MOTION_EASINGS[easing_index].1; + let easing_label = (MOTION_EASINGS[easing_index].1)(); let duration = segment.end - segment.start; let look = camera3d_shot_label(segment); let advanced = self.sidebar.section(PanelSection::Camera3DAdvanced); @@ -7222,7 +7424,7 @@ impl EditorWindow { .child( ui::EditorButton::plain(&theme, "camera3d-done") .left_icon("icons/check.svg") - .label("Done") + .label(tr::editor::panel::done()) .text_size(px(12.)) .on_click(cx.listener(|this, _, _window, cx| this.set_selection(None, cx))), ) @@ -7233,15 +7435,18 @@ impl EditorWindow { .truncate() .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_2)) - .child(format!("3D shot \u{b7} {look} \u{b7} {duration:.1}s")), + .child(tr::editor::panel::camera3d::header( + look, + format!("{duration:.1}"), + )), ) .child( ui::EditorButton::plain(&theme, "camera3d-play-shot") .left_icon("icons/play.svg") .icon_size(px(12.)) - .label("Play shot") + .label(tr::editor::panel::camera3d::play_shot()) .text_size(px(12.)) - .tooltip(&theme, "Play this shot") + .tooltip(&theme, tr::editor::panel::camera3d::play_shot_tooltip()) .on_click(cx.listener(move |this, _, _window, cx| { this.play_camera3d_shot(index, cx); })), @@ -7251,9 +7456,9 @@ impl EditorWindow { .danger(&theme) .left_icon("icons/trash.svg") .icon_size(px(13.)) - .label("Delete") + .label(tr::editor::panel::delete()) .text_size(px(12.)) - .tooltip(&theme, "Delete this shot") + .tooltip(&theme, tr::editor::panel::camera3d::delete_tooltip()) .on_click(cx.listener(|this, _, window, cx| { this.delete_selection(window, cx); })), @@ -7277,7 +7482,7 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_2)) - .child("Camera"), + .child(tr::editor::panel::camera3d::camera()), ) .child(div().flex_1()) .child( @@ -7285,7 +7490,7 @@ impl EditorWindow { "camera3d-flip-h", "icons/flip-horizontal-2.svg", None, - "Flip horizontally", + tr::editor::panel::flip_horizontally(), false, ) .on_click(cx.listener( @@ -7299,7 +7504,7 @@ impl EditorWindow { "camera3d-flip-v", "icons/flip-vertical-2.svg", None, - "Flip vertically", + tr::editor::panel::flip_vertically(), false, ) .on_click(cx.listener( @@ -7323,7 +7528,7 @@ impl EditorWindow { .flex() .flex_col() .child(self.camera3d_slider_row( - "Distance", + tr::editor::panel::camera3d::distance(), SliderKey::Panel( PanelSlider::Camera3DPose(Camera3DProperty::Zoom), index, @@ -7332,7 +7537,7 @@ impl EditorWindow { cx, )) .child(self.camera3d_slider_row( - "Roll", + tr::editor::panel::camera3d::roll(), SliderKey::Panel( PanelSlider::Camera3DPose(Camera3DProperty::Roll), index, @@ -7341,7 +7546,7 @@ impl EditorWindow { cx, )) .child(self.camera3d_slider_row( - "Shift X", + tr::editor::panel::camera3d::shift_x(), SliderKey::Panel( PanelSlider::Camera3DPose(Camera3DProperty::PanX), index, @@ -7350,7 +7555,7 @@ impl EditorWindow { cx, )) .child(self.camera3d_slider_row( - "Shift Y", + tr::editor::panel::camera3d::shift_y(), SliderKey::Panel( PanelSlider::Camera3DPose(Camera3DProperty::PanY), index, @@ -7363,21 +7568,22 @@ impl EditorWindow { // -- Depth blur ------------------------------------------------------- let mut depth = div().flex().flex_col().gap(px(6.)).child( - self.camera3d_group_label("Depth blur").child( - ui::Toggle::plain( - &theme, - SharedString::from(format!("camera3d-blur-{index}")), - blur_on, - ) - .on_click(cx.listener(move |this, _, window, cx| { - this.toggle_camera3d_blur(index, window, cx); - })), - ), + self.camera3d_group_label(tr::editor::panel::camera3d::depth_blur()) + .child( + ui::Toggle::plain( + &theme, + SharedString::from(format!("camera3d-blur-{index}")), + blur_on, + ) + .on_click(cx.listener(move |this, _, window, cx| { + this.toggle_camera3d_blur(index, window, cx); + })), + ), ); if blur_on { depth = depth .child(self.camera3d_slider_row( - "Amount", + tr::editor::panel::camera3d::amount(), SliderKey::Panel(PanelSlider::Camera3DBlur(Camera3DBlurKey::Strength), index), "int", cx, @@ -7391,7 +7597,13 @@ impl EditorWindow { .gap(px(8.)) .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_2)) - .child(div().w(px(CAMERA3D_ROW_LABEL)).flex_none().child("Focus")) + .child( + div() + .w(px(CAMERA3D_ROW_LABEL)) + .flex_none() + .truncate() + .child(tr::editor::panel::camera3d::focus()), + ) .child( div().flex_1().min_w_0().child( ui::SegmentedControl::editor( @@ -7400,7 +7612,7 @@ impl EditorWindow { CAMERA3D_FOCUS_MODES .iter() .map(|(mode, label)| { - ui::SegmentOption::new(*label, blur.mode == *mode) + ui::SegmentOption::new(label(), blur.mode == *mode) }) .collect(), ) @@ -7423,7 +7635,7 @@ impl EditorWindow { .child(crate::editor_sidebar::disclosure_row( &theme, "camera3d-blur-tune", - "Fine-tune", + tr::editor::panel::camera3d::fine_tune(), tune.is_open(), cx.listener(|this, _, window, cx| { this.sidebar @@ -7443,7 +7655,7 @@ impl EditorWindow { .filter(|(key, _)| *key != Camera3DBlurKey::Strength) .map(|(key, label)| { self.camera3d_slider_row( - label, + label(), SliderKey::Panel(PanelSlider::Camera3DBlur(*key), index), if *key == Camera3DBlurKey::Angle { "deg" @@ -7463,7 +7675,13 @@ impl EditorWindow { .gap(px(8.)) .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_2)) - .child(div().w(px(CAMERA3D_ROW_LABEL)).flex_none().child("Bokeh")) + .child( + div() + .w(px(CAMERA3D_ROW_LABEL)) + .flex_none() + .truncate() + .child(tr::editor::panel::camera3d::bokeh()), + ) .child(div().flex_1()) .child( ui::Toggle::plain( @@ -7502,15 +7720,21 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_1)) - .child("Timing & advanced") + .child( + div() + .min_w_0() + .truncate() + .child(tr::editor::panel::camera3d::timing_advanced()), + ) .child(div().flex_1()) .child( div() + .flex_none() .font_weight(FontWeight::NORMAL) .text_color(Hsla::from(theme.editor.text_2)) - .child(format!( - "{easing_label} \u{b7} Lens {}", - start.fov.round() as i32 + .child(tr::editor::panel::camera3d::timing_summary( + easing_label, + start.fov.round() as i32, )), ) .child( @@ -7540,42 +7764,43 @@ impl EditorWindow { .flex_col() .gap(px(2.)) .child( - ui::Subfield::plain(&theme, "Motion style").child( - div() - .w(px(150.)) - // A still shot has no span to shape and nowhere - // to store a curve (`:5357-5360`). - .when(still, |this| this.opacity(0.5)) - .child(if still { - ui::Select::plain(&theme, "camera3d-easing", easing_label) - .stretch_label() - .disabled(true) - .into_any_element() - } else { - self.easing_select(index, easing_index, cx) - }), - ), + ui::Subfield::plain(&theme, tr::editor::panel::camera3d::motion_style()) + .child( + div() + .w(px(150.)) + // A still shot has no span to shape and nowhere + // to store a curve (`:5357-5360`). + .when(still, |this| this.opacity(0.5)) + .child(if still { + ui::Select::plain(&theme, "camera3d-easing", easing_label) + .stretch_label() + .disabled(true) + .into_any_element() + } else { + self.easing_select(index, easing_index, cx) + }), + ), ) .child(self.camera3d_slider_row( - "Ease in", + tr::editor::panel::camera3d::ease_in(), SliderKey::Panel(PanelSlider::Camera3DTransitionIn, index), "secs", cx, )) .child(self.camera3d_slider_row( - "Ease out", + tr::editor::panel::camera3d::ease_out(), SliderKey::Panel(PanelSlider::Camera3DTransitionOut, index), "secs", cx, )) .child(self.camera3d_slider_row( - "Lens", + tr::editor::panel::camera3d::lens(), SliderKey::Panel(PanelSlider::Camera3DPose(Camera3DProperty::Fov), index), "deg", cx, )) .child(self.camera3d_slider_row( - "Rotate X", + tr::editor::panel::camera3d::rotate_x(), SliderKey::Panel( PanelSlider::Camera3DPose(Camera3DProperty::RotateX), index, @@ -7584,7 +7809,7 @@ impl EditorWindow { cx, )) .child(self.camera3d_slider_row( - "Rotate Y", + tr::editor::panel::camera3d::rotate_y(), SliderKey::Panel( PanelSlider::Camera3DPose(Camera3DProperty::RotateY), index, @@ -7596,7 +7821,7 @@ impl EditorWindow { div().pt(px(4.)).pb(px(4.)).child( ui::EditorButton::plain(&theme, "camera3d-reset") .left_icon("icons/rotate-ccw.svg") - .label("Reset camera") + .label(tr::editor::panel::camera3d::reset_camera()) .on_click(cx.listener(move |this, _, window, cx| { this.reset_camera3d_pose(index, window, cx); })), @@ -7836,7 +8061,7 @@ impl EditorWindow { self.menu_select( SidebarMenu::Camera3DEasing(index), "camera3d-easing", - MOTION_EASINGS[current].1, + (MOTION_EASINGS[current].1)(), cx, ) } @@ -8492,7 +8717,7 @@ mod tests { ); // Every preset is reachable from a chip. assert!( - TEXT_PRESET_GROUPS.contains(&preset.group), + TEXT_PRESET_GROUPS.iter().any(|(id, _)| *id == preset.group), "{} is in {}", preset.id, preset.group @@ -8502,7 +8727,7 @@ mod tests { #[test] fn the_animation_catalogue_is_the_renderers_own_order() { - let labels: Vec<_> = TEXT_ANIMATIONS.iter().map(|(_, label)| *label).collect(); + let labels: Vec<_> = TEXT_ANIMATIONS.iter().map(|(_, label)| label()).collect(); assert_eq!( labels, [ @@ -8907,7 +9132,7 @@ mod tests { Some(Look { kind: LookKind::Move, id: template.id, - name: template.name + name: (template.name)() }), "{} did not match itself", template.id @@ -8921,7 +9146,7 @@ mod tests { Some(Look { kind: LookKind::Angle, id: preset.id, - name: preset.name + name: (preset.name)() }), "{} did not match itself", preset.id @@ -9056,9 +9281,9 @@ pub enum ImageProperty { impl ImageProperty { fn label(self) -> &'static str { match self { - Self::Opacity => "Opacity", - Self::Rotation => "Rotation", - Self::Rounding => "Rounding", + Self::Opacity => tr::editor::panel::opacity(), + Self::Rotation => tr::editor::panel::image::rotation(), + Self::Rounding => tr::editor::panel::image::rounding(), } } fn limits(self) -> (f32, f32, f32) { @@ -9101,7 +9326,7 @@ impl EditorWindow { .flex_col() .gap(px(16.)) .child(self.labelled_small( - "Name", + tr::editor::panel::name(), self.render_field_input(FieldKey::ImageName(index), None), )); panel = panel @@ -9124,13 +9349,13 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .child(svg().path("icons/move.svg").size(px(16.))) - .child("Arrange on canvas"), + .child(tr::editor::panel::image::arrange()), ) .child( div() .text_size(px(12.)) .text_color(Hsla::from(self.theme.gray_10)) - .child("Drag the image to move it. Pull a corner to resize, or use the rotation control below to turn it."), + .child(tr::editor::panel::image::arrange_help()), ) .child( ui::Button::plain( @@ -9140,19 +9365,21 @@ impl EditorWindow { ui::ButtonSize::Sm, ) .icon("icons/move.svg") - .label("Center on canvas") - .on_click(cx.listener(move |this, _, window, cx| { - this.edit_image_segment( - "image-center", - index, - window, - cx, - |segment| { - segment.center = XY::new(0.5, 0.5); - true - }, - ); - })), + .label(tr::editor::panel::image::center()) + .on_click(cx.listener( + move |this, _, window, cx| { + this.edit_image_segment( + "image-center", + index, + window, + cx, + |segment| { + segment.center = XY::new(0.5, 0.5); + true + }, + ); + }, + )), ), ) .child( @@ -9162,7 +9389,7 @@ impl EditorWindow { ui::ButtonVariant::Gray, ui::ButtonSize::Md, ) - .label("Replace image") + .label(tr::editor::panel::image::replace()) .disabled(self.sidebar.picking_image) .on_click(cx.listener(move |this, _, window, cx| { this.replace_timeline_image(index, window, cx) @@ -9174,16 +9401,24 @@ impl EditorWindow { .as_ref() .is_some_and(|(path, present)| path == &segment.path && !present) { - panel = panel.child(div().text_size(px(12.)).child("Image file is missing. Replace it to restore this segment while keeping its timing and transforms.")); + panel = panel.child( + div() + .text_size(px(12.)) + .child(tr::editor::panel::image::missing()), + ); } if let Some(error) = &self.sidebar.image_import_error { panel = panel.child(div().text_size(px(12.)).child(error.clone())); } for (key, label, value) in [ - (0, "Enabled", segment.enabled), - (1, "Lock aspect ratio", segment.lock_aspect), - (2, "Flip horizontally", segment.flip_x), - (3, "Flip vertically", segment.flip_y), + (0, tr::editor::panel::enabled(), segment.enabled), + ( + 1, + tr::editor::panel::image::lock_aspect(), + segment.lock_aspect, + ), + (2, tr::editor::panel::flip_horizontally(), segment.flip_x), + (3, tr::editor::panel::flip_vertically(), segment.flip_y), ] { panel = panel.child( ui::Subfield::plain(&self.theme, label).child( @@ -9239,10 +9474,40 @@ impl EditorWindow { return div().into_any_element(); }; let enabled = segment.enabled; - let mut panel = div().flex().flex_col().gap(px(16.)) - .child(div().text_size(px(12.)).child("Overrides apply only during this segment. Enable a group to copy its global settings.")) - .child(self.labelled_small("Name", self.render_field_input(FieldKey::StyleName(index), None))) - .child(ui::Subfield::plain(&self.theme,"Enabled").child(ui::Toggle::plain(&self.theme,SharedString::from(format!("style-enabled-{index}")),enabled).on_click(cx.listener(move |this,_,window,cx| this.edit_style_segment("style-enabled",index,window,cx,move |segment| { segment.enabled = !enabled; true }))))); + let mut panel = div() + .flex() + .flex_col() + .gap(px(16.)) + .child( + div() + .text_size(px(12.)) + .child(tr::editor::panel::style::help()), + ) + .child(self.labelled_small( + tr::editor::panel::name(), + self.render_field_input(FieldKey::StyleName(index), None), + )) + .child( + ui::Subfield::plain(&self.theme, tr::editor::panel::enabled()).child( + ui::Toggle::plain( + &self.theme, + SharedString::from(format!("style-enabled-{index}")), + enabled, + ) + .on_click(cx.listener(move |this, _, window, cx| { + this.edit_style_segment( + "style-enabled", + index, + window, + cx, + move |segment| { + segment.enabled = !enabled; + true + }, + ) + })), + ), + ); for (group, active) in [ ( StyleGroup::Background, @@ -9313,7 +9578,17 @@ impl EditorWindow { .items_center() .gap(px(6.)) .child(svg().path(group.icon()).size(px(14.))) - .child(format!("Edit {}", group.label())), + .child(match group { + StyleGroup::Background => { + tr::editor::panel::style::edit_background() + } + StyleGroup::Camera => { + tr::editor::panel::style::edit_camera() + } + StyleGroup::Cursor => { + tr::editor::panel::style::edit_cursor() + } + }), ) .on_click(cx.listener(move |this, _, window, cx| { this.open_style_group(index, group, window, cx) @@ -9325,9 +9600,14 @@ impl EditorWindow { let mut crop = div().flex().flex_col().gap(px(8.)).child( div() .text_size(px(12.)) - .child("Screen crop (source pixels)"), + .child(tr::editor::panel::style::crop_title()), ); - for (axis, label) in [(0, "Left"), (1, "Top"), (2, "Width"), (3, "Height")] { + for (axis, label) in [ + (0, tr::editor::panel::style::crop_left()), + (1, tr::editor::panel::style::crop_top()), + (2, tr::editor::panel::style::crop_width()), + (3, tr::editor::panel::style::crop_height()), + ] { crop = crop.child(self.labelled_small( label, self.render_number_field(FieldKey::StyleCrop(index, axis), "px", 80.), @@ -9337,7 +9617,7 @@ impl EditorWindow { div() .id(SharedString::from(format!("style-crop-reset-{index}"))) .cursor_pointer() - .child("Reset crop") + .child(tr::editor::panel::style::reset_crop()) .on_click(cx.listener(move |this, _, window, cx| { this.edit_style_segment( "style-crop-reset", @@ -9359,7 +9639,11 @@ impl EditorWindow { } let padding = segment.overrides.camera_only_padding.is_some(); panel = panel.child( - ui::Subfield::plain(&self.theme, "Camera Only background").child( + ui::Subfield::plain( + &self.theme, + tr::editor::panel::style::camera_only_background(), + ) + .child( ui::Toggle::plain( &self.theme, SharedString::from(format!("style-camera-only-{index}")), @@ -9383,7 +9667,7 @@ impl EditorWindow { panel = panel .child( self.labelled_small( - "Camera Only padding", + tr::editor::panel::style::camera_only_padding(), self.slider( SliderKey::Panel(PanelSlider::StyleCameraOnlyPadding, index), "%", @@ -9392,9 +9676,11 @@ impl EditorWindow { .into_any_element(), ), ) - .child(div().text_size(px(11.)).child( - "Use a Camera Only scene. Padding reveals the background around the camera.", - )); + .child( + div() + .text_size(px(11.)) + .child(tr::editor::panel::style::camera_only_help()), + ); } panel.into_any_element() } diff --git a/apps/desktop-gpui/src/editor_sidebar.rs b/apps/desktop-gpui/src/editor_sidebar.rs index ee004dbae73..4519c854d0f 100644 --- a/apps/desktop-gpui/src/editor_sidebar.rs +++ b/apps/desktop-gpui/src/editor_sidebar.rs @@ -58,6 +58,7 @@ use crate::{ editor_tabs::{AudioSlider, CameraSlider, CaptionSlider, CursorSlider, KeyboardSlider}, editor_timeline::TrackKind, editor_window::EditorWindow, + i18n::tr, library, ui::{self, CollapsibleState, SliderTrack}, }; @@ -138,6 +139,18 @@ pub const BACKGROUND_THEMES: [(&str, &str); 6] = [ ("orange", "Orange"), ]; +/// A [`BACKGROUND_THEMES`] tab's label in the current language. +pub fn background_theme_label(id: &str) -> &'static str { + match id { + "dark" => tr::editor::sidebar::wallpaper_theme::dark(), + "blue" => tr::editor::sidebar::wallpaper_theme::blue(), + "cities" => tr::editor::sidebar::wallpaper_theme::cities(), + "purple" => tr::editor::sidebar::wallpaper_theme::purple(), + "orange" => tr::editor::sidebar::wallpaper_theme::orange(), + _ => "macOS", + } +} + /// `BACKGROUND_COLORS` (`:259-277`). The last one is transparent and renders as /// a checkerboard rather than a swatch. pub const BACKGROUND_COLORS: [&str; 17] = [ @@ -266,8 +279,20 @@ impl SidebarTab { } } - /// The tab's own name. Used by the timeline's own logging and by the tests - /// that pin the rail's order; the rail itself is icon-only. + /// What the rail's tooltip calls the tab, in the current language. + pub fn title(self) -> &'static str { + match self { + Self::Background => tr::editor::sidebar::tab::background(), + Self::Camera => tr::editor::sidebar::tab::camera(), + Self::Audio => tr::editor::sidebar::tab::audio(), + Self::Cursor => tr::editor::sidebar::tab::cursor(), + Self::Keyboard => tr::editor::sidebar::tab::keyboard(), + Self::Captions => tr::editor::sidebar::tab::captions(), + } + } + + /// The tab's own English name, which `CAP_GPUI_AUTO_SIDEBAR` matches + /// and the tests that pin the rail's order read. #[allow(dead_code)] pub fn label(self) -> &'static str { match self { @@ -310,13 +335,13 @@ impl SourceTab { pub fn label(self) -> &'static str { match self { - Self::Desktop => "Desktop", - Self::Wallpaper => "Wallpaper", - Self::Image => "Image", - Self::Color => "Color", - Self::Gradient => "Gradient", - Self::AnimatedGradient => "Animated", - Self::None => "None", + Self::Desktop => tr::editor::sidebar::source::desktop(), + Self::Wallpaper => tr::editor::sidebar::source::wallpaper(), + Self::Image => tr::editor::sidebar::source::image(), + Self::Color => tr::editor::sidebar::source::color(), + Self::Gradient => tr::editor::sidebar::source::gradient(), + Self::AnimatedGradient => tr::editor::sidebar::source::animated(), + Self::None => tr::editor::sidebar::source::none(), } } } @@ -2437,7 +2462,10 @@ impl EditorWindow { self.sidebar.picker_task = Some(cx.spawn_in(window, async move |this, cx| { #[cfg(target_os = "linux")] let picked = crate::platform::open_file_panel_async( - &[("Image", &BACKGROUND_IMAGE_EXTENSIONS[..])], + &[( + tr::editor::sidebar::source::image(), + &BACKGROUND_IMAGE_EXTENSIONS[..], + )], None, cx, ) @@ -2516,7 +2544,11 @@ impl EditorWindow { } /// `importDesktopBackground` (`:2033-2052`). - pub(crate) fn import_desktop_background(&mut self, window: &mut Window, cx: &mut Context) { + pub(crate) fn import_desktop_background( + &mut self, + window: &mut Window, + cx: &mut Context, + ) { if self.sidebar.importing_desktop { return; } @@ -2550,7 +2582,7 @@ impl EditorWindow { tracing::error!("couldn't import your desktop wallpaper: {error}"); cx.spawn(async move |_, _| { crate::platform::alert_dialog( - "Couldn't import your desktop wallpaper", + tr::editor::sidebar::desktop_import_failed(), &error, ); }) @@ -2601,7 +2633,7 @@ impl EditorWindow { // the indicator is hidden (`:586-592, 667-677`). ui::TabRailItem::new( tab.icon(), - tab.label(), + tab.title(), selection.is_none() && tab == selected_tab, disabled, ) @@ -2642,11 +2674,11 @@ impl EditorWindow { .text_color(Hsla::from(theme.editor.text_1)) .child(error.clone()) })) - .children( - self.sidebar - .picking_image - .then(|| div().p(px(12.)).child("Importing image…")), - ) + .children(self.sidebar.picking_image.then(|| { + div() + .p(px(12.)) + .child(tr::editor::sidebar::importing_image()) + })) .child(if let Some((index, group)) = self.sidebar.style_target { self.render_style_group(index, group, cx) } else if self.audio_picker.is_some() { @@ -2729,18 +2761,18 @@ impl EditorWindow { .child(self.render_source_field(cx)) .child(dashed_divider(Hsla::from(theme.editor.line))) .child( - ui::Field::section(&theme, "Layout").child( + ui::Field::section(&theme, tr::editor::sidebar::layout::title()).child( div() .flex() .flex_col() .child(self.slider_field( - "Background Blur", + tr::editor::sidebar::layout::background_blur(), SliderKey::Bg(BgSlider::Blur), "%", cx, )) .child(self.slider_field( - "Padding", + tr::editor::sidebar::layout::padding(), SliderKey::Bg(BgSlider::Padding), "%", cx, @@ -2756,14 +2788,16 @@ impl EditorWindow { .h(px(34.)) .child( div() + .min_w_0() + .truncate() .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_2)) - .child("Custom screen position (dragged on canvas)"), + .child(tr::editor::sidebar::layout::custom_position()), ) .child(section_action( &theme, "reset-display-position", - "Reset", + tr::common::reset(), None, false, cx.listener(|this, _, window, cx| { @@ -2781,7 +2815,7 @@ impl EditorWindow { .into_any_element() })) .child(self.slider_field( - "Rounded Corners", + tr::editor::sidebar::layout::rounded_corners(), SliderKey::Bg(BgSlider::Rounding), "%", cx, @@ -2789,7 +2823,7 @@ impl EditorWindow { .child(self.render_corner_style(cx)) .children(self.sidebar.style_target.is_none().then(|| { self.slider_field( - "Motion Blur", + tr::editor::sidebar::layout::motion_blur(), SliderKey::Bg(BgSlider::MotionBlur), "x100%", cx, @@ -2824,7 +2858,7 @@ impl EditorWindow { .map(|item| ui::SegmentOption::new(item.label(), tab == item)) .collect(); - ui::Field::section(&theme, "Background") + ui::Field::section(&theme, tr::editor::sidebar::background::title()) .value(section_action( &theme, "source-none", @@ -2875,9 +2909,9 @@ impl EditorWindow { let theme = self.theme; let importing = self.sidebar.importing_desktop; let label = if importing { - "Importing..." + tr::editor::sidebar::desktop::importing() } else { - "Import desktop background" + tr::editor::sidebar::desktop::import() }; match self.sidebar.desktop_background.clone() { @@ -2902,7 +2936,7 @@ impl EditorWindow { div() .text_size(px(13.)) .text_color(Hsla::from(theme.editor.text_1)) - .child("Use the wallpaper from your desktop"), + .child(tr::editor::sidebar::desktop::prompt()), ) .child( ui::EditorButton::plain(&theme, "import-desktop") @@ -2964,9 +2998,9 @@ impl EditorWindow { .left_icon("icons/monitor-outline.svg") .icon_size(px(16.)) .label(if importing { - "Importing..." + tr::editor::sidebar::desktop::importing() } else { - "Re-import" + tr::editor::sidebar::desktop::reimport() }) .disabled(importing) .on_click(cx.listener(|this, _, window, cx| { @@ -3020,7 +3054,7 @@ impl EditorWindow { BACKGROUND_THEMES .iter() .enumerate() - .map(|(index, (_, label))| { + .map(|(index, (id, _))| { let selected = index == selected_theme; div() .id(SharedString::from(format!("wallpaper-theme-{index}"))) @@ -3041,7 +3075,7 @@ impl EditorWindow { .cursor_pointer() .hover(|this| this.bg(Hsla::from(theme.editor.ctl))) }) - .child(*label) + .child(background_theme_label(id)) .on_click(cx.listener(move |this, _, window, cx| { this.sidebar.wallpaper_theme = index; this.ensure_wallpapers(window, cx); @@ -3134,7 +3168,7 @@ impl EditorWindow { .child( div() .text_color(Hsla::from(theme.editor.text_1)) - .child("Click to select or drag and drop image"), + .child(tr::editor::sidebar::image::prompt()), ) .on_click(cx.listener(|this, _, window, cx| { this.pick_background_image(window, cx); @@ -3378,14 +3412,14 @@ impl EditorWindow { .items_end() .child(self.render_gradient_stop( "gradient-from", - "From", + tr::editor::sidebar::gradient::from(), ColorTarget::GradientFrom, from, cx, )) .child(self.render_gradient_stop( "gradient-to", - "To", + tr::editor::sidebar::gradient::to(), ColorTarget::GradientTo, to, cx, @@ -3397,13 +3431,13 @@ impl EditorWindow { .flex() .flex_col() .child(self.slider_field( - "Angle", + tr::editor::sidebar::gradient::angle(), SliderKey::Bg(BgSlider::GradientAngle), "deg", cx, )) .child(self.slider_field( - "Noise", + tr::editor::sidebar::gradient::noise(), SliderKey::Bg(BgSlider::GradientNoise), "%", cx, @@ -3411,7 +3445,7 @@ impl EditorWindow { // Grain Scale appears only while noise is on (`:204-221`). .children((noise > 0.).then(|| { self.slider_field( - "Grain Scale", + tr::editor::sidebar::gradient::grain_scale(), SliderKey::Bg(BgSlider::GradientGrain), "%", cx, @@ -3556,16 +3590,18 @@ impl EditorWindow { let theme = self.theme; let current = self.project.background.rounding_type; let label = match current { - CornerStyle::Squircle => "Squircle", - CornerStyle::Rounded => "Rounded", + CornerStyle::Squircle => tr::editor::sidebar::corner::squircle(), + CornerStyle::Rounded => tr::editor::sidebar::corner::rounded(), }; - ui::Field::inline(&theme, "Corner Style").child(div().w(px(150.)).child(self.menu_select( - crate::editor_tabs::SidebarMenu::BackgroundCornerStyle, - "corner-style", - label, - cx, - ))) + ui::Field::inline(&theme, tr::editor::sidebar::corner::title()).child( + div().w(px(150.)).child(self.menu_select( + crate::editor_tabs::SidebarMenu::BackgroundCornerStyle, + "corner-style", + label, + cx, + )), + ) } fn render_border_field(&self, cx: &mut Context) -> impl IntoElement { @@ -3578,7 +3614,7 @@ impl EditorWindow { .flex() .flex_col() .child( - ui::Field::inline(&theme, "Border").value( + ui::Field::inline(&theme, tr::editor::sidebar::border::title()).value( ui::Toggle::plain(&theme, "border-enabled", enabled) .on_click(cx.listener(move |this, _, window, cx| { let next = !enabled; @@ -3612,21 +3648,23 @@ impl EditorWindow { .flex() .flex_col() .child(self.slider_field( - "Border Width", + tr::editor::sidebar::border::width(), SliderKey::Bg(BgSlider::BorderWidth), "px", cx, )) .child( - ui::Field::stacked(&theme, "Border Color").child(self.render_rgb_input( - "border-color", - ColorTarget::BorderColor, - color, - cx, - )), + ui::Field::stacked(&theme, tr::editor::sidebar::border::color()).child( + self.render_rgb_input( + "border-color", + ColorTarget::BorderColor, + color, + cx, + ), + ), ) .child(self.slider_field( - "Border Opacity", + tr::editor::sidebar::border::opacity(), SliderKey::Bg(BgSlider::BorderOpacity), "%", cx, @@ -3648,7 +3686,7 @@ impl EditorWindow { .flex() .flex_col() .child( - ui::Field::inline(&theme, "MacBook notch").value( + ui::Field::inline(&theme, tr::editor::sidebar::notch::title()).value( ui::Toggle::plain(&theme, "notch-enabled", enabled) .on_click(cx.listener(move |this, _, window, cx| { let next = !enabled; @@ -3685,26 +3723,22 @@ impl EditorWindow { .pt(px(4.)) .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_2)) - .child( - "Draws a MacBook notch over the recording. Recordings made \ - on a Mac with a notch use their own measurements; \ - otherwise start from the size below and adjust to match.", - ), + .child(tr::editor::sidebar::notch::description()), ) .child(self.slider_field( - "Notch Width", + tr::editor::sidebar::notch::width(), SliderKey::Bg(BgSlider::NotchWidth), "pct", cx, )) .child(self.slider_field( - "Notch Height", + tr::editor::sidebar::notch::height(), SliderKey::Bg(BgSlider::NotchHeight), "pct", cx, )) .child(self.slider_field( - "Notch Position", + tr::editor::sidebar::notch::position(), SliderKey::Bg(BgSlider::NotchX), "pct", cx, @@ -3720,7 +3754,12 @@ impl EditorWindow { div() .flex() .flex_col() - .child(self.slider_field("Shadow", SliderKey::Bg(BgSlider::Shadow), "%", cx)) + .child(self.slider_field( + tr::editor::sidebar::shadow::title(), + SliderKey::Bg(BgSlider::Shadow), + "%", + cx, + )) // `ShadowSettings` (`ShadowSettings.tsx:37-86`): its own trigger // row with a rotating chevron, over three sliders. .child( @@ -3730,7 +3769,7 @@ impl EditorWindow { .child(disclosure_row( &theme, "shadow-advanced", - "Advanced shadow settings", + tr::editor::sidebar::shadow::advanced(), open, cx.listener(|this, _, window, cx| { this.sidebar.shadow_open.toggle(); @@ -3743,19 +3782,19 @@ impl EditorWindow { .flex() .flex_col() .child(self.slider_field( - "Size", + tr::editor::sidebar::shadow::shadow_size(), SliderKey::Bg(BgSlider::ShadowSize), "", cx, )) .child(self.slider_field( - "Opacity", + tr::editor::sidebar::shadow::opacity(), SliderKey::Bg(BgSlider::ShadowOpacity), "", cx, )) .child(self.slider_field( - "Blur", + tr::editor::sidebar::shadow::blur(), SliderKey::Bg(BgSlider::ShadowBlur), "", cx, @@ -4483,9 +4522,9 @@ pub(crate) enum StyleGroup { impl StyleGroup { pub(crate) fn label(self) -> &'static str { match self { - Self::Background => "Background & frame", - Self::Camera => "Camera", - Self::Cursor => "Cursor", + Self::Background => tr::editor::sidebar::style_group::background(), + Self::Camera => tr::editor::sidebar::style_group::camera(), + Self::Cursor => tr::editor::sidebar::style_group::cursor(), } } @@ -4688,12 +4727,15 @@ impl EditorWindow { .gap(px(8.)) .font_weight(gpui::FontWeight::MEDIUM) .child(svg().path(group.icon()).size(px(16.))) - .child(format!("Style {} · {}", index + 1, group.label())), + .child(tr::editor::sidebar::style_group::title( + index + 1, + group.label(), + )), ) .child( div() .text_size(px(11.)) - .child("Editing this segment only. Global settings are unchanged."), + .child(tr::editor::sidebar::style_group::editing_note()), ) .child( div() @@ -4707,7 +4749,7 @@ impl EditorWindow { .items_center() .gap(px(4.)) .child(svg().path("icons/arrow-left.svg").size(px(14.))) - .child("Back to Style"), + .child(tr::editor::sidebar::style_group::back()), ) .on_click(cx.listener(|this, _, window, cx| { this.end_field_edit(cx); diff --git a/apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs b/apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs index eeda7203a0d..83c15a10b2c 100644 --- a/apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs +++ b/apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs @@ -107,6 +107,79 @@ impl AnimatedGradientState { } } +/// A control's label in the current language. The catalog's own label is the +/// English one the Tauri app shares. +fn control_label(control: &AnimatedGradientControl) -> SharedString { + SharedString::new_static(match control.key { + AnimatedGradientParameter::Direction => { + tr::editor::sidebar::animated_gradient::control::direction() + } + AnimatedGradientParameter::FlowScale => { + tr::editor::sidebar::animated_gradient::control::flow_scale() + } + AnimatedGradientParameter::FlowStrength => { + tr::editor::sidebar::animated_gradient::control::flow_strength() + } + AnimatedGradientParameter::Curvature => { + tr::editor::sidebar::animated_gradient::control::curvature() + } + AnimatedGradientParameter::Detail => { + tr::editor::sidebar::animated_gradient::control::detail() + } + AnimatedGradientParameter::Relief => { + tr::editor::sidebar::animated_gradient::control::relief() + } + AnimatedGradientParameter::Light => { + tr::editor::sidebar::animated_gradient::control::highlights() + } + AnimatedGradientParameter::Shade => { + tr::editor::sidebar::animated_gradient::control::shading() + } + AnimatedGradientParameter::Ripples => { + tr::editor::sidebar::animated_gradient::control::ripple_size() + } + AnimatedGradientParameter::GrainAmount => { + tr::editor::sidebar::animated_gradient::control::grain_amount() + } + AnimatedGradientParameter::GrainSize => { + tr::editor::sidebar::animated_gradient::control::grain_size() + } + AnimatedGradientParameter::Exposure => { + tr::editor::sidebar::animated_gradient::control::exposure() + } + AnimatedGradientParameter::Contrast => { + tr::editor::sidebar::animated_gradient::control::contrast() + } + AnimatedGradientParameter::Vibrance => { + tr::editor::sidebar::animated_gradient::control::vibrance() + } + AnimatedGradientParameter::MotionSpeed => { + tr::editor::sidebar::animated_gradient::control::motion_speed() + } + }) +} + +/// A control group's tab label in the current language, by the catalog's +/// English group name. +fn group_label(name: &str) -> SharedString { + match name { + "Flow" => SharedString::new_static(tr::editor::sidebar::animated_gradient::group::flow()), + "Lighting" => { + SharedString::new_static(tr::editor::sidebar::animated_gradient::group::lighting()) + } + "Texture" => { + SharedString::new_static(tr::editor::sidebar::animated_gradient::group::texture()) + } + "Colour" => { + SharedString::new_static(tr::editor::sidebar::animated_gradient::group::colour()) + } + "Animation" => { + SharedString::new_static(tr::editor::sidebar::animated_gradient::group::animation()) + } + _ => SharedString::from(name.to_string()), + } +} + fn control_groups(catalog: &AnimatedGradientCatalog) -> Vec<(&str, Vec<&AnimatedGradientControl>)> { let mut groups: Vec<(&str, Vec<&AnimatedGradientControl>)> = Vec::new(); for control in &catalog.controls { @@ -238,7 +311,9 @@ fn slider_unit(key: AnimatedGradientParameter) -> &'static str { fn format_control_value(control: &AnimatedGradientControl, value: f32) -> String { match control.key { AnimatedGradientParameter::Direction => format!("{}\u{b0}", value.round() as i32), - AnimatedGradientParameter::MotionSpeed if value == 0. => "Still".into(), + AnimatedGradientParameter::MotionSpeed if value == 0. => { + tr::editor::sidebar::animated_gradient::still().into() + } _ if control.step < 1. => format!("{value:.1}"), _ => format!("{}", value.round() as i32), } @@ -453,7 +528,7 @@ impl EditorWindow { } let input = cx.new(|cx| { let mut input = ui::TextInputState::single_line(window, cx); - input.set_placeholder("Name this gradient"); + input.set_placeholder(tr::editor::sidebar::animated_gradient::name_placeholder()); input }); self.push_text_subscription(cx.subscribe_in( @@ -692,9 +767,9 @@ impl EditorWindow { let mut library = read_library(); if library.save_preset(&name, &config).is_none() { self.sidebar.animated_gradient.error = Some(if name.trim().is_empty() { - "Enter a name for this gradient.".to_string() + tr::editor::sidebar::animated_gradient::name_required().to_string() } else { - format!("You can save up to {MAX_SAVED} gradients. Delete one to add another.") + tr::editor::sidebar::animated_gradient::limit_reached(MAX_SAVED) }); } else if write_setting("presets", &library.presets) { self.sidebar.animated_gradient.library.presets = library.presets; @@ -702,7 +777,7 @@ impl EditorWindow { self.close_animated_gradient_save(cx); } else { self.sidebar.animated_gradient.error = - Some("Could not save this gradient. Try again.".into()); + Some(tr::editor::sidebar::animated_gradient::save_failed().into()); } cx.notify(); } @@ -715,7 +790,7 @@ impl EditorWindow { self.sidebar.animated_gradient.error = None; } else { self.sidebar.animated_gradient.error = - Some("Could not delete this gradient. Try again.".into()); + Some(tr::editor::sidebar::animated_gradient::delete_failed().into()); } cx.notify(); } @@ -729,7 +804,7 @@ impl EditorWindow { let error = state.error.clone().or_else(|| { state .persistence_failed - .then(|| "Could not remember your animated gradient settings.".to_string()) + .then(|| tr::editor::sidebar::animated_gradient::remember_failed().to_string()) }); div() @@ -772,7 +847,10 @@ impl EditorWindow { .text_color(Hsla::from(theme.editor.text_3)) .cursor_pointer() .hover(|this| this.bg(Hsla::from(theme.editor.ctl_hover))) - .tooltip(move |_window, cx| ui::Tooltip::new(&theme, "Randomize").view(cx)) + .tooltip(move |_window, cx| { + ui::Tooltip::new(&theme, tr::editor::sidebar::animated_gradient::randomize()) + .view(cx) + }) .child( svg() .path("icons/shuffle.svg") @@ -800,6 +878,12 @@ impl EditorWindow { let save_row = state.save_open.then(|| { let input = state.name_input.clone(); + if let Some(input) = &input { + input.update(cx, |input, _| { + input + .set_placeholder(tr::editor::sidebar::animated_gradient::name_placeholder()) + }); + } let name_empty = input .as_ref() .is_none_or(|input| input.read(cx).text().trim().is_empty()); @@ -833,13 +917,13 @@ impl EditorWindow { this.focus_root(window, cx); })) }) - .child("Save"), + .child(tr::common::save()), ) .child(icon_button( &theme, "animated-gradient-save-cancel", "icons/x.svg", - "Cancel", + tr::common::cancel(), false, cx.listener(|this, _, window, cx| { this.close_animated_gradient_save(cx); @@ -849,14 +933,14 @@ impl EditorWindow { .into_any_element() }); - ui::Field::section(&theme, "Presets") + ui::Field::section(&theme, tr::editor::sidebar::animated_gradient::presets()) .value( header_button( &theme, "animated-gradient-save-toggle", "icons/save.svg", - "Save", - "Save the current gradient", + tr::common::save(), + tr::editor::sidebar::animated_gradient::save_tooltip(), save_disabled, save_open, cx.listener(move |this, _, window, cx| { @@ -882,7 +966,7 @@ impl EditorWindow { div() .text_size(px(11.)) .text_color(Hsla::from(theme.editor.text_3)) - .child("Saved"), + .child(tr::editor::sidebar::animated_gradient::saved()), ) .child(swatch_rows(saved)) }), @@ -938,7 +1022,13 @@ impl EditorWindow { .shadow_sm() .cursor_pointer() .hover(|this| this.bg(Hsla::from(theme.red_11))) - .tooltip(move |_window, cx| ui::Tooltip::new(&theme, "Delete").view(cx)) + .tooltip(move |_window, cx| { + ui::Tooltip::new( + &theme, + tr::editor::sidebar::animated_gradient::delete(), + ) + .view(cx) + }) .child( svg() .path("icons/x.svg") @@ -1074,7 +1164,7 @@ impl EditorWindow { &theme, "animated-gradient-remove-stop", "icons/trash.svg", - "Remove colour", + tr::editor::sidebar::animated_gradient::remove_colour(), count <= MIN_STOPS, cx.listener(move |this, _, window, cx| { this.remove_animated_gradient_stop(selected, window, cx); @@ -1082,14 +1172,14 @@ impl EditorWindow { )) }); - ui::Field::section(&theme, "Colours") + ui::Field::section(&theme, tr::editor::sidebar::animated_gradient::colours()) .value( header_button( &theme, "animated-gradient-add-stop", "icons/plus.svg", - "Add", - "Add a colour", + tr::common::add(), + tr::editor::sidebar::animated_gradient::add_colour(), count >= MAX_STOPS, false, cx.listener(|this, _, window, cx| { @@ -1117,7 +1207,7 @@ impl EditorWindow { let theme = self.theme; let control = AnimatedGradientParameter::MotionSpeed.control(); let value = AnimatedGradientParameter::MotionSpeed.get(config); - ui::Field::inline(&theme, "Motion") + ui::Field::inline(&theme, tr::editor::sidebar::animated_gradient::motion()) .value_text(format_control_value(&control, value)) .child(self.slider_flex( SliderKey::AnimatedGradient(AnimatedGradientParameter::MotionSpeed), @@ -1144,7 +1234,9 @@ impl EditorWindow { groups .iter() .enumerate() - .map(|(index, (name, _))| ui::SegmentOption::new(name.to_string(), index == active)) + .map(|(index, (name, _))| { + ui::SegmentOption::new(group_label(name), index == active) + }) .collect(), ) .item_padding(px(6.), px(4.)) @@ -1160,7 +1252,7 @@ impl EditorWindow { .iter() .map(|control| { let value = control.key.get(config); - ui::Field::inline(&theme, control.label.clone()) + ui::Field::inline(&theme, control_label(control)) .value_text(format_control_value(control, value)) .child(self.slider_flex( SliderKey::AnimatedGradient(control.key), @@ -1181,7 +1273,7 @@ impl EditorWindow { .child(div().flex().flex_1().child(disclosure_row( &theme, "animated-gradient-fine-tune", - "Fine-tune", + tr::editor::sidebar::animated_gradient::fine_tune(), open, cx.listener(|this, _, window, cx| { this.sidebar.animated_gradient.fine_tune_open.toggle(); @@ -1193,8 +1285,8 @@ impl EditorWindow { &theme, "animated-gradient-reset", "icons/rotate-ccw.svg", - "Reset", - "Reset fine-tune settings", + tr::common::reset(), + tr::editor::sidebar::animated_gradient::reset_tooltip(), false, false, cx.listener(|this, _, window, cx| { diff --git a/apps/desktop-gpui/src/editor_sidebar/cursor.rs b/apps/desktop-gpui/src/editor_sidebar/cursor.rs index beac97dc78d..910310e9b18 100644 --- a/apps/desktop-gpui/src/editor_sidebar/cursor.rs +++ b/apps/desktop-gpui/src/editor_sidebar/cursor.rs @@ -21,11 +21,11 @@ enum CursorCard { impl CursorCard { fn label(self) -> &'static str { match self { - Self::Default => "Default", + Self::Default => tr::editor::sidebar::cursor::default(), Self::Family(CursorFamily::MacOS) => "macOS", Self::Family(CursorFamily::MacOSTahoe) => "macOS Tahoe", Self::Family(CursorFamily::Windows) => "Windows", - Self::Circle => "Circle", + Self::Circle => tr::editor::sidebar::cursor::circle(), } } @@ -201,12 +201,16 @@ impl EditorWindow { ARROW_BOX, ), ) - .child(div().text_size(px(12.)).child("Default")) + .child( + div() + .text_size(px(12.)) + .child(tr::editor::sidebar::cursor::default()), + ) .child( div() .text_size(px(11.)) .text_color(Hsla::from(theme.gray_11)) - .child("Recorded cursors"), + .child(tr::editor::sidebar::cursor::recorded()), ) .into_any_element(), CursorCard::Family(family) => self.cursor_art(family.arrow(), ARROW_BOX), @@ -237,7 +241,8 @@ impl EditorWindow { }) .when(recorded, |this| { this.tooltip(move |_window, cx| { - ui::Tooltip::new(&theme, "Recorded with this cursor").view(cx) + ui::Tooltip::new(&theme, tr::editor::sidebar::cursor::recorded_tooltip()) + .view(cx) }) }) .child(art) @@ -307,17 +312,17 @@ impl EditorWindow { let recorded = self.recorded_cursor_family; let description = match selected { - CursorCard::Default => "Keeps the cursor shapes and appearance from your recording.", + CursorCard::Default => tr::editor::sidebar::cursor::description_default(), CursorCard::Family(CursorFamily::MacOS) => { - "Classic macOS appearance. Custom cursors keep their recorded shape." + tr::editor::sidebar::cursor::description_macos() } CursorCard::Family(CursorFamily::MacOSTahoe) => { - "macOS Tahoe appearance. Custom cursors keep their recorded shape." + tr::editor::sidebar::cursor::description_tahoe() } CursorCard::Family(CursorFamily::Windows) => { - "Windows appearance. Custom cursors keep their recorded shape." + tr::editor::sidebar::cursor::description_windows() } - CursorCard::Circle => "Replaces all cursor shapes with a circle.", + CursorCard::Circle => tr::editor::sidebar::cursor::description_circle(), }; div() .flex() @@ -357,7 +362,7 @@ impl EditorWindow { .flex() .flex_col() .child( - ui::Field::inline(&theme, "Click Ripple").value( + ui::Field::inline(&theme, tr::editor::sidebar::cursor::click_ripple()).value( ui::Toggle::plain(&theme, "cursor-ripple", enabled) .on_click(cx.listener(move |this, _, window, cx| { let next = !this.style_control_project().cursor.ripple.enabled; @@ -379,27 +384,29 @@ impl EditorWindow { .pt(px(4.)) .pb(px(8.)) .child( - ui::Subfield::plain(&theme, "Color").child(self.render_rgb_input( - "cursor-ripple-color", - ColorTarget::CursorRipple, - color, - cx, - )), + ui::Subfield::plain(&theme, tr::editor::sidebar::cursor::color()).child( + self.render_rgb_input( + "cursor-ripple-color", + ColorTarget::CursorRipple, + color, + cx, + ), + ), ) .child(self.slider_field( - "Strength", + tr::editor::sidebar::cursor::strength(), SliderKey::Cursor(CursorSlider::RippleStrength), "%", cx, )) .child(self.slider_field( - "Size", + tr::editor::sidebar::cursor::ripple_size(), SliderKey::Cursor(CursorSlider::RippleSize), "%", cx, )) .child(self.slider_field( - "Duration", + tr::editor::sidebar::cursor::duration(), SliderKey::Cursor(CursorSlider::RippleDuration), "secs", cx, diff --git a/apps/desktop-gpui/src/i18n/editor_panels_surface_tests.rs b/apps/desktop-gpui/src/i18n/editor_panels_surface_tests.rs new file mode 100644 index 00000000000..48f60c9cdc5 --- /dev/null +++ b/apps/desktop-gpui/src/i18n/editor_panels_surface_tests.rs @@ -0,0 +1,944 @@ +use super::*; + +pub(super) fn is_editor_panels_surface_key(key: &str) -> bool { + [ + "editor.panel.", + "editor.sidebar.", + "editor.color.", + "editor.audio.", + "editor.crop.", + ] + .iter() + .any(|prefix| key.starts_with(prefix)) +} + +const FORMATTED: &[&str] = &[ + "editor.panel.zoom.title", + "editor.panel.font_weight.custom", + "editor.panel.text_segment.title", + "editor.panel.caption.title", + "editor.panel.keyboard.title", + "editor.panel.audio.title", + "editor.panel.mask.title", + "editor.panel.camera3d.shots", + "editor.panel.camera3d.pose_start", + "editor.panel.camera3d.pose_end", + "editor.panel.camera3d.header", + "editor.panel.camera3d.timing_summary", + "editor.sidebar.style_group.title", + "editor.sidebar.animated_gradient.limit_reached", + "editor.crop.hint", + "editor.crop.editing_style", + "editor.crop.editing_style_override", +]; + +#[test] +fn editor_panels_match_the_literals_they_replaced() { + let pairs: &[(&str, &str)] = &[ + (tr::editor::panel::done(), "Done"), + (tr::editor::panel::delete(), "Delete"), + (tr::editor::panel::enabled(), "Enabled"), + (tr::editor::panel::name(), "Name"), + (tr::editor::panel::duration(), "Duration"), + (tr::editor::panel::fade_duration(), "Fade Duration"), + (tr::editor::panel::flip_horizontally(), "Flip horizontally"), + (tr::editor::panel::flip_vertically(), "Flip vertically"), + (tr::editor::panel::opacity(), "Opacity"), + (tr::editor::panel::edge_in(), "In"), + (tr::editor::panel::edge_out(), "Out"), + (tr::editor::panel::zoom::mode(), "Zoom Mode"), + (tr::editor::panel::zoom::amount(), "Zoom Amount"), + (tr::editor::panel::zoom::mixed(), "Mixed"), + ( + tr::editor::panel::zoom::positions_mixed(), + "Segments zoom into different spots. Drag to move them all to the same one.", + ), + (tr::editor::panel::zoom::auto(), "Auto"), + (tr::editor::panel::zoom::manual(), "Manual"), + ( + tr::editor::panel::zoom::auto_locked(), + "Auto mode needs cursor capture. Enable \"Custom cursor capture (Studio)\" in Settings → General.", + ), + (tr::editor::panel::zoom::how_it_works(), "How does it work?"), + ( + tr::editor::panel::zoom::manual_help(), + "Manual zoom stays on a fixed spot you pick below.", + ), + ( + tr::editor::panel::zoom::auto_help(), + "Automatic zoom follows your cursor around the screen.", + ), + (tr::editor::panel::font_weight::light(), "Light"), + (tr::editor::panel::font_weight::regular(), "Regular"), + (tr::editor::panel::font_weight::medium(), "Medium"), + (tr::editor::panel::font_weight::semibold(), "Semibold"), + (tr::editor::panel::font_weight::bold(), "Bold"), + (tr::editor::panel::font_weight::extra_bold(), "Extra Bold"), + (tr::editor::panel::font_weight::black(), "Black"), + (tr::editor::panel::text_animation::none(), "None"), + (tr::editor::panel::text_animation::fade(), "Fade"), + (tr::editor::panel::text_animation::slide_up(), "Slide up"), + ( + tr::editor::panel::text_animation::slide_down(), + "Slide down", + ), + ( + tr::editor::panel::text_animation::slide_left(), + "Slide left", + ), + ( + tr::editor::panel::text_animation::slide_right(), + "Slide right", + ), + (tr::editor::panel::text_animation::pop(), "Pop"), + (tr::editor::panel::text_animation::zoom(), "Zoom"), + (tr::editor::panel::text_animation::bounce(), "Bounce"), + (tr::editor::panel::text_animation::wipe(), "Wipe"), + (tr::editor::panel::text_animation::words(), "Words"), + (tr::editor::panel::text_animation::letters(), "Letters"), + (tr::editor::panel::text_animation::tracking(), "Tracking"), + ( + tr::editor::panel::text_animation::typewriter(), + "Typewriter", + ), + (tr::editor::panel::text_background::none(), "None"), + (tr::editor::panel::text_background::box_shape(), "Box"), + (tr::editor::panel::text_background::pill(), "Pill"), + (tr::editor::panel::text_background::highlight(), "Highlight"), + (tr::editor::panel::text_layout::overlay(), "Overlay"), + (tr::editor::panel::text_layout::fullscreen(), "Fullscreen"), + (tr::editor::panel::text_preset::group::all(), "All"), + (tr::editor::panel::text_preset::group::titles(), "Titles"), + ( + tr::editor::panel::text_preset::group::lower_thirds(), + "Lower thirds", + ), + ( + tr::editor::panel::text_preset::group::callouts(), + "Callouts", + ), + ( + tr::editor::panel::text_preset::group::statements(), + "Statements", + ), + (tr::editor::panel::text_preset::group::code(), "Code"), + (tr::editor::panel::text_preset::name::title(), "Title"), + (tr::editor::panel::text_preset::name::headline(), "Headline"), + ( + tr::editor::panel::text_preset::name::cinematic(), + "Cinematic", + ), + (tr::editor::panel::text_preset::name::gradient(), "Gradient"), + ( + tr::editor::panel::text_preset::name::lower_third(), + "Lower third", + ), + (tr::editor::panel::text_preset::name::name_tag(), "Name tag"), + (tr::editor::panel::text_preset::name::caption(), "Caption"), + (tr::editor::panel::text_preset::name::kicker(), "Kicker"), + (tr::editor::panel::text_preset::name::label(), "Label"), + ( + tr::editor::panel::text_preset::name::highlight(), + "Highlight", + ), + (tr::editor::panel::text_preset::name::sticker(), "Sticker"), + (tr::editor::panel::text_preset::name::neon(), "Neon"), + (tr::editor::panel::text_preset::name::stat(), "Big stat"), + (tr::editor::panel::text_preset::name::quote(), "Quote"), + (tr::editor::panel::text_preset::name::code(), "Code"), + ( + tr::editor::panel::text_preset::name::typewriter(), + "Typewriter", + ), + ( + tr::editor::panel::text_preset::sample::title(), + "Introducing Cap", + ), + ( + tr::editor::panel::text_preset::sample::headline(), + "Ship faster", + ), + ( + tr::editor::panel::text_preset::sample::cinematic(), + "Chapter one", + ), + ( + tr::editor::panel::text_preset::sample::gradient(), + "Beautiful text", + ), + ( + tr::editor::panel::text_preset::sample::name_tag(), + "Richie · Founder", + ), + ( + tr::editor::panel::text_preset::sample::caption(), + "Recorded with Cap", + ), + ( + tr::editor::panel::text_preset::sample::kicker(), + "New feature", + ), + (tr::editor::panel::text_preset::sample::label(), "Pro tip"), + ( + tr::editor::panel::text_preset::sample::highlight(), + "the important part", + ), + (tr::editor::panel::text_preset::sample::sticker(), "Boom!"), + (tr::editor::panel::text_preset::sample::neon(), "Glow up"), + ( + tr::editor::panel::text_preset::sample::quote(), + "“Make it feel effortless”", + ), + ( + tr::editor::panel::text_preset::sample::typewriter(), + "typing it out…", + ), + (tr::editor::panel::font::system_sans(), "System Sans"), + (tr::editor::panel::font::system_serif(), "System Serif"), + (tr::editor::panel::font::system_mono(), "System Mono"), + (tr::editor::panel::text_segment::style(), "Style"), + (tr::editor::panel::text_segment::font(), "Font"), + (tr::editor::panel::text_segment::font_size(), "Size"), + ( + tr::editor::panel::text_segment::line_height(), + "Line height", + ), + ( + tr::editor::panel::text_segment::letter_spacing(), + "Letter spacing", + ), + (tr::editor::panel::text_segment::look(), "Look"), + (tr::editor::panel::text_segment::color(), "Color"), + (tr::editor::panel::text_segment::gradient(), "Gradient"), + (tr::editor::panel::text_segment::background(), "Background"), + (tr::editor::panel::text_segment::outline(), "Outline"), + (tr::editor::panel::text_segment::shadow(), "Shadow"), + (tr::editor::panel::text_segment::glow(), "Glow"), + (tr::editor::panel::text_segment::animation(), "Animation"), + (tr::editor::panel::text_segment::layout(), "Layout"), + ( + tr::editor::panel::text_segment::fullscreen_help(), + "Pauses the video while the text is shown, then resumes where it left off.", + ), + ( + tr::editor::panel::text_segment::screen_transition(), + "Screen transition", + ), + (tr::editor::panel::timing::title(), "Timing"), + (tr::editor::panel::timing::start(), "Start"), + (tr::editor::panel::timing::to(), "to"), + (tr::editor::panel::timing::end(), "End"), + (tr::editor::panel::audio::fallback_name(), "Audio"), + ( + tr::editor::panel::audio::change_hint(), + "Tap to change track", + ), + (tr::editor::panel::audio::change(), "Change"), + (tr::editor::panel::audio::volume(), "Volume"), + (tr::editor::panel::audio::fade_in(), "Fade In"), + (tr::editor::panel::audio::fade_out(), "Fade Out"), + (tr::editor::panel::mask::sensitive(), "Sensitive"), + (tr::editor::panel::mask::highlight(), "Highlight"), + (tr::editor::panel::mask::effect(), "Effect"), + (tr::editor::panel::mask::blur(), "Blur"), + (tr::editor::panel::mask::pixelate(), "Pixelate"), + (tr::editor::panel::mask::pixel_size(), "Pixel Size"), + ( + tr::editor::panel::mask::outside_darkness(), + "Outside Darkness", + ), + (tr::editor::panel::scene::camera_layout(), "Camera Layout"), + (tr::editor::panel::scene::transition(), "Transition"), + (tr::editor::panel::scene::screen_zoom(), "Screen Zoom"), + ( + tr::editor::panel::scene::screen_position(), + "Screen Position", + ), + (tr::editor::panel::scene::camera_zoom(), "Camera Zoom"), + ( + tr::editor::panel::scene::camera_position(), + "Camera Position", + ), + (tr::editor::panel::scene::mode::default(), "Default"), + (tr::editor::panel::scene::mode::camera_only(), "Camera Only"), + (tr::editor::panel::scene::mode::hide_camera(), "Hide Camera"), + ( + tr::editor::panel::scene::mode::split_screen(), + "Split Screen", + ), + (tr::editor::panel::scene::mode::floating(), "Floating"), + ( + tr::editor::panel::scene::description::default(), + "Shows both screen and camera", + ), + ( + tr::editor::panel::scene::description::camera_only(), + "Shows only the camera feed", + ), + ( + tr::editor::panel::scene::description::hide_camera(), + "Shows only the screen recording", + ), + ( + tr::editor::panel::scene::description::split_screen(), + "Screen and camera side by side (auto-stacks in portrait)", + ), + ( + tr::editor::panel::scene::description::floating(), + "Screen and camera float side by side as rounded cards over the background", + ), + (tr::editor::panel::camera3d::look_title(), "Look"), + (tr::editor::panel::camera3d::moves(), "Moves"), + (tr::editor::panel::camera3d::angles(), "Angles"), + ( + tr::editor::panel::camera3d::drag_to_orbit(), + "Drag to orbit", + ), + ( + tr::editor::panel::camera3d::end_same_as_start(), + "End · same as start", + ), + ( + tr::editor::panel::camera3d::nothing_to_swap(), + "Nothing to swap on a still shot", + ), + (tr::editor::panel::camera3d::swap(), "Swap start and end"), + (tr::editor::panel::camera3d::still_shot(), "Still shot"), + ( + tr::editor::panel::camera3d::already_still(), + "Already a still shot", + ), + ( + tr::editor::panel::camera3d::hold_opening(), + "Hold the opening pose for the whole shot", + ), + (tr::editor::panel::camera3d::auto_scene(), "Auto scene"), + ( + tr::editor::panel::camera3d::auto_scene_tooltip(), + "Rebuilds every 3D shot on the track", + ), + (tr::editor::panel::camera3d::play_shot(), "Play shot"), + ( + tr::editor::panel::camera3d::play_shot_tooltip(), + "Play this shot", + ), + ( + tr::editor::panel::camera3d::delete_tooltip(), + "Delete this shot", + ), + (tr::editor::panel::camera3d::camera(), "Camera"), + (tr::editor::panel::camera3d::distance(), "Distance"), + (tr::editor::panel::camera3d::roll(), "Roll"), + (tr::editor::panel::camera3d::shift_x(), "Shift X"), + (tr::editor::panel::camera3d::shift_y(), "Shift Y"), + (tr::editor::panel::camera3d::depth_blur(), "Depth blur"), + (tr::editor::panel::camera3d::amount(), "Amount"), + (tr::editor::panel::camera3d::focus(), "Focus"), + (tr::editor::panel::camera3d::fine_tune(), "Fine-tune"), + (tr::editor::panel::camera3d::bokeh(), "Bokeh"), + ( + tr::editor::panel::camera3d::timing_advanced(), + "Timing & advanced", + ), + (tr::editor::panel::camera3d::motion_style(), "Motion style"), + (tr::editor::panel::camera3d::ease_in(), "Ease in"), + (tr::editor::panel::camera3d::ease_out(), "Ease out"), + (tr::editor::panel::camera3d::lens(), "Lens"), + (tr::editor::panel::camera3d::rotate_x(), "Rotate X"), + (tr::editor::panel::camera3d::rotate_y(), "Rotate Y"), + (tr::editor::panel::camera3d::reset_camera(), "Reset camera"), + (tr::editor::panel::camera3d::shot::still(), "Still shot"), + (tr::editor::panel::camera3d::shot::custom(), "Custom move"), + (tr::editor::panel::camera3d::look::spotlight(), "Spotlight"), + ( + tr::editor::panel::camera3d::look::perspective(), + "Perspective", + ), + (tr::editor::panel::camera3d::look::center(), "Center"), + (tr::editor::panel::camera3d::look::low_angle(), "Low angle"), + (tr::editor::panel::camera3d::look::close_up(), "Close up"), + ( + tr::editor::panel::camera3d::look::glide_across(), + "Glide across", + ), + ( + tr::editor::panel::camera3d::look::drift_down(), + "Drift down", + ), + ( + tr::editor::panel::camera3d::look::rising_sweep(), + "Rising sweep", + ), + (tr::editor::panel::camera3d::look::pull_back(), "Pull back"), + (tr::editor::panel::camera3d::look::top_down(), "Top down"), + (tr::editor::panel::camera3d::look::tilt_away(), "Tilt away"), + (tr::editor::panel::camera3d::look::unfold(), "Unfold"), + (tr::editor::panel::camera3d::look::slide_by(), "Slide by"), + ( + tr::editor::panel::camera3d::sequence::showcase(), + "Showcase", + ), + ( + tr::editor::panel::camera3d::sequence::product_tour(), + "Product tour", + ), + ( + tr::editor::panel::camera3d::sequence::punch_in(), + "Punch in", + ), + (tr::editor::panel::camera3d::easing::linear(), "Linear"), + (tr::editor::panel::camera3d::easing::smooth(), "Smooth"), + (tr::editor::panel::camera3d::easing::ease_in(), "Ease in"), + (tr::editor::panel::camera3d::easing::ease_out(), "Ease out"), + (tr::editor::panel::camera3d::focus_mode::radial(), "Radial"), + ( + tr::editor::panel::camera3d::focus_mode::directional(), + "Directional", + ), + ( + tr::editor::panel::camera3d::focus_mode::tilt_shift(), + "Tilt shift", + ), + (tr::editor::panel::camera3d::blur::strength(), "Strength"), + (tr::editor::panel::camera3d::blur::focus_x(), "Focus X"), + (tr::editor::panel::camera3d::blur::focus_y(), "Focus Y"), + ( + tr::editor::panel::camera3d::blur::focus_size(), + "Focus size", + ), + (tr::editor::panel::camera3d::blur::falloff(), "Falloff"), + (tr::editor::panel::camera3d::blur::angle(), "Angle"), + (tr::editor::panel::camera3d::blur::position(), "Position"), + (tr::editor::panel::camera3d::blur::scan(), "Scan"), + (tr::editor::panel::image::arrange(), "Arrange on canvas"), + ( + tr::editor::panel::image::arrange_help(), + "Drag the image to move it. Pull a corner to resize, or use the rotation control below to turn it.", + ), + (tr::editor::panel::image::center(), "Center on canvas"), + (tr::editor::panel::image::replace(), "Replace image"), + ( + tr::editor::panel::image::missing(), + "Image file is missing. Replace it to restore this segment while keeping its timing and transforms.", + ), + (tr::editor::panel::image::lock_aspect(), "Lock aspect ratio"), + (tr::editor::panel::image::rotation(), "Rotation"), + (tr::editor::panel::image::rounding(), "Rounding"), + ( + tr::editor::panel::style::help(), + "Overrides apply only during this segment. Enable a group to copy its global settings.", + ), + ( + tr::editor::panel::style::edit_background(), + "Edit Background & frame", + ), + (tr::editor::panel::style::edit_camera(), "Edit Camera"), + (tr::editor::panel::style::edit_cursor(), "Edit Cursor"), + ( + tr::editor::panel::style::crop_title(), + "Screen crop (source pixels)", + ), + (tr::editor::panel::style::crop_left(), "Left"), + (tr::editor::panel::style::crop_top(), "Top"), + (tr::editor::panel::style::crop_width(), "Width"), + (tr::editor::panel::style::crop_height(), "Height"), + (tr::editor::panel::style::reset_crop(), "Reset crop"), + ( + tr::editor::panel::style::camera_only_background(), + "Camera Only background", + ), + ( + tr::editor::panel::style::camera_only_padding(), + "Camera Only padding", + ), + ( + tr::editor::panel::style::camera_only_help(), + "Use a Camera Only scene. Padding reveals the background around the camera.", + ), + (tr::editor::sidebar::tab::background(), "Background"), + (tr::editor::sidebar::tab::camera(), "Camera"), + (tr::editor::sidebar::tab::audio(), "Audio"), + (tr::editor::sidebar::tab::cursor(), "Cursor"), + (tr::editor::sidebar::tab::keyboard(), "Keyboard"), + (tr::editor::sidebar::tab::captions(), "Captions"), + (tr::editor::sidebar::source::desktop(), "Desktop"), + (tr::editor::sidebar::source::wallpaper(), "Wallpaper"), + (tr::editor::sidebar::source::image(), "Image"), + (tr::editor::sidebar::source::color(), "Color"), + (tr::editor::sidebar::source::gradient(), "Gradient"), + (tr::editor::sidebar::source::animated(), "Animated"), + (tr::editor::sidebar::source::none(), "None"), + (tr::editor::sidebar::wallpaper_theme::dark(), "Dark"), + (tr::editor::sidebar::wallpaper_theme::blue(), "Blue"), + (tr::editor::sidebar::wallpaper_theme::cities(), "Cities"), + (tr::editor::sidebar::wallpaper_theme::purple(), "Purple"), + (tr::editor::sidebar::wallpaper_theme::orange(), "Orange"), + (tr::editor::sidebar::importing_image(), "Importing image…"), + ( + tr::editor::sidebar::desktop_import_failed(), + "Couldn't import your desktop wallpaper", + ), + (tr::editor::sidebar::layout::title(), "Layout"), + ( + tr::editor::sidebar::layout::background_blur(), + "Background Blur", + ), + (tr::editor::sidebar::layout::padding(), "Padding"), + ( + tr::editor::sidebar::layout::custom_position(), + "Custom screen position (dragged on canvas)", + ), + ( + tr::editor::sidebar::layout::rounded_corners(), + "Rounded Corners", + ), + (tr::editor::sidebar::layout::motion_blur(), "Motion Blur"), + (tr::editor::sidebar::background::title(), "Background"), + (tr::editor::sidebar::desktop::importing(), "Importing..."), + ( + tr::editor::sidebar::desktop::import(), + "Import desktop background", + ), + ( + tr::editor::sidebar::desktop::prompt(), + "Use the wallpaper from your desktop", + ), + (tr::editor::sidebar::desktop::reimport(), "Re-import"), + ( + tr::editor::sidebar::image::prompt(), + "Click to select or drag and drop image", + ), + (tr::editor::sidebar::gradient::from(), "From"), + (tr::editor::sidebar::gradient::to(), "To"), + (tr::editor::sidebar::gradient::angle(), "Angle"), + (tr::editor::sidebar::gradient::noise(), "Noise"), + (tr::editor::sidebar::gradient::grain_scale(), "Grain Scale"), + (tr::editor::sidebar::corner::title(), "Corner Style"), + (tr::editor::sidebar::corner::squircle(), "Squircle"), + (tr::editor::sidebar::corner::rounded(), "Rounded"), + (tr::editor::sidebar::border::title(), "Border"), + (tr::editor::sidebar::border::width(), "Border Width"), + (tr::editor::sidebar::border::color(), "Border Color"), + (tr::editor::sidebar::border::opacity(), "Border Opacity"), + (tr::editor::sidebar::notch::title(), "MacBook notch"), + ( + tr::editor::sidebar::notch::description(), + "Draws a MacBook notch over the recording. Recordings made on a Mac with a notch use their own measurements; otherwise start from the size below and adjust to match.", + ), + (tr::editor::sidebar::notch::width(), "Notch Width"), + (tr::editor::sidebar::notch::height(), "Notch Height"), + (tr::editor::sidebar::notch::position(), "Notch Position"), + (tr::editor::sidebar::shadow::title(), "Shadow"), + ( + tr::editor::sidebar::shadow::advanced(), + "Advanced shadow settings", + ), + (tr::editor::sidebar::shadow::shadow_size(), "Size"), + (tr::editor::sidebar::shadow::opacity(), "Opacity"), + (tr::editor::sidebar::shadow::blur(), "Blur"), + ( + tr::editor::sidebar::style_group::background(), + "Background & frame", + ), + (tr::editor::sidebar::style_group::camera(), "Camera"), + (tr::editor::sidebar::style_group::cursor(), "Cursor"), + ( + tr::editor::sidebar::style_group::editing_note(), + "Editing this segment only. Global settings are unchanged.", + ), + (tr::editor::sidebar::style_group::back(), "Back to Style"), + (tr::editor::sidebar::cursor::default(), "Default"), + (tr::editor::sidebar::cursor::circle(), "Circle"), + (tr::editor::sidebar::cursor::recorded(), "Recorded cursors"), + ( + tr::editor::sidebar::cursor::recorded_tooltip(), + "Recorded with this cursor", + ), + ( + tr::editor::sidebar::cursor::description_default(), + "Keeps the cursor shapes and appearance from your recording.", + ), + ( + tr::editor::sidebar::cursor::description_macos(), + "Classic macOS appearance. Custom cursors keep their recorded shape.", + ), + ( + tr::editor::sidebar::cursor::description_tahoe(), + "macOS Tahoe appearance. Custom cursors keep their recorded shape.", + ), + ( + tr::editor::sidebar::cursor::description_windows(), + "Windows appearance. Custom cursors keep their recorded shape.", + ), + ( + tr::editor::sidebar::cursor::description_circle(), + "Replaces all cursor shapes with a circle.", + ), + (tr::editor::sidebar::cursor::click_ripple(), "Click Ripple"), + (tr::editor::sidebar::cursor::color(), "Color"), + (tr::editor::sidebar::cursor::strength(), "Strength"), + (tr::editor::sidebar::cursor::ripple_size(), "Size"), + (tr::editor::sidebar::cursor::duration(), "Duration"), + (tr::editor::sidebar::animated_gradient::still(), "Still"), + ( + tr::editor::sidebar::animated_gradient::name_placeholder(), + "Name this gradient", + ), + ( + tr::editor::sidebar::animated_gradient::name_required(), + "Enter a name for this gradient.", + ), + ( + tr::editor::sidebar::animated_gradient::save_failed(), + "Could not save this gradient. Try again.", + ), + ( + tr::editor::sidebar::animated_gradient::delete_failed(), + "Could not delete this gradient. Try again.", + ), + ( + tr::editor::sidebar::animated_gradient::remember_failed(), + "Could not remember your animated gradient settings.", + ), + ( + tr::editor::sidebar::animated_gradient::randomize(), + "Randomize", + ), + (tr::editor::sidebar::animated_gradient::presets(), "Presets"), + ( + tr::editor::sidebar::animated_gradient::save_tooltip(), + "Save the current gradient", + ), + (tr::editor::sidebar::animated_gradient::saved(), "Saved"), + (tr::editor::sidebar::animated_gradient::delete(), "Delete"), + ( + tr::editor::sidebar::animated_gradient::remove_colour(), + "Remove colour", + ), + (tr::editor::sidebar::animated_gradient::colours(), "Colours"), + ( + tr::editor::sidebar::animated_gradient::add_colour(), + "Add a colour", + ), + (tr::editor::sidebar::animated_gradient::motion(), "Motion"), + ( + tr::editor::sidebar::animated_gradient::fine_tune(), + "Fine-tune", + ), + ( + tr::editor::sidebar::animated_gradient::reset_tooltip(), + "Reset fine-tune settings", + ), + ( + tr::editor::sidebar::animated_gradient::control::direction(), + "Direction", + ), + ( + tr::editor::sidebar::animated_gradient::control::flow_scale(), + "Flow Scale", + ), + ( + tr::editor::sidebar::animated_gradient::control::flow_strength(), + "Flow Strength", + ), + ( + tr::editor::sidebar::animated_gradient::control::curvature(), + "Curvature", + ), + ( + tr::editor::sidebar::animated_gradient::control::detail(), + "Detail", + ), + ( + tr::editor::sidebar::animated_gradient::control::relief(), + "Relief", + ), + ( + tr::editor::sidebar::animated_gradient::control::highlights(), + "Highlights", + ), + ( + tr::editor::sidebar::animated_gradient::control::shading(), + "Shading", + ), + ( + tr::editor::sidebar::animated_gradient::control::ripple_size(), + "Ripple Size", + ), + ( + tr::editor::sidebar::animated_gradient::control::grain_amount(), + "Grain Amount", + ), + ( + tr::editor::sidebar::animated_gradient::control::grain_size(), + "Grain Size", + ), + ( + tr::editor::sidebar::animated_gradient::control::exposure(), + "Exposure", + ), + ( + tr::editor::sidebar::animated_gradient::control::contrast(), + "Contrast", + ), + ( + tr::editor::sidebar::animated_gradient::control::vibrance(), + "Vibrance", + ), + ( + tr::editor::sidebar::animated_gradient::control::motion_speed(), + "Motion Speed", + ), + ( + tr::editor::sidebar::animated_gradient::group::flow(), + "Flow", + ), + ( + tr::editor::sidebar::animated_gradient::group::lighting(), + "Lighting", + ), + ( + tr::editor::sidebar::animated_gradient::group::texture(), + "Texture", + ), + ( + tr::editor::sidebar::animated_gradient::group::colour(), + "Colour", + ), + ( + tr::editor::sidebar::animated_gradient::group::animation(), + "Animation", + ), + (tr::editor::color::title(), "Color Correction"), + (tr::editor::color::apply_to_cursor(), "Apply to cursor"), + (tr::editor::color::fine_tune(), "Fine-tune colors"), + (tr::editor::color::preset::none::name(), "None"), + ( + tr::editor::color::preset::none::description(), + "Original colors, no grade applied", + ), + (tr::editor::color::preset::cinematic::name(), "Cinematic"), + ( + tr::editor::color::preset::cinematic::description(), + "Teal shadows and orange highlights with light grain", + ), + (tr::editor::color::preset::noir::name(), "Noir"), + ( + tr::editor::color::preset::noir::description(), + "High-contrast black and white with heavy grain", + ), + (tr::editor::color::preset::vintage::name(), "Vintage"), + ( + tr::editor::color::preset::vintage::description(), + "Faded warm film look with soft contrast", + ), + (tr::editor::color::preset::frost::name(), "Frost"), + ( + tr::editor::color::preset::frost::description(), + "Cool, crisp tones with muted color", + ), + (tr::editor::color::preset::golden::name(), "Golden"), + ( + tr::editor::color::preset::golden::description(), + "Warm golden-hour glow", + ), + (tr::editor::color::preset::midnight::name(), "Midnight"), + ( + tr::editor::color::preset::midnight::description(), + "Dark, moody teal with subdued color", + ), + (tr::editor::color::preset::vivid::name(), "Vivid"), + ( + tr::editor::color::preset::vivid::description(), + "Punchy saturation and contrast boost", + ), + (tr::editor::color::preset::dreamy::name(), "Dreamy"), + ( + tr::editor::color::preset::dreamy::description(), + "Soft, airy pastels with lifted blacks", + ), + (tr::editor::color::slider::grain(), "Grain"), + (tr::editor::color::slider::strength(), "Strength"), + (tr::editor::color::slider::exposure(), "Exposure"), + (tr::editor::color::slider::contrast(), "Contrast"), + (tr::editor::color::slider::saturation(), "Saturation"), + (tr::editor::color::slider::temperature(), "Temperature"), + (tr::editor::color::slider::tint(), "Tint"), + (tr::editor::color::slider::fade(), "Fade"), + (tr::editor::color::slider::split_tone(), "Split Tone"), + (tr::editor::color::slider::vignette(), "Vignette"), + (tr::editor::audio::library::done(), "Done"), + (tr::editor::audio::library::change_title(), "Change audio"), + (tr::editor::audio::library::add_title(), "Add audio"), + ( + tr::editor::audio::library::change_hint(), + "Pick a different track for this segment", + ), + ( + tr::editor::audio::library::add_hint(), + "Add audio, music or other sounds to your video", + ), + (tr::editor::audio::library::import(), "Import file"), + (tr::editor::crop::title(), "Crop"), + (tr::editor::crop::free(), "Free"), + (tr::editor::crop::snap_to_ratios(), "Snap to ratios"), + ( + tr::editor::crop::style_changed(), + "This Style changed while cropping. Cancel and reopen Crop.", + ), + (tr::editor::crop::preview(), "Preview"), + (tr::editor::crop::aspect_ratio(), "Aspect ratio"), + (tr::editor::crop::dimensions(), "Size"), + (tr::editor::crop::position(), "Position"), + (tr::editor::crop::center(), "Center"), + (tr::editor::crop::full(), "Full"), + (tr::editor::crop::loading_frame(), "Loading frame…"), + (tr::editor::crop::rendering_preview(), "Rendering preview…"), + ]; + for (translated, literal) in pairs { + assert_eq!(translated, literal); + } + let plain_keys = KEYS + .iter() + .filter(|key| is_editor_panels_surface_key(key)) + .count() + - FORMATTED.len(); + assert_eq!( + pairs.len(), + plain_keys, + "every plain key has a literal above" + ); + for key in FORMATTED { + assert!(KEYS.contains(key), "{key}"); + } +} + +#[test] +fn editor_panel_messages_format_like_the_code_they_replaced() { + for index in [0_usize, 1, 4, 11] { + let number = index + 1; + assert_eq!( + tr::editor::panel::zoom::title(number), + format!("Zoom {}", index + 1) + ); + assert_eq!( + tr::editor::panel::text_segment::title(number), + format!("Text {}", index + 1) + ); + assert_eq!( + tr::editor::panel::caption::title(number), + format!("Caption {}", index + 1) + ); + assert_eq!( + tr::editor::panel::keyboard::title(number), + format!("Keyboard {}", index + 1) + ); + assert_eq!( + tr::editor::panel::audio::title(number), + format!("Audio {}", index + 1) + ); + assert_eq!( + tr::editor::panel::mask::title(number), + format!("Mask {}", index + 1) + ); + assert_eq!( + tr::editor::crop::editing_style(number), + format!("Editing Style {} only", index + 1) + ); + assert_eq!( + tr::editor::crop::editing_style_override(number), + format!( + "Editing Style {} only · Saving enables its background override", + index + 1 + ) + ); + for (group, label) in [ + ( + tr::editor::sidebar::style_group::background(), + "Background & frame", + ), + (tr::editor::sidebar::style_group::camera(), "Camera"), + (tr::editor::sidebar::style_group::cursor(), "Cursor"), + ] { + assert_eq!( + tr::editor::sidebar::style_group::title(number, group), + format!("Style {} · {}", index + 1, label) + ); + } + } + + for weight in [450_f32, 350., 725.5, 1000.] { + assert_eq!( + tr::editor::panel::font_weight::custom(weight), + format!("Custom ({})", weight) + ); + } + + for scene in crate::editor_panels::CAMERA3D_SCENES { + let shots = scene.shots.len(); + assert_eq!( + tr::editor::panel::camera3d::shots(shots), + format!("{shots} shots") + ); + } + assert_eq!(tr::editor::panel::camera3d::shots(0), "0 shots"); + assert_eq!(tr::editor::panel::camera3d::shots(1), "1 shot"); + assert_eq!(tr::editor::panel::camera3d::shots(2), "2 shots"); + assert_eq!(tr::editor::panel::camera3d::shots(5), "5 shots"); + + for time in ["0:00.0", "0:04.5", "12:03.2", "1:02:03.4"] { + assert_eq!( + tr::editor::panel::camera3d::pose_start(time), + format!("{} \u{b7} {}", "Start", time) + ); + assert_eq!( + tr::editor::panel::camera3d::pose_end(time), + format!("{} \u{b7} {}", "End", time) + ); + } + + for (look, duration) in [ + ("Glide across", 4.0_f64), + ("Still shot", 0.25), + ("Custom move", 12.96), + ] { + assert_eq!( + tr::editor::panel::camera3d::header(look, format!("{duration:.1}")), + format!("3D shot \u{b7} {look} \u{b7} {duration:.1}s") + ); + } + + for (easing_label, fov) in [("Smooth", 24.4_f64), ("Linear", 45.), ("Ease out", 119.6)] { + assert_eq!( + tr::editor::panel::camera3d::timing_summary(easing_label, fov.round() as i32), + format!("{easing_label} \u{b7} Lens {}", fov.round() as i32) + ); + } + + for saved in [2_usize, 5, 12] { + assert_eq!( + tr::editor::sidebar::animated_gradient::limit_reached(saved), + format!("You can save up to {saved} gradients. Delete one to add another.") + ); + } + assert_eq!( + tr::editor::sidebar::animated_gradient::limit_reached(0), + "You can save up to 0 gradients. Delete one to add another." + ); + assert_eq!( + tr::editor::sidebar::animated_gradient::limit_reached(1), + "You can save up to 1 gradient. Delete one to add another." + ); + + assert_eq!( + tr::editor::crop::hint("\u{21e7}"), + "Drag to move \u{b7} Arrow keys nudge \u{b7} Hold \u{21e7} to skip snapping" + ); + assert_eq!( + crate::editor_crop::crop_hint_parts(), + ( + "Drag to move · Arrow keys nudge · Hold".to_string(), + "to skip snapping".to_string() + ) + ); +} diff --git a/apps/desktop-gpui/src/i18n/mod.rs b/apps/desktop-gpui/src/i18n/mod.rs index d7d487d1a50..e2af067bcf8 100644 --- a/apps/desktop-gpui/src/i18n/mod.rs +++ b/apps/desktop-gpui/src/i18n/mod.rs @@ -158,6 +158,8 @@ pub fn apply(preference: Preference, cx: &mut App) { cx.refresh_windows(); } +#[cfg(test)] +mod editor_panels_surface_tests; #[cfg(test)] mod surface_tests; #[cfg(test)] diff --git a/apps/desktop-gpui/src/i18n/tests.rs b/apps/desktop-gpui/src/i18n/tests.rs index c3b43e28840..d78c5e1ac42 100644 --- a/apps/desktop-gpui/src/i18n/tests.rs +++ b/apps/desktop-gpui/src/i18n/tests.rs @@ -440,6 +440,7 @@ fn english_matches_the_literals_it_replaced() { .filter(|key| { !key.starts_with(NEW_IN_THE_LANGUAGE_PICKER) && !super::surface_tests::is_main_surface_key(key) + && !super::editor_panels_surface_tests::is_editor_panels_surface_key(key) }) .count() - 3; From 0cf02f1ae11ae68f7f448173e4dc50c289e19f94 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:43:09 +0000 Subject: [PATCH 02/25] feat(desktop-gpui): localize the screenshot editor --- .../src/screenshot_annotations.rs | 68 ++-- apps/desktop-gpui/src/screenshot_crop.rs | 23 +- apps/desktop-gpui/src/screenshot_editor.rs | 295 +++++++++++------- apps/desktop-gpui/src/screenshot_export.rs | 12 +- 4 files changed, 245 insertions(+), 153 deletions(-) diff --git a/apps/desktop-gpui/src/screenshot_annotations.rs b/apps/desktop-gpui/src/screenshot_annotations.rs index 11bd4e64a7a..a493e263073 100644 --- a/apps/desktop-gpui/src/screenshot_annotations.rs +++ b/apps/desktop-gpui/src/screenshot_annotations.rs @@ -42,6 +42,7 @@ use gpui::{ }; use crate::editor_sidebar::{BACKGROUND_COLORS, hex_digit_count, hex_to_rgb}; +use crate::i18n::tr; use crate::screenshot_editor::ScreenshotEditorWindow; use crate::theme::Theme; use crate::ui; @@ -88,7 +89,7 @@ impl Tool { } } - pub fn label(self) -> &'static str { + pub fn id(self) -> &'static str { match self { Tool::Select => "Select", Tool::Draw => "Draw", @@ -100,6 +101,18 @@ impl Tool { } } + pub fn label(self) -> &'static str { + match self { + Tool::Select => tr::screenshot_editor::tool::select(), + Tool::Draw => tr::screenshot_editor::tool::draw(), + Tool::Arrow => tr::screenshot_editor::tool::arrow(), + Tool::Rectangle => tr::screenshot_editor::tool::rectangle(), + Tool::Mask => tr::screenshot_editor::tool::mask(), + Tool::Circle => tr::screenshot_editor::tool::circle(), + Tool::Text => tr::screenshot_editor::tool::text_label(), + } + } + /// The `kbd` chip on the button's tooltip, and the bare key `Editor.tsx` /// binds it to (`:120-152`). pub fn shortcut(self) -> &'static str { @@ -143,12 +156,12 @@ pub fn annotation_icon(annotation_type: AnnotationType) -> &'static str { /// `ANNOTATION_TYPE_LABELS` (`LayersPanel.tsx:23-30`). pub fn annotation_label(annotation_type: AnnotationType) -> &'static str { match annotation_type { - AnnotationType::Arrow => "Arrow", - AnnotationType::Rectangle => "Rectangle", - AnnotationType::Circle => "Circle", - AnnotationType::Mask => "Mask", - AnnotationType::Text => "Text", - AnnotationType::Draw => "Draw", + AnnotationType::Arrow => tr::screenshot_editor::tool::arrow(), + AnnotationType::Rectangle => tr::screenshot_editor::tool::rectangle(), + AnnotationType::Circle => tr::screenshot_editor::tool::circle(), + AnnotationType::Mask => tr::screenshot_editor::tool::mask(), + AnnotationType::Text => tr::screenshot_editor::tool::text_label(), + AnnotationType::Draw => tr::screenshot_editor::tool::draw(), } } @@ -1636,7 +1649,7 @@ impl ScreenshotEditorWindow { fill_color: TRANSPARENT.to_string(), opacity: style.as_ref().map_or(1., |(_, _, opacity)| *opacity), rotation: 0., - text: is_text.then(|| "Text".to_string()), + text: is_text.then(|| tr::screenshot_editor::annotation::default_text().to_string()), mask_type: is_mask.then_some(MaskType::Pixelate), mask_level: is_mask.then_some(MASK_DEFAULT_LEVEL), points: (annotation_type == AnnotationType::Draw).then(|| vec![[x, y]]), @@ -2526,7 +2539,11 @@ impl ScreenshotEditorWindow { if !is_mask { row = row.child(config_item( &theme, - if is_text { "Color" } else { "Stroke" }, + if is_text { + tr::screenshot_editor::style::color() + } else { + tr::screenshot_editor::annotation::stroke() + }, None, self.render_color_button(ColorTarget::Stroke, cx), )); @@ -2534,7 +2551,7 @@ impl ScreenshotEditorWindow { if !is_mask && !is_text { row = row.child(config_item( &theme, - "Width", + tr::screenshot_editor::style::width(), Some(format!("{}px", annotation.stroke_width.round() as i64)), self.annotation_slider(AnnotationSlider::Width, px(80.), cx) .into_any_element(), @@ -2543,7 +2560,7 @@ impl ScreenshotEditorWindow { if matches!(kind, AnnotationType::Rectangle | AnnotationType::Circle) { row = row.child(config_item( &theme, - "Fill", + tr::screenshot_editor::annotation::fill(), None, self.render_color_button(ColorTarget::Fill, cx), )); @@ -2551,7 +2568,7 @@ impl ScreenshotEditorWindow { if !is_mask { row = row.child(config_item( &theme, - "Opacity", + tr::screenshot_editor::style::opacity(), Some(format!("{}%", (annotation.opacity * 100.).round() as i64)), self.annotation_slider(AnnotationSlider::Opacity, px(80.), cx) .into_any_element(), @@ -2562,19 +2579,19 @@ impl ScreenshotEditorWindow { row = row .child(config_item( &theme, - "Style", + tr::screenshot_editor::annotation::mask_style(), None, div() .flex() .flex_row() .gap(px(4.)) - .child(self.render_mask_pill("Blur", MaskType::Blur, mask_type, cx)) - .child(self.render_mask_pill("Pixelate", MaskType::Pixelate, mask_type, cx)) + .child(self.render_mask_pill(MaskType::Blur, mask_type, cx)) + .child(self.render_mask_pill(MaskType::Pixelate, mask_type, cx)) .into_any_element(), )) .child(config_item( &theme, - "Intensity", + tr::screenshot_editor::annotation::intensity(), Some(format!( "{}", annotation.mask_level.unwrap_or(MASK_FALLBACK_LEVEL).round() as i64 @@ -2586,7 +2603,7 @@ impl ScreenshotEditorWindow { if is_text { row = row.child(config_item( &theme, - "Size", + tr::screenshot_editor::style::size_label(), Some(format!("{}px", annotation.height.round() as i64)), self.annotation_slider(AnnotationSlider::TextSize, px(80.), cx) .into_any_element(), @@ -2599,16 +2616,19 @@ impl ScreenshotEditorWindow { /// The two `Style` pills (`AnnotationConfig.tsx:100-123`). fn render_mask_pill( &self, - label: &'static str, value: MaskType, current: MaskType, cx: &mut Context, ) -> AnyElement { let theme = self.theme; let active = value == current; + let (id, label) = match value { + MaskType::Blur => ("Blur", tr::screenshot_editor::style::blur()), + MaskType::Pixelate => ("Pixelate", tr::screenshot_editor::annotation::pixelate()), + }; div() .id(gpui::SharedString::from(format!( - "screenshot-mask-style-{label}" + "screenshot-mask-style-{id}" ))) .flex() .items_center() @@ -2920,7 +2940,11 @@ impl ScreenshotEditorWindow { .when(selected, |this| this.border_2()) .children(checker_quarters(20.)) .tooltip(move |_window, cx| { - ui::Tooltip::new(&theme, "Transparent").view(cx) + ui::Tooltip::new( + &theme, + tr::screenshot_editor::annotation::transparent(), + ) + .view(cx) }) .on_click(cx.listener(move |this, _, window, cx| { cx.stop_propagation(); @@ -3164,14 +3188,14 @@ impl ScreenshotEditorWindow { div() .text_size(px(12.)) .text_color(theme.gray_10) - .child("No layers yet"), + .child(tr::screenshot_editor::layers::empty()), ) .child( div() .mt(px(4.)) .text_size(px(10.)) .text_color(theme.gray_8) - .child("Use the tools above to add annotations"), + .child(tr::screenshot_editor::layers::empty_hint()), ) .into_any_element(); } diff --git a/apps/desktop-gpui/src/screenshot_crop.rs b/apps/desktop-gpui/src/screenshot_crop.rs index a882b471625..90b9ba6db17 100644 --- a/apps/desktop-gpui/src/screenshot_crop.rs +++ b/apps/desktop-gpui/src/screenshot_crop.rs @@ -37,6 +37,7 @@ use crate::editor_crop::{ self as engine, CropAnim, CropBounds, CropDrag, CropField, CropHit, CropMenuChoice, CropState, HANDLES, ResizeSession, SE_HANDLE, Vec2, }; +use crate::i18n::tr; use crate::screenshot_editor::ScreenshotEditorWindow; use crate::theme::Theme; use crate::ui; @@ -757,8 +758,18 @@ impl ScreenshotEditorWindow { .flex_row() .gap(px(32.)) .flex_none() - .child(group("Size", CropField::Width, CropField::Height, self)) - .child(group("Position", CropField::X, CropField::Y, self)), + .child(group( + tr::screenshot_editor::style::size_label(), + CropField::Width, + CropField::Height, + self, + )) + .child(group( + tr::screenshot_editor::crop::position(), + CropField::X, + CropField::Y, + self, + )), ) .child( div() @@ -807,7 +818,7 @@ impl ScreenshotEditorWindow { .child( ui::EditorButton::plain(&theme, "screenshot-crop-full") .left_icon("icons/maximize.svg") - .label("Full") + .label(tr::screenshot_editor::crop::full()) .disabled(full) .on_click(cx.listener(|this, _, window, cx| { if let Some(dialog) = this.crop.as_mut() { @@ -820,7 +831,7 @@ impl ScreenshotEditorWindow { .child( ui::EditorButton::plain(&theme, "screenshot-crop-reset") .left_icon("icons/circle-x.svg") - .label("Reset") + .label(tr::common::reset()) .disabled(untouched) .on_click(cx.listener(|this, _, window, cx| { if let Some(dialog) = this.crop.as_mut() { @@ -1046,7 +1057,7 @@ impl ScreenshotEditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.gray_10)) - .child("Loading frame…") + .child(tr::screenshot_editor::crop::loading_frame()) .into_any_element(), ), }) @@ -1089,7 +1100,7 @@ impl ScreenshotEditorWindow { ui::ButtonVariant::Primary, ui::ButtonSize::Md, ) - .label("Save") + .label(tr::common::save()) .on_click(cx.listener(|this, _, window, cx| this.save_crop_dialog(window, cx))), ) .into_any_element() diff --git a/apps/desktop-gpui/src/screenshot_editor.rs b/apps/desktop-gpui/src/screenshot_editor.rs index af940970534..7d6057f8d41 100644 --- a/apps/desktop-gpui/src/screenshot_editor.rs +++ b/apps/desktop-gpui/src/screenshot_editor.rs @@ -46,6 +46,7 @@ use crate::editor_sidebar::{ self, BACKGROUND_COLORS, BACKGROUND_IMAGE_EXTENSIONS, BACKGROUND_THEMES, DEFAULT_GRADIENT_FROM, DEFAULT_GRADIENT_TO, GRADIENT_PRESETS, color_to_hsla, hex_to_rgb, }; +use crate::i18n::tr; use crate::screenshot_annotations::{self as annotations, AnnotationState, Tool}; use crate::theme::LocaleFont as _; use crate::theme::Theme; @@ -134,21 +135,21 @@ pub(crate) fn bundle_image_path(bundle: &Path) -> Option { /// Read `original.png` (or the first PNG in the bundle), the recording meta /// and the project config -- the disk half of `create_standalone_instance`. pub fn load_source(bundle: &Path) -> Result { - let image_path = - bundle_image_path(bundle).ok_or_else(|| format!("No PNG found in {}", bundle.display()))?; + let image_path = bundle_image_path(bundle) + .ok_or_else(|| tr::screenshot_editor::error::no_image(bundle.display()))?; - let image = image::open(&image_path).map_err(|e| format!("Failed to open image: {e}"))?; + let image = image::open(&image_path).map_err(tr::screenshot_editor::error::open_image)?; let (width, height) = (image.width(), image.height()); if width > MAX_DIMENSION || height > MAX_DIMENSION { - return Err(format!("Image dimensions exceed maximum: {width}x{height}")); + return Err(tr::screenshot_editor::error::too_large(width, height)); } - let meta = RecordingMeta::load_for_project(bundle) - .map_err(|e| format!("Failed to load screenshot meta: {e}"))?; + let meta = + RecordingMeta::load_for_project(bundle).map_err(tr::screenshot_editor::error::load_meta)?; let studio_meta = match &meta.inner { RecordingMetaInner::Studio(inner) => (**inner).clone(), RecordingMetaInner::Instant(_) => { - return Err("Not a screenshot bundle".to_string()); + return Err(tr::screenshot_editor::error::not_screenshot().to_string()); } }; let config = ProjectConfiguration::load(bundle).unwrap_or_default(); @@ -219,7 +220,7 @@ pub async fn run_still_renderer( } { Ok(constants) => constants, Err(error) => { - let _ = setup_tx.send(Err(format!("The GPU renderer failed to start: {error}"))); + let _ = setup_tx.send(Err(tr::screenshot_editor::error::renderer_failed(error))); return; } }; @@ -480,12 +481,25 @@ enum BgTab { } impl BgTab { - const ALL: [(Self, &'static str); 4] = [ - (Self::Wallpaper, "Wallpaper"), - (Self::Image, "Image"), - (Self::Color, "Color"), - (Self::Gradient, "Gradient"), - ]; + const ALL: [Self; 4] = [Self::Wallpaper, Self::Image, Self::Color, Self::Gradient]; + + fn id(self) -> &'static str { + match self { + Self::Wallpaper => "Wallpaper", + Self::Image => "Image", + Self::Color => "Color", + Self::Gradient => "Gradient", + } + } + + fn label(self) -> &'static str { + match self { + Self::Wallpaper => tr::screenshot_editor::background::wallpaper(), + Self::Image => tr::screenshot_editor::background::image(), + Self::Color => tr::screenshot_editor::background::color(), + Self::Gradient => tr::screenshot_editor::background::gradient(), + } + } fn for_source(source: &BackgroundSource) -> Self { match source { @@ -543,11 +557,11 @@ impl Popover { fn tooltip(self) -> &'static str { match self { - Self::Background => "Background", - Self::Padding => "Padding", - Self::Rounding => "Corner Rounding", - Self::Shadow => "Shadow", - Self::Border => "Border", + Self::Background => tr::screenshot_editor::style::background(), + Self::Padding => tr::screenshot_editor::style::padding(), + Self::Rounding => tr::screenshot_editor::style::corner_rounding(), + Self::Shadow => tr::screenshot_editor::style::shadow(), + Self::Border => tr::screenshot_editor::style::border(), } } @@ -1539,7 +1553,10 @@ impl ScreenshotEditorWindow { cx.spawn_in(window, async move |this, cx| { #[cfg(target_os = "linux")] let picked = crate::platform::open_file_panel_async( - &[("Image", &BACKGROUND_IMAGE_EXTENSIONS[..])], + &[( + tr::screenshot_editor::background::image_files(), + &BACKGROUND_IMAGE_EXTENSIONS[..], + )], None, cx, ) @@ -1945,10 +1962,10 @@ impl ScreenshotEditorWindow { let rendered = if export_tx.send(request).await.is_ok() { match reply_rx.await { Ok(result) => result, - Err(_) => Err("The renderer stopped before the export finished".into()), + Err(_) => Err(tr::screenshot_editor::error::renderer_stopped().into()), } } else { - Err("The screenshot renderer is not running".into()) + Err(tr::screenshot_editor::error::renderer_not_running().into()) }; // The canvas pass counts as "rendering" over there too @@ -2002,7 +2019,7 @@ impl ScreenshotEditorWindow { { Ok(()) => { this.toast_success( - "Screenshot copied to clipboard!", + tr::screenshot_editor::toast::copied(), window, cx, ); @@ -2033,7 +2050,11 @@ impl ScreenshotEditorWindow { match written { Ok(()) => { this.update_in(cx, |this, window, cx| { - this.toast_success("Screenshot saved!", window, cx) + this.toast_success( + tr::screenshot_editor::toast::saved(), + window, + cx, + ) }) .ok(); } @@ -2113,7 +2134,7 @@ impl ScreenshotEditorWindow { self.toast_update( toast, ToastKind::Success, - "Share link copied to clipboard", + tr::screenshot_editor::toast::link_copied(), window, cx, ); @@ -2145,7 +2166,7 @@ impl ScreenshotEditorWindow { }; self.exporting = true; self.export_status = ExportStatus::Encoding; - let toast = self.toast_loading("Preparing upload", cx); + let toast = self.toast_loading(tr::screenshot_editor::share::preparing(), cx); cx.notify(); let config = self.project.clone(); @@ -2173,7 +2194,7 @@ impl ScreenshotEditorWindow { .background_executor() .spawn(async move { RecordingMeta::load_for_project(&meta_bundle) - .map_err(|error| format!("Failed to load screenshot metadata: {error}")) + .map_err(tr::screenshot_editor::error::load_metadata) }) .await; let mut meta = match meta { @@ -2205,7 +2226,7 @@ impl ScreenshotEditorWindow { this.share_progress( toast, ExportStatus::Rendering, - "Rendering screenshot", + tr::screenshot_editor::share::rendering(), window, cx, ); @@ -2219,10 +2240,10 @@ impl ScreenshotEditorWindow { let rendered = if export_tx.send(request).await.is_ok() { match reply_rx.await { Ok(result) => result, - Err(_) => Err("The renderer stopped before the export finished".into()), + Err(_) => Err(tr::screenshot_editor::error::renderer_stopped().into()), } } else { - Err("The screenshot renderer is not running".into()) + Err(tr::screenshot_editor::error::renderer_not_running().into()) }; let raw = match rendered { Ok(raw) => raw, @@ -2245,7 +2266,7 @@ impl ScreenshotEditorWindow { this.share_progress( toast, ExportStatus::Encoding, - "Preparing upload", + tr::screenshot_editor::share::preparing(), window, cx, ); @@ -2274,7 +2295,7 @@ impl ScreenshotEditorWindow { this.share_progress( toast, ExportStatus::Uploading, - "Uploading screenshot", + tr::screenshot_editor::share::uploading(), window, cx, ); @@ -2288,7 +2309,7 @@ impl ScreenshotEditorWindow { }); let outcome = match upload.await { Ok(outcome) => outcome, - Err(error) => Err(format!("The upload task failed: {error}")), + Err(error) => Err(tr::screenshot_editor::error::upload_task(error)), }; let uploaded = match outcome { @@ -2297,7 +2318,7 @@ impl ScreenshotEditorWindow { this.update_in(cx, |this, window, cx| { this.finish_share_error( toast, - "Sign in to Cap in your browser, then share again".into(), + tr::screenshot_editor::error::sign_in_to_share().into(), window, cx, ); @@ -2312,7 +2333,7 @@ impl ScreenshotEditorWindow { this.update_in(cx, |this, window, cx| { this.finish_share_error( toast, - "This feature requires an upgraded plan".into(), + tr::screenshot_editor::error::upgrade_required().into(), window, cx, ) @@ -2342,7 +2363,7 @@ impl ScreenshotEditorWindow { .background_executor() .spawn(async move { meta.save_for_project() - .map_err(|error| format!("Error saving project: {error}")) + .map_err(tr::screenshot_editor::error::save_project) }) .await; @@ -2362,9 +2383,9 @@ impl ScreenshotEditorWindow { cx.spawn_in(window, async move |_this, cx| { if !crate::platform::confirm_dialog( "Cap", - "Are you sure you want to delete this screenshot?", - "Yes", - "No", + tr::main::delete_screenshot_confirm(), + tr::common::yes(), + tr::common::no(), false, ) { return; @@ -2388,20 +2409,31 @@ impl ScreenshotEditorWindow { // -- Aspect ratio ------------------------------------------------------- - const ASPECT_OPTIONS: [(Option, &'static str); 6] = [ - (None, "Auto"), - (Some(cap_project::AspectRatio::Wide), "Wide ⋅16:9"), - (Some(cap_project::AspectRatio::Vertical), "Vertical ⋅9:16"), - (Some(cap_project::AspectRatio::Square), "Square ⋅1:1"), - (Some(cap_project::AspectRatio::Classic), "Classic ⋅4:3"), - (Some(cap_project::AspectRatio::Tall), "Tall ⋅3:4"), + const ASPECT_OPTIONS: [Option; 6] = [ + None, + Some(cap_project::AspectRatio::Wide), + Some(cap_project::AspectRatio::Vertical), + Some(cap_project::AspectRatio::Square), + Some(cap_project::AspectRatio::Classic), + Some(cap_project::AspectRatio::Tall), ]; + fn aspect_option_label(value: &Option) -> &'static str { + match value { + None => tr::screenshot_editor::aspect::auto(), + Some(cap_project::AspectRatio::Wide) => tr::screenshot_editor::aspect::wide(), + Some(cap_project::AspectRatio::Vertical) => tr::screenshot_editor::aspect::vertical(), + Some(cap_project::AspectRatio::Square) => tr::screenshot_editor::aspect::square(), + Some(cap_project::AspectRatio::Classic) => tr::screenshot_editor::aspect::classic(), + Some(cap_project::AspectRatio::Tall) => tr::screenshot_editor::aspect::tall(), + } + } + /// `KSelect.Value` (`AspectRatioSelect.tsx:78-88`) -- the trigger shows the /// bare ratio, not the option's name. fn aspect_label(&self) -> &'static str { match &self.project.aspect_ratio { - None => "Auto", + None => tr::screenshot_editor::aspect::auto(), Some(cap_project::AspectRatio::Wide) => "16:9", Some(cap_project::AspectRatio::Vertical) => "9:16", Some(cap_project::AspectRatio::Square) => "1:1", @@ -2413,15 +2445,18 @@ impl ScreenshotEditorWindow { fn aspect_menu_items(&self) -> Vec { Self::ASPECT_OPTIONS .iter() - .map(|(value, label)| { - ui::MenuItem::new(*label, aspect_eq(value, &self.project.aspect_ratio)) + .map(|value| { + ui::MenuItem::new( + Self::aspect_option_label(value), + aspect_eq(value, &self.project.aspect_ratio), + ) }) .collect() } fn choose_aspect(&mut self, index: usize, window: &mut Window, cx: &mut Context) { self.menu = None; - let Some((next, _)) = Self::ASPECT_OPTIONS.get(index) else { + let Some(next) = Self::ASPECT_OPTIONS.get(index) else { return; }; let next = next.clone(); @@ -2438,29 +2473,33 @@ impl ScreenshotEditorWindow { ); } - const CORNER_STYLES: [(CornerStyle, &'static str); 2] = [ - (CornerStyle::Squircle, "Squircle"), - (CornerStyle::Rounded, "Rounded"), - ]; + const CORNER_STYLES: [CornerStyle; 2] = [CornerStyle::Squircle, CornerStyle::Rounded]; - fn corner_style_label(&self) -> &'static str { - match self.project.background.rounding_type { - CornerStyle::Squircle => "Squircle", - CornerStyle::Rounded => "Rounded", + fn corner_style_name(style: CornerStyle) -> &'static str { + match style { + CornerStyle::Squircle => tr::screenshot_editor::corner::squircle(), + CornerStyle::Rounded => tr::screenshot_editor::corner::rounded(), } } + fn corner_style_label(&self) -> &'static str { + Self::corner_style_name(self.project.background.rounding_type) + } + fn menu_items(&self, kind: MenuKind) -> Vec { match kind { MenuKind::Aspect => self.aspect_menu_items(), MenuKind::More => vec![ - ui::MenuItem::new("Open Folder", false), - ui::MenuItem::new("Delete", false), + ui::MenuItem::new(tr::screenshot_editor::menu::open_folder(), false), + ui::MenuItem::new(tr::screenshot_editor::menu::delete(), false), ], MenuKind::CornerStyle => Self::CORNER_STYLES .iter() - .map(|(style, label)| { - ui::MenuItem::new(*label, *style == self.project.background.rounding_type) + .map(|style| { + ui::MenuItem::new( + Self::corner_style_name(*style), + *style == self.project.background.rounding_type, + ) }) .collect(), } @@ -2742,7 +2781,7 @@ impl ScreenshotEditorWindow { &theme, "screenshot-layers-toggle", "icons/layers.svg", - "Layers", + tr::screenshot_editor::layers::title(), "L", layers_open, cx.listener(|this, _, _window, cx| { @@ -2754,7 +2793,7 @@ impl ScreenshotEditorWindow { .children(Tool::ALL.map(|tool| { tool_button( &theme, - gpui::SharedString::from(format!("screenshot-tool-{}", tool.label())), + gpui::SharedString::from(format!("screenshot-tool-{}", tool.id())), tool.icon(), tool.label(), tool.shortcut(), @@ -2773,10 +2812,10 @@ impl ScreenshotEditorWindow { let crop_enabled = self.image_size.is_some(); let exporting = self.exporting; let share_tooltip = match self.export_status { - ExportStatus::Rendering => "Rendering screenshot", - ExportStatus::Encoding => "Preparing upload", - ExportStatus::Uploading => "Uploading screenshot", - ExportStatus::Idle => "Create shareable link", + ExportStatus::Rendering => tr::screenshot_editor::share::rendering(), + ExportStatus::Encoding => tr::screenshot_editor::share::preparing(), + ExportStatus::Uploading => tr::screenshot_editor::share::uploading(), + ExportStatus::Idle => tr::screenshot_editor::share::create_link(), }; let tools = div() @@ -2800,7 +2839,7 @@ impl ScreenshotEditorWindow { .as_ref() .is_some_and(|(kind, _)| *kind == MenuKind::Aspect), ) - .tooltip(&theme, "Aspect Ratio") + .tooltip(&theme, tr::screenshot_editor::header::aspect_ratio()) .on_click(cx.listener(|this, _, window, cx| { cx.stop_propagation(); this.toggle_menu(MenuKind::Aspect, Anchor::Aspect, window, cx); @@ -2812,7 +2851,7 @@ impl ScreenshotEditorWindow { .left_icon("icons/crop.svg") .icon_size(px(16.)) .disabled(!crop_enabled) - .tooltip(&theme, "Crop Image") + .tooltip(&theme, tr::screenshot_editor::header::crop_image()) .on_click(cx.listener(|this, _, window, cx| { cx.stop_propagation(); this.open_crop_dialog(window, cx); @@ -2910,7 +2949,7 @@ impl ScreenshotEditorWindow { .left_icon("icons/copy.svg") .icon_size(px(16.)) .disabled(exporting) - .tooltip(&theme, "Copy to Clipboard") + .tooltip(&theme, tr::screenshot_editor::header::copy()) .on_click(cx.listener(|this, _, window, cx| { cx.stop_propagation(); this.export_image(ExportDestination::Clipboard, window, cx); @@ -2921,7 +2960,7 @@ impl ScreenshotEditorWindow { .left_icon("icons/save.svg") .icon_size(px(16.)) .disabled(exporting) - .tooltip(&theme, "Save") + .tooltip(&theme, tr::common::save()) .on_click(cx.listener(|this, _, window, cx| { cx.stop_propagation(); this.export_image(ExportDestination::File, window, cx); @@ -2950,7 +2989,7 @@ impl ScreenshotEditorWindow { .as_ref() .is_some_and(|(kind, _)| *kind == MenuKind::More), ) - .tooltip(&theme, "More Actions") + .tooltip(&theme, tr::screenshot_editor::header::more_actions()) .on_click(cx.listener(|this, _, window, cx| { cx.stop_propagation(); this.toggle_menu(MenuKind::More, Anchor::More, window, cx); @@ -3015,7 +3054,7 @@ impl ScreenshotEditorWindow { .size(px(16.)) .text_color(theme.gray_12), ) - .child("Layers"), + .child(tr::screenshot_editor::layers::title()), ) .child( div() @@ -3057,7 +3096,7 @@ impl ScreenshotEditorWindow { .border_color(theme.gray_3) .text_size(px(10.)) .text_color(theme.gray_9) - .child("Drag to reorder • Top = front"), + .child(tr::screenshot_editor::layers::reorder_hint()), ) } @@ -3104,7 +3143,7 @@ impl ScreenshotEditorWindow { .font_weight(FontWeight::MEDIUM) .text_color(theme.blue_11) .hover(|style| style.text_color(theme.blue_9)) - .child("Done") + .child(tr::screenshot_editor::annotation::done()) .on_click(cx.listener(|this, _, _window, cx| { cx.stop_propagation(); this.selected_annotation = None; @@ -3225,13 +3264,15 @@ impl ScreenshotEditorWindow { .flex_row() .items_center() .gap(px(8.)) - .children(BgTab::ALL.map(|(tab, label)| { + .children(BgTab::ALL.map(|tab| { let selected = tab == current; div() .id(gpui::SharedString::from(format!( - "screenshot-bg-tab-{label}" + "screenshot-bg-tab-{}", + tab.id() ))) .flex_1() + .min_w_0() .flex() .justify_center() .py(px(10.)) @@ -3250,7 +3291,7 @@ impl ScreenshotEditorWindow { .text_color(theme.gray_11) .hover(|style| style.border_color(theme.gray_7)) }) - .child(label) + .child(div().truncate().child(tab.label())) .on_click(cx.listener(move |this, _, window, cx| { cx.stop_propagation(); this.choose_bg_tab(tab, window, cx); @@ -3448,7 +3489,7 @@ impl ScreenshotEditorWindow { div() .text_size(px(13.)) .text_color(theme.gray_12) - .child("Click to select or drag and drop image"), + .child(tr::screenshot_editor::background::choose_image()), ) .on_click(cx.listener(|this, _, window, cx| { cx.stop_propagation(); @@ -3576,7 +3617,7 @@ impl ScreenshotEditorWindow { return div() .text_size(px(12.)) .text_color(theme.gray_11) - .child("Animated Gradient is rendered as a still image for screenshots. Choose another background to replace it.") + .child(tr::screenshot_editor::background::animated_gradient()) .into_any_element(); } let (from, to, angle) = match &self.project.background.source { @@ -3668,7 +3709,7 @@ impl ScreenshotEditorWindow { .flex_col() .gap(px(24.)) .child( - ui::Field::plain(&theme, "Background Image") + ui::Field::plain(&theme, tr::screenshot_editor::background::image_title()) .icon("icons/image.svg") .icon_size(px(16.)) .child(self.render_bg_tabs(cx)) @@ -3683,10 +3724,15 @@ impl ScreenshotEditorWindow { .child(tab_content), ) .child( - ui::Field::plain(&theme, "Background Blur") + ui::Field::plain(&theme, tr::screenshot_editor::background::blur_title()) .icon("icons/bg-blur.svg") .icon_size(px(16.)) - .child(self.slider_row("Blur", StyleSlider::Blur, "%", cx)), + .child(self.slider_row( + tr::screenshot_editor::style::blur(), + StyleSlider::Blur, + "%", + cx, + )), ) .into_any_element() } @@ -3694,7 +3740,12 @@ impl ScreenshotEditorWindow { fn render_padding_popover(&self, cx: &mut Context) -> AnyElement { div() .p(px(16.)) - .child(self.slider_row("Padding", StyleSlider::Padding, "px", cx)) + .child(self.slider_row( + tr::screenshot_editor::style::padding(), + StyleSlider::Padding, + "px", + cx, + )) .into_any_element() } @@ -3705,7 +3756,12 @@ impl ScreenshotEditorWindow { .flex() .flex_col() .gap(px(16.)) - .child(self.slider_row("Rounding", StyleSlider::Rounding, "px", cx)) + .child(self.slider_row( + tr::screenshot_editor::style::rounding(), + StyleSlider::Rounding, + "px", + cx, + )) .child( div() .flex() @@ -3715,7 +3771,7 @@ impl ScreenshotEditorWindow { div() .text_size(px(10.4)) .text_color(theme.gray_11) - .child("CORNER STYLE"), + .child(tr::screenshot_editor::corner::style()), ) .child( self.anchored( @@ -3766,35 +3822,38 @@ impl ScreenshotEditorWindow { fn render_shadow_popover(&self, cx: &mut Context) -> AnyElement { let theme = self.theme; let open = self.shadow_advanced.is_open(); - let body = div() - .mt(px(16.)) - .flex() - .flex_col() - .gap(px(24.)) - .font_weight(FontWeight::MEDIUM) - .child(ui::Field::plain(&theme, "Size").child(self.slider( - StyleSlider::ShadowSize, - None, - cx, - ))) - .child(ui::Field::plain(&theme, "Opacity").child(self.slider( - StyleSlider::ShadowOpacity, - None, - cx, - ))) - .child(ui::Field::plain(&theme, "Blur").child(self.slider( - StyleSlider::ShadowBlur, - None, - cx, - ))) - .into_any_element(); + let body = + div() + .mt(px(16.)) + .flex() + .flex_col() + .gap(px(24.)) + .font_weight(FontWeight::MEDIUM) + .child( + ui::Field::plain(&theme, tr::screenshot_editor::style::size_label()) + .child(self.slider(StyleSlider::ShadowSize, None, cx)), + ) + .child( + ui::Field::plain(&theme, tr::screenshot_editor::style::opacity()) + .child(self.slider(StyleSlider::ShadowOpacity, None, cx)), + ) + .child( + ui::Field::plain(&theme, tr::screenshot_editor::style::blur()) + .child(self.slider(StyleSlider::ShadowBlur, None, cx)), + ) + .into_any_element(); div() .p(px(16.)) .flex() .flex_col() .gap(px(16.)) - .child(self.slider_row("Shadow", StyleSlider::Shadow, "%", cx)) + .child(self.slider_row( + tr::screenshot_editor::style::shadow(), + StyleSlider::Shadow, + "%", + cx, + )) .child( div() .w_full() @@ -3811,7 +3870,7 @@ impl ScreenshotEditorWindow { .font_weight(FontWeight::MEDIUM) .text_color(theme.gray_12) .hover(|style| style.text_color(theme.gray_10)) - .child("Advanced shadow settings") + .child(tr::screenshot_editor::style::advanced_shadow()) .child( svg() .path("icons/chevron-down.svg") @@ -3854,19 +3913,19 @@ impl ScreenshotEditorWindow { .flex_col() .gap(px(16.)) .child( - ui::Field::plain(&theme, "Width") + ui::Field::plain(&theme, tr::screenshot_editor::style::width()) .icon("icons/enlarge.svg") .icon_size(px(16.)) .child(self.slider(StyleSlider::BorderWidth, None, cx)), ) .child( - ui::Field::plain(&theme, "Color") + ui::Field::plain(&theme, tr::screenshot_editor::style::color()) .icon("icons/image.svg") .icon_size(px(16.)) .child(self.render_rgb_input(HexTarget::BorderColor, color)), ) .child( - ui::Field::plain(&theme, "Opacity") + ui::Field::plain(&theme, tr::screenshot_editor::style::opacity()) .icon("icons/shadow.svg") .icon_size(px(16.)) .child(self.slider(StyleSlider::BorderOpacity, None, cx)), @@ -3889,7 +3948,7 @@ impl ScreenshotEditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(theme.gray_11) - .child("Border"), + .child(tr::screenshot_editor::style::border()), ) .child( ui::Toggle::plain(&theme, "screenshot-border-toggle", enabled).on_click( @@ -4167,7 +4226,7 @@ impl ScreenshotEditorWindow { div() .text_size(px(13.)) .text_color(theme.gray_11) - .child("Loading preview...") + .child(tr::screenshot_editor::loading_preview()) .into_any_element(), ), }) @@ -4210,7 +4269,7 @@ impl ScreenshotEditorWindow { }]) .child(kbd_tooltip( &theme, - "Zoom Out", + tr::screenshot_editor::zoom_out(), &["meta", "-"], ui::EditorButton::plain(&theme, "screenshot-zoom-out") .left_icon("icons/zoom-out.svg") @@ -4223,7 +4282,7 @@ impl ScreenshotEditorWindow { .child(self.slider(StyleSlider::Zoom, Some(px(80.)), cx)) .child(kbd_tooltip( &theme, - "Zoom In", + tr::screenshot_editor::zoom_in(), &["meta", "+"], ui::EditorButton::plain(&theme, "screenshot-zoom-in") .left_icon("icons/zoom-in.svg") diff --git a/apps/desktop-gpui/src/screenshot_export.rs b/apps/desktop-gpui/src/screenshot_export.rs index 525858d4fea..6c99da0ba90 100644 --- a/apps/desktop-gpui/src/screenshot_export.rs +++ b/apps/desktop-gpui/src/screenshot_export.rs @@ -23,6 +23,7 @@ use std::sync::{Mutex, OnceLock}; use cap_gpui_kernels::{codec, screenshot as pixels}; use cap_project::{Annotation, AnnotationType, ProjectConfiguration}; +use crate::i18n::tr; use crate::screenshot_annotations::{self as annotations, Rect}; use crate::screenshot_editor::has_no_visible_background; @@ -226,8 +227,7 @@ pub fn flatten_onto_white(out: &Composited) -> Composited { /// frame straight to PNG" path, still callable through /// `screenshot_editor::render_export_png`. pub fn encode_rgba_png(rgba: &[u8], width: u32, height: u32) -> Result, String> { - codec::encode_png_rgba(rgba, width, height) - .map_err(|e| format!("Failed to encode screenshot export: {e}")) + codec::encode_png_rgba(rgba, width, height).map_err(tr::screenshot_editor::error::encode) } fn encode_png(out: &Composited) -> Result, String> { @@ -240,7 +240,7 @@ fn encode_png(out: &Composited) -> Result, String> { fn encode_jpeg(out: &Composited) -> Result, String> { let rgb = codec::flatten_rgba_to_rgb_over_white(&out.rgba); codec::encode_jpeg_rgb(&rgb, out.width, out.height, 90) - .map_err(|e| format!("Failed to encode screenshot export: {e}")) + .map_err(tr::screenshot_editor::error::encode) } /// An encoded export, tagged with the content type the upload sends. @@ -320,8 +320,7 @@ fn stable_value(value: &serde_json::Value) -> serde_json::Value { } fn stable_stringify(value: &serde_json::Value) -> Result { - serde_json::to_string(&stable_value(value)) - .map_err(|e| format!("Failed to serialize screenshot config: {e}")) + serde_json::to_string(&stable_value(value)).map_err(tr::screenshot_editor::error::serialize) } /// The content hash the share flow compares against `sharing.contentHash`: @@ -330,8 +329,7 @@ fn stable_stringify(value: &serde_json::Value) -> Result { /// simply misses and re-uploads against the same video id. pub fn content_hash(config: &ProjectConfiguration) -> Result { use sha2::Digest as _; - let value = serde_json::to_value(config) - .map_err(|e| format!("Failed to serialize screenshot config: {e}"))?; + let value = serde_json::to_value(config).map_err(tr::screenshot_editor::error::serialize)?; let payload = stable_stringify(&value)?; let digest = sha2::Sha256::digest(payload.as_bytes()); let mut hex = String::with_capacity(64); From ff54451b5c53e67d45f8934f7fc2b204b0ec9cbf Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:43:09 +0000 Subject: [PATCH 03/25] feat(desktop-gpui): localize overlays, dialogs and permission onboarding --- apps/desktop-gpui/src/app_windows.rs | 79 +++++------ apps/desktop-gpui/src/deeplink.rs | 4 +- apps/desktop-gpui/src/devices.rs | 4 +- apps/desktop-gpui/src/diagnostics.rs | 95 ++++++++----- apps/desktop-gpui/src/hotkeys.rs | 26 ++-- apps/desktop-gpui/src/import.rs | 47 ++++--- apps/desktop-gpui/src/main_window.rs | 2 +- apps/desktop-gpui/src/mode_select_window.rs | 11 +- .../src/onboarding_window/stage.rs | 125 ++++++++++-------- apps/desktop-gpui/src/permission_guide.rs | 31 ++--- apps/desktop-gpui/src/permissions.rs | 11 ++ apps/desktop-gpui/src/platform.rs | 28 ++-- apps/desktop-gpui/src/platform/windows.rs | 17 ++- apps/desktop-gpui/src/session.rs | 10 +- apps/desktop-gpui/src/sign_in.rs | 3 +- apps/desktop-gpui/src/target_overlay.rs | 100 ++++++++------ apps/desktop-gpui/src/teleprompter_window.rs | 16 ++- apps/desktop-gpui/src/transcription.rs | 29 ++-- apps/desktop-gpui/src/upload/queue.rs | 17 +-- 19 files changed, 365 insertions(+), 290 deletions(-) diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs index aafc67eb919..cdcb019da92 100644 --- a/apps/desktop-gpui/src/app_windows.rs +++ b/apps/desktop-gpui/src/app_windows.rs @@ -25,6 +25,7 @@ use crate::{ controls_window::ControlsWindow, editor_timeline, editor_window::{self, EditorWindow}, + i18n::tr, main_window::{MainWindow, Mode, TargetType}, mode_select_window::{self, ModeSelectWindow}, onboarding_window::{self, OnboardingWindow}, @@ -1917,7 +1918,7 @@ fn reopens_overlays(armed: Option, mode: TargetType, recording_mode: pub(crate) fn reject_unavailable_window(cx: &mut App) { dismiss_target_overlays(cx); RecordingSession::global(cx).update(cx, |session, cx| { - session.error = Some("The selected window is no longer available. Select it again.".into()); + session.error = Some(tr::dialog::capture::window_unavailable().into()); cx.notify(); }); } @@ -2406,9 +2407,7 @@ pub fn start_recording_from_overlay(target: ScreenCaptureTarget, cx: &mut App) { let Some(handle) = handle else { dismiss_target_overlays(cx); RecordingSession::global(cx).update(cx, |session, cx| { - session.error = Some( - "The selected area changed or is no longer available. Select it again.".into(), - ); + session.error = Some(tr::dialog::capture::area_unavailable().into()); cx.notify(); }); return; @@ -3093,7 +3092,7 @@ fn linux_camera_presentation( && y >= 0 && x + i64::from(rect.width) <= i64::from(capture.width) && y + i64::from(rect.height) <= i64::from(capture.height), - "Move the whole camera preview inside the capture area before starting Instant recording" + tr::dialog::capture::camera_outside_area() ); let radius = snapshot.corner_radius_pixels.round(); anyhow::ensure!( @@ -3147,11 +3146,12 @@ pub(crate) fn refresh_linux_instant_camera( } anyhow::ensure!( !cap_recording::screenshot::uses_wayland_portal(), - "Instant camera overlays require an X11 desktop on Linux" + tr::dialog::capture::camera_needs_x11() ); - let camera = cx.global::().camera.ok_or_else(|| { - anyhow::anyhow!("Open the selected camera preview before Instant recording") - })?; + let camera = cx + .global::() + .camera + .ok_or_else(|| anyhow::anyhow!(tr::dialog::capture::camera_preview_closed()))?; let snapshot = camera.update(cx, |camera, window, _| camera.recording_snapshot(window))??; let capture = linux_capture_bounds(&config.target)?; let presentation = linux_camera_presentation(snapshot, capture)?; @@ -3183,11 +3183,11 @@ pub(crate) fn clean_capture_camera_message(cx: &App) -> &'static str { .and_then(|lease| lease.config.as_ref()) .is_some_and(|config| config.mode == RecordingMode::Instant) { - return "Cap will hide its preview and controls. Any selected camera will appear at its preview position with the selected shape and effects. Keep the whole preview inside the capture area. Opening Cap will stop this Instant recording before showing its controls."; + return tr::dialog::capture::camera_notice_instant(); } #[cfg(not(target_os = "linux"))] let _ = cx; - "Cap will hide its camera preview and recording controls. Any selected camera will keep recording as a separate editable track." + tr::dialog::capture::camera_notice() } pub fn clean_capture_owned(cx: &App) -> bool { @@ -3695,7 +3695,7 @@ fn begin_retained_capture_restore(cx: &mut App) { } tracing::error!(%error, "Recording UI restoration or Stop cleanup is unconfirmed"); RecordingSession::global(cx).update(cx, |session, cx| { - session.error = Some(format!("Recording cleanup is incomplete: {error}. A new recording is blocked.")); + session.error = Some(tr::dialog::capture::cleanup_incomplete(error)); cx.notify(); }); } @@ -3786,12 +3786,7 @@ pub fn handle_clean_capture_shortcut(state: global_hotkey::HotKeyState, cx: &mut .is_none_or(|lease| lease.requested_inputs != current_inputs) { restore_clean_capture_ui(cx); - report_clean_capture_error( - anyhow::anyhow!( - "Selected devices changed. Start again to confirm the new recording inputs." - ), - cx, - ); + report_clean_capture_error(anyhow::anyhow!(tr::dialog::capture::devices_changed()), cx); return; } if let Err(error) = refresh_linux_instant_camera(&mut config, cx) { @@ -3812,7 +3807,10 @@ pub fn handle_clean_capture_shortcut(state: global_hotkey::HotKeyState, cx: &mut }; let visibility = gpui_tokio::Tokio::spawn(cx, visibility); cx.spawn(async move |cx| { - let result = visibility.await.map_err(anyhow::Error::from).and_then(|result| result); + let result = visibility + .await + .map_err(anyhow::Error::from) + .and_then(|result| result); if let Err(error) = result { cx.update(|cx| { if clean_capture_generation_matches(generation, cx) { @@ -3837,7 +3835,7 @@ pub fn handle_clean_capture_shortcut(state: global_hotkey::HotKeyState, cx: &mut { restore_clean_capture_ui(cx); report_clean_capture_error( - anyhow::anyhow!("Selected devices changed before recording started. Start again to confirm the inputs."), + anyhow::anyhow!(tr::dialog::capture::devices_changed_before_start()), cx, ); return; @@ -3870,9 +3868,7 @@ pub fn begin_recording(mut config: StartConfig, cx: &mut App) { if let Some(lease) = cx.global::().clean_capture.as_ref() { if let Some(error) = lease.restoration_error.clone() { session.update(cx, |session, cx| { - session.error = Some(format!( - "Recording cleanup is incomplete: {error}. A new recording is blocked." - )); + session.error = Some(tr::dialog::capture::cleanup_incomplete(error)); cx.notify(); }); } @@ -5469,11 +5465,7 @@ fn load_editor_project_candidate( Err(join_error) => { handle .update(cx, |view, window, cx| { - view.set_error( - format!("Opening this recording failed: {join_error}"), - window, - cx, - ) + view.set_error(tr::dialog::open_recording_failed(join_error), window, cx) }) .ok(); return; @@ -6255,34 +6247,29 @@ fn editor_deletion_allowed( root.canonicalize() .is_ok_and(|root| project_path != root && project_path.starts_with(root)) }) { - return Err( - "This recording is outside the recordings library. Its files were not changed.".into(), - ); + return Err(tr::dialog::delete_recording::outside_library().into()); } let windows = cx.global::(); if windows.deleting_editors.contains(project_path) { - return Err("This recording is already being deleted.".into()); + return Err(tr::dialog::delete_recording::already_deleting().into()); } if !windows .editors .iter() .any(|(path, current)| path == project_path && *current == handle) { - return Err( - "The editor changed while deletion was being confirmed. The recording was not deleted." - .into(), - ); + return Err(tr::dialog::delete_recording::editor_changed().into()); } let session = RecordingSession::global(cx); if session.read(cx).phase != Phase::Idle || session.read(cx).editor_recording_target().is_some() || clean_capture_owned(cx) { - return Err("Finish or cancel the recording before deleting this project.".into()); + return Err(tr::dialog::delete_recording::recording_active().into()); } let editor = handle .read(cx) - .map_err(|_| "The editor has already closed. The recording was not deleted.")?; + .map_err(|_| tr::dialog::delete_recording::editor_closed())?; if let Some(reason) = editor.deletion_blocked_reason() { return Err(reason.into()); } @@ -6297,16 +6284,16 @@ pub(crate) fn request_editor_deletion( let key = editor_key(&project_path); cx.spawn(async move |cx| { if let Err(error) = cx.update(|cx| editor_deletion_allowed(&key, handle, cx)) { - platform::alert_dialog("Recording retained", &error); + platform::alert_dialog(tr::dialog::delete_recording::retained_title(), &error); return; } - if !platform::confirm_dialog("Cap", "Are you sure you want to delete this recording?", "Yes", "No", false) { + if !platform::confirm_dialog("Cap", tr::main::delete_recording_confirm(), tr::common::yes(), tr::common::no(), false) { return; } let prepared = cx.update(|cx| { editor_deletion_allowed(&key, handle, cx)?; let ownership = cap_recording::upload_resume::UploadLock::acquire(&key) - .map_err(|error| format!("This recording is still in use. Finish or cancel its upload and try again: {error}"))?; + .map_err(tr::dialog::delete_recording::in_use)?; let windows = cx.global_mut::(); windows.deleting_editors.insert(key.clone()); windows.editors.retain(|(_, current)| *current != handle); @@ -6328,14 +6315,14 @@ pub(crate) fn request_editor_deletion( } Err(error) => { cx.global_mut::().deleting_editors.remove(&key); - Err(format!("Could not close the editor: {error}")) + Err(tr::dialog::delete_recording::close_failed(error)) } } }); let (instance, ownership) = match prepared { Ok(prepared) => prepared, Err(error) => { - platform::alert_dialog("Recording retained", &error); + platform::alert_dialog(tr::dialog::delete_recording::retained_title(), &error); return; } }; @@ -6364,17 +6351,17 @@ pub(crate) fn request_editor_deletion( let result = match futures_util::future::select(&mut task, &mut notice).await { futures_util::future::Either::Left((result, _)) => result, futures_util::future::Either::Right(_) => { - platform::alert_dialog("Deletion is still finishing", "Cap is waiting for recording cleanup to finish before completing this deletion."); + platform::alert_dialog(tr::dialog::delete_recording::finishing_title(), tr::dialog::delete_recording::finishing_body()); task.await } - }.unwrap_or_else(|error| Err(format!("Recording deletion failed: {error}"))); + }.unwrap_or_else(|error| Err(tr::dialog::delete_recording::failed(error))); cx.update(|cx| { cx.global_mut::().deleting_editors.remove(&key); refresh_library_after_delete(cx); }); if let Err(error) = result { tracing::error!(path = %key.display(), %error, "recording deletion failed"); - platform::alert_dialog("Could not delete recording", &error); + platform::alert_dialog(tr::dialog::delete_recording::failed_title(), &error); } }).detach(); } diff --git a/apps/desktop-gpui/src/deeplink.rs b/apps/desktop-gpui/src/deeplink.rs index 8a1523477da..74e4bb57591 100644 --- a/apps/desktop-gpui/src/deeplink.rs +++ b/apps/desktop-gpui/src/deeplink.rs @@ -606,7 +606,9 @@ fn camera_selection(id: DeviceOrModelID) -> SelectedCamera { .map(|info| info.display_name().to_string()) .unwrap_or_else(|| match &id { DeviceOrModelID::DeviceID(device) => device.clone(), - DeviceOrModelID::ModelID(_) => "Camera".to_string(), + DeviceOrModelID::ModelID(_) => { + crate::i18n::tr::main::device::fallback_camera().to_string() + } }), id, } diff --git a/apps/desktop-gpui/src/devices.rs b/apps/desktop-gpui/src/devices.rs index 745d9d46bc0..19305cd8184 100644 --- a/apps/desktop-gpui/src/devices.rs +++ b/apps/desktop-gpui/src/devices.rs @@ -63,8 +63,8 @@ impl MicrophoneOption { let sample_rate = self.sample_rate?; let khz = sample_rate as f32 / 1000.; let layout = match self.channels { - Some(1) => "Mono".to_string(), - Some(2) => "Stereo".to_string(), + Some(1) => crate::i18n::tr::main::device::format::mono().to_string(), + Some(2) => crate::i18n::tr::main::device::format::stereo().to_string(), Some(n) => format!("{n}ch"), None => return Some(format!("{khz}kHz")), }; diff --git a/apps/desktop-gpui/src/diagnostics.rs b/apps/desktop-gpui/src/diagnostics.rs index af250bff901..2b65af2d1b7 100644 --- a/apps/desktop-gpui/src/diagnostics.rs +++ b/apps/desktop-gpui/src/diagnostics.rs @@ -29,6 +29,7 @@ use std::{ use serde_json::{Value, json}; +use crate::i18n::tr; use crate::{permissions, store}; /// The rolling log file's prefix. Deliberately *not* the Tauri app's @@ -261,7 +262,7 @@ impl SyncMode { match self { Self::Studio => "Studio", Self::Instant => "Instant", - Self::Both => "Both", + Self::Both => tr::diagnostics::mode_both(), } } } @@ -325,7 +326,7 @@ impl RunGuard { DIAGNOSTIC_RUNNING .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) .map(|_| Self) - .map_err(|_| ALREADY_RUNNING.to_string()) + .map_err(|_| tr::diagnostics::already_running().to_string()) } } @@ -335,8 +336,6 @@ impl Drop for RunGuard { } } -pub const ALREADY_RUNNING: &str = "A diagnostic is already running."; - /// Kills the self-test if it is dropped before the run's own kill/wait /// sequence. `std::process::Child` does not kill on drop, and an orphaned /// self-test owns the screen with a fullscreen flashing window while it is @@ -409,24 +408,51 @@ pub const CANCEL_STAGE: &str = "cancelling"; /// The user-facing words for a stage. The CLI's stage names are stable API but /// they are not sentences. pub fn stage_label(stage: &str, mode: Option<&str>) -> String { - let leg = match mode { - Some("studio") => " studio", - Some("instant") => " instant", - _ => "", + let leg = |both: fn() -> &'static str, + studio: fn() -> &'static str, + instant: fn() -> &'static str| match mode { + Some("studio") => studio(), + Some("instant") => instant(), + _ => both(), }; match stage { - "collecting" => format!("Preparing the{leg} test..."), - "recording" => format!("Recording the{leg} test..."), - "pattern-run" => format!("Playing the test pattern{leg}..."), - "remuxing" => format!("Finalizing the{leg} recording..."), - "analyzing" => format!("Analyzing the{leg} recording..."), - "exporting" => format!("Exporting the{leg} recording..."), - "done" => "Wrapping up the sync test...".to_string(), - START_STAGE => "Starting the diagnostic...".to_string(), - COLLECT_STAGE => "Collecting system information...".to_string(), - CANCEL_STAGE => "Cancelling...".to_string(), - other => format!("{other}..."), + "collecting" => leg( + tr::diagnostics::stage::preparing, + tr::diagnostics::stage::preparing_studio, + tr::diagnostics::stage::preparing_instant, + ), + "recording" => leg( + tr::diagnostics::stage::recording, + tr::diagnostics::stage::recording_studio, + tr::diagnostics::stage::recording_instant, + ), + "pattern-run" => leg( + tr::diagnostics::stage::pattern, + tr::diagnostics::stage::pattern_studio, + tr::diagnostics::stage::pattern_instant, + ), + "remuxing" => leg( + tr::diagnostics::stage::finalizing, + tr::diagnostics::stage::finalizing_studio, + tr::diagnostics::stage::finalizing_instant, + ), + "analyzing" => leg( + tr::diagnostics::stage::analyzing, + tr::diagnostics::stage::analyzing_studio, + tr::diagnostics::stage::analyzing_instant, + ), + "exporting" => leg( + tr::diagnostics::stage::exporting, + tr::diagnostics::stage::exporting_studio, + tr::diagnostics::stage::exporting_instant, + ), + "done" => tr::diagnostics::stage::done(), + START_STAGE => tr::diagnostics::stage::starting(), + COLLECT_STAGE => tr::diagnostics::stage::collecting(), + CANCEL_STAGE => tr::diagnostics::stage::cancelling(), + other => return format!("{other}..."), } + .to_string() } fn trim_to_tail(text: &mut String, max_bytes: usize) { @@ -463,7 +489,7 @@ pub fn run_sync_test( mut on_stage: impl FnMut(Stage), ) -> Result { if cancel.load(Ordering::Relaxed) { - return Err(CANCELLED.to_string()); + return Err(tr::diagnostics::cancelled().to_string()); } // Wrapped before anything else can fail: every early return from here on @@ -475,14 +501,14 @@ pub fn run_sync_test( .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .map_err(|error| format!("Failed to start {}: {error}", binary.display()))?, + .map_err(|error| tr::diagnostics::error::start_failed(binary.display(), error))?, ); let stdout = child .0 .stdout .take() - .ok_or_else(|| "The self-test produced no output stream".to_string())?; + .ok_or_else(|| tr::diagnostics::error::no_output().to_string())?; let stderr = child.0.stderr.take(); let stderr_tail = Arc::new(Mutex::new(String::new())); @@ -542,7 +568,7 @@ pub fn run_sync_test( .and_then(|mut child| child.0.wait().map_err(|error| error.to_string())); if cancel.load(Ordering::Relaxed) { - return Err(CANCELLED.to_string()); + return Err(tr::diagnostics::cancelled().to_string()); } if let Some(report) = report { return Ok(report); @@ -553,8 +579,8 @@ pub fn run_sync_test( .map(|tail| tail.trim().to_string()) .unwrap_or_default(); let status = match status { - Ok(status) => format!("The self-test exited with {status}"), - Err(error) => format!("The self-test could not be waited on: {error}"), + Ok(status) => tr::diagnostics::error::exited(status), + Err(error) => tr::diagnostics::error::wait_failed(error), }; Err( [Some(status), error_line, (!tail.is_empty()).then_some(tail)] @@ -565,8 +591,6 @@ pub fn run_sync_test( ) } -pub const CANCELLED: &str = "The diagnostic was cancelled."; - // --------------------------------------------------------------------------- // The environment report // --------------------------------------------------------------------------- @@ -824,8 +848,7 @@ fn write_report_into(dir: &Path, report: &Value) -> Result { } fn write_report_at(dir: &Path, report: &Value, stamp: u64) -> Result { - std::fs::create_dir_all(dir) - .map_err(|error| format!("Failed to create the diagnostics folder: {error}"))?; + std::fs::create_dir_all(dir).map_err(tr::diagnostics::error::create_folder)?; // A run with no sidecar finishes in well under a second, so two of them // would otherwise write the same name and the second would overwrite the // first. The prefix and suffix stay exactly as the pruning filter expects. @@ -834,10 +857,9 @@ fn write_report_at(dir: &Path, report: &Value, stamp: u64) -> Result String { if let Some(stop) = cx.try_global::() { return stop.label.clone(); } - "Press and release Ctrl+Shift+F9 to start. Use the same shortcut to stop. This temporary shortcut does not change your settings.".into() + tr::dialog::capture::shortcut_hint("Ctrl+Shift+F9") } #[cfg(target_os = "linux")] fn clean_capture_shortcut_label(trigger: &str) -> String { - let trigger = readable_trigger(trigger); - format!( - "Press and release {trigger} to start. Use the same shortcut to stop. This temporary shortcut does not change your settings." - ) + tr::dialog::capture::shortcut_hint(readable_trigger(trigger)) } /// Portal backends describe triggers in their own words: GNOME says @@ -391,7 +389,7 @@ fn reserve_wayland_stop(generation: u64, cx: &mut App) -> anyhow::Result<()> { gpui_tokio::Tokio::handle(cx).spawn(run_wayland_stop(generation, cancelled, events)); cx.set_global(WaylandStop { generation, - label: "Approve a recording shortcut, or activate the Cap Stop tray icon when it appears. Recording has not started.".into(), + label: tr::dialog::capture::approve_shortcut().into(), ready: false, cancel, worker: Some(worker), @@ -564,7 +562,7 @@ async fn run_tray_stop( })?; let received = tray.events(); let outcome = async { - events.send(WaylandStopEvent::Ready("Activate the Cap Stop tray icon to start. Activate the same icon again to stop. Recording will not start until you use that control.".into()))?; + events.send(WaylandStopEvent::Ready(tr::dialog::capture::tray_stop_hint().into()))?; loop { tokio::select! { biased; @@ -608,7 +606,7 @@ async fn run_portal_stop( let changed = portal.receive_shortcuts_changed().await?; let closed = session.receive_closed().await?; futures_util::pin_mut!(activated, deactivated, changed, closed); - let shortcuts = [NewShortcut::new(&shortcut_id, "Start or stop this Cap recording").preferred_trigger("CTRL+SHIFT+F9")]; + let shortcuts = [NewShortcut::new(&shortcut_id, tr::dialog::capture::shortcut_description()).preferred_trigger("CTRL+SHIFT+F9")]; let bind = portal.bind_shortcuts(&session, &shortcuts, None); let response = tokio::select! { _ = cancel.recv_async() => return Ok(()), @@ -675,9 +673,7 @@ pub fn reserve_clean_capture_stop( let key = HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::F9); let actions = actions_for(&hotkeys.bindings, key.id()); if clean_capture_shortcut_conflicts(&actions) { - anyhow::bail!( - "Ctrl+Shift+F9 is assigned to another Cap action. Change that shortcut before recording." - ); + anyhow::bail!(tr::dialog::capture::shortcut_conflict()); } if actions.is_empty() { let registered = hotkeys @@ -987,11 +983,11 @@ fn start_from_hotkey(mode: Mode, cx: &mut App) { main.update(cx, |_, window, cx| { window.prompt( PromptLevel::Warning, - "No microphone detected", - Some("This recording will not include your voice."), + tr::main::mic_warning::title(), + Some(tr::dialog::no_microphone_body()), &[ - PromptButton::ok("Record without microphone"), - PromptButton::cancel("Cancel"), + PromptButton::ok(tr::main::mic_warning::record_without()), + PromptButton::cancel(tr::common::cancel()), ], cx, ) diff --git a/apps/desktop-gpui/src/import.rs b/apps/desktop-gpui/src/import.rs index 165d7244dcf..abcede4716a 100644 --- a/apps/desktop-gpui/src/import.rs +++ b/apps/desktop-gpui/src/import.rs @@ -25,6 +25,8 @@ use cap_project::{ use ffmpeg::{ChannelLayout, codec as avcodec, format as avformat}; use gpui::{App, Global}; +use crate::i18n::tr; + /// `import.rs:38-44` in the Tauri binary. pub const VIDEO_IMPORT_EXTENSIONS: &[&str] = &["mp4", "mov", "avi", "mkv", "webm", "wmv", "m4v", "flv"]; @@ -292,7 +294,11 @@ pub fn pick_and_import_video(cx: &mut App) { cx.spawn(async move |cx| { // Blocking modal, so from a spawned task with no borrow held -- the // `save_file_panel` rule. - let Some(path) = pick_import_file(&[("Video Files", VIDEO_IMPORT_EXTENSIONS)], cx).await + let Some(path) = pick_import_file( + &[(tr::import::filter::video(), VIDEO_IMPORT_EXTENSIONS)], + cx, + ) + .await else { return; }; @@ -303,7 +309,11 @@ pub fn pick_and_import_video(cx: &mut App) { pub fn pick_and_import_image(cx: &mut App) { cx.spawn(async move |cx| { - let Some(path) = pick_import_file(&[("Image Files", IMAGE_IMPORT_EXTENSIONS)], cx).await + let Some(path) = pick_import_file( + &[(tr::import::filter::image(), IMAGE_IMPORT_EXTENSIONS)], + cx, + ) + .await else { return; }; @@ -318,9 +328,9 @@ pub fn pick_and_import_media(cx: &mut App) { cx.spawn(async move |cx| { let Some(path) = pick_import_file( &[ - ("Media Files", MEDIA_IMPORT_EXTENSIONS), - ("Video Files", VIDEO_IMPORT_EXTENSIONS), - ("Image Files", IMAGE_IMPORT_EXTENSIONS), + (tr::import::filter::media(), MEDIA_IMPORT_EXTENSIONS), + (tr::import::filter::video(), VIDEO_IMPORT_EXTENSIONS), + (tr::import::filter::image(), IMAGE_IMPORT_EXTENSIONS), ], cx, ) @@ -334,7 +344,7 @@ pub fn pick_and_import_media(cx: &mut App) { cx.update(|cx| import_image_from_path(path, cx)); } else { tracing::error!(path = %path.display(), "unsupported media import path"); - show_error_dialog("Unsupported media file.".to_string()); + show_error_dialog(tr::import::unsupported().to_string()); } }) .detach(); @@ -426,12 +436,15 @@ fn show_import_error(kind: ImportKind, message: &str) { ImportKind::Image => "image", }; tracing::error!("failed to import {media}: {message}"); - show_error_dialog(format!("Failed to import {media}: {message}")); + show_error_dialog(match kind { + ImportKind::Video => tr::import::video_failed(message), + ImportKind::Image => tr::import::image_failed(message), + }); } fn show_error_dialog(description: String) { let _ = rfd::MessageDialog::new() - .set_title("Import Error") + .set_title(tr::import::error_title()) .set_description(description) .set_level(rfd::MessageLevel::Error) .set_buttons(rfd::MessageButtons::Ok) @@ -478,7 +491,7 @@ fn run_video_import(source_path: &Path, tx: &flume::Sender) { pretty_name: &project_name, }; - sink.send(ImportStage::Probing, 0.0, "Analyzing video file..."); + sink.send(ImportStage::Probing, 0.0, tr::import::progress::analyzing()); match probe_video_can_decode(source_path) { Ok(true) => {} Ok(false) => { @@ -534,7 +547,11 @@ fn run_video_import(source_path: &Path, tx: &flume::Sender) { return; } - sink.send(ImportStage::Converting, 0.0, "Starting conversion..."); + sink.send( + ImportStage::Converting, + 0.0, + tr::import::progress::starting(), + ); let result = transcode_video( source_path, &output_video_path, @@ -544,7 +561,7 @@ fn run_video_import(source_path: &Path, tx: &flume::Sender) { sink.send( ImportStage::Converting, progress, - &format!("Converting video... {}%", (progress * 100.0) as u32), + &tr::import::progress::converting((progress * 100.0) as u32), ); }, None, @@ -583,7 +600,7 @@ fn run_video_import(source_path: &Path, tx: &flume::Sender) { sink.send( ImportStage::Finalizing, 0.95, - "Creating project metadata...", + tr::import::progress::metadata(), ); // `import.rs:1490-1504`: an Opus file this small is headers with no @@ -618,7 +635,7 @@ fn run_video_import(source_path: &Path, tx: &flume::Sender) { tracing::warn!("could not write the imported video's thumbnail: {error}"); } - sink.send(ImportStage::Complete, 1.0, "Import complete!"); + sink.send(ImportStage::Complete, 1.0, tr::import::progress::complete()); tracing::info!(path = %project_path.display(), "video import complete"); } @@ -665,7 +682,7 @@ fn run_image_import(source_path: &Path, tx: &flume::Sender) { pretty_name: &project_name, }; - sink.send(ImportStage::Probing, 0.0, "Importing image..."); + sink.send(ImportStage::Probing, 0.0, tr::import::progress::image()); let (width, height, rgba) = match decode_image_rgba(source_path) { Ok(decoded) => decoded, @@ -729,7 +746,7 @@ fn run_image_import(source_path: &Path, tx: &flume::Sender) { return; } - sink.send(ImportStage::Complete, 1.0, "Import complete!"); + sink.send(ImportStage::Complete, 1.0, tr::import::progress::complete()); tracing::info!(path = %project_path.display(), "image import complete"); } diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 48989b3b09f..9a4e77a3ee2 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -4069,7 +4069,7 @@ impl MainWindow { .id("panel-back") .when(self.keyboard_nav, ui::keyboard_nav::stop) .role(gpui::Role::Button) - .aria_label("Back") + .aria_label(tr::common::back()) .flex() .flex_row() .items_center() diff --git a/apps/desktop-gpui/src/mode_select_window.rs b/apps/desktop-gpui/src/mode_select_window.rs index 739576a183e..122c7e6df7f 100644 --- a/apps/desktop-gpui/src/mode_select_window.rs +++ b/apps/desktop-gpui/src/mode_select_window.rs @@ -13,6 +13,7 @@ use gpui::{ div, prelude::FluentBuilder, px, svg, }; +use crate::i18n::tr; use crate::theme::LocaleFont as _; use crate::{main_window::Mode, theme::Theme, ui::keyboard_nav}; @@ -31,9 +32,9 @@ impl Mode { /// app carries all three; so do we. fn card_description(self) -> &'static str { match self { - Self::Instant => "Share instantly with a link. Uploads as you record.", - Self::Studio => "Highest quality local recording for editing later.", - Self::Screenshot => "Capture and annotate screenshots instantly.", + Self::Instant => tr::dialog::mode_select::instant(), + Self::Studio => tr::dialog::mode_select::studio(), + Self::Screenshot => tr::dialog::mode_select::screenshot(), } } } @@ -292,14 +293,14 @@ impl Render for ModeSelectWindow { .text_size(px(20.)) .font_weight(FontWeight::BOLD) .text_color(Hsla::from(theme.gray_12)) - .child("Choose Recording Mode"), + .child(tr::dialog::mode_select::title()), ) .child( // `text-sm text-gray-11` div() .text_size(px(14.)) .text_color(Hsla::from(theme.gray_11)) - .child("Select how you want to capture your screen"), + .child(tr::dialog::mode_select::subtitle()), ), ) .child( diff --git a/apps/desktop-gpui/src/onboarding_window/stage.rs b/apps/desktop-gpui/src/onboarding_window/stage.rs index 68396015e58..cc30fa10e10 100644 --- a/apps/desktop-gpui/src/onboarding_window/stage.rs +++ b/apps/desktop-gpui/src/onboarding_window/stage.rs @@ -15,6 +15,7 @@ use gpui::{ use super::OnboardingWindow; use crate::doodle::{self, Doodle, Palette}; +use crate::i18n::tr; use crate::onboarding_doodles as art; use crate::permissions::{self, OSPermission, OSPermissionStatus}; use crate::permissions_ui::RowAction; @@ -146,35 +147,35 @@ struct Copy { fn copy_for(permission: OSPermission) -> Copy { match permission { OSPermission::ScreenRecording => Copy { - title: "Let Cap see your screen", - body: "Screen Recording lets Cap capture your displays, windows and system audio.", - done_title: "Screen recording is on", - done_body: "Cap can capture your screen.", - allow: "Allow screen recording", + title: tr::permission::onboarding::screen::title(), + body: tr::permission::onboarding::screen::body(), + done_title: tr::permission::onboarding::screen::done_title(), + done_body: tr::permission::onboarding::screen::done_body(), + allow: tr::permission::onboarding::screen::allow(), doodle: &art::SCREEN, }, OSPermission::Accessibility => Copy { - title: "Let Cap follow your cursor", - body: "Accessibility lets Cap see your clicks and cursor, on this Mac only, so auto zoom knows where to look.", - done_title: "Accessibility is on", - done_body: "Auto zoom can follow your clicks.", - allow: "Allow accessibility", + title: tr::permission::onboarding::accessibility::title(), + body: tr::permission::onboarding::accessibility::body(), + done_title: tr::permission::onboarding::accessibility::done_title(), + done_body: tr::permission::onboarding::accessibility::done_body(), + allow: tr::permission::onboarding::accessibility::allow(), doodle: &art::CURSOR, }, OSPermission::Microphone => Copy { - title: "Let Cap hear you", - body: "Add your voice to your recordings. You can skip this and turn it on later.", - done_title: "Microphone is on", - done_body: "Your voice can be in your recordings.", - allow: "Allow microphone", + title: tr::permission::onboarding::microphone::title(), + body: tr::permission::onboarding::microphone::body(), + done_title: tr::permission::onboarding::microphone::done_title(), + done_body: tr::permission::onboarding::microphone::done_body(), + allow: tr::permission::onboarding::microphone::allow(), doodle: &art::MICROPHONE, }, OSPermission::Camera => Copy { - title: "Let Cap see you", - body: "Show yourself in a corner of your recordings. Skip it if you don't need it.", - done_title: "Camera is on", - done_body: "You can add your camera to any recording.", - allow: "Allow camera", + title: tr::permission::onboarding::camera::title(), + body: tr::permission::onboarding::camera::body(), + done_title: tr::permission::onboarding::camera::done_title(), + done_body: tr::permission::onboarding::camera::done_body(), + allow: tr::permission::onboarding::camera::allow(), doodle: &art::CAMERA, }, } @@ -329,13 +330,13 @@ impl OnboardingWindow { fn render_checklist(&self, paper: &Paper, cx: &mut Context) -> Div { let (title, lede) = if self.permissions_only { ( - "Cap needs a permission back", - "Something was switched off in System Settings. Turn it back on to keep recording.", + tr::permission::onboarding::checklist::regained_title(), + tr::permission::onboarding::checklist::regained_lede(), ) } else { ( - "Let's get Cap ready", - "macOS asks you to OK a few things first. It only takes a minute.", + tr::permission::onboarding::checklist::title(), + tr::permission::onboarding::checklist::lede(), ) }; let stage = self.current_stage(); @@ -398,15 +399,15 @@ impl OnboardingWindow { Mark::Skipped => &art::MARK_SKIPPED, }; let (caption, caption_color) = if status.permitted() { - ("Allowed", paper.green) + (tr::permission::onboarding::status::allowed(), paper.green) } else if self.is_skipped(permission) { - ("Skipped for now", paper.soft) + (tr::permission::onboarding::status::skipped(), paper.soft) } else if status == OSPermissionStatus::Denied { - ("Off in System Settings", paper.red) + (tr::permission::onboarding::status::off(), paper.red) } else if permission.required() { - ("Required", paper.soft) + (tr::permission::onboarding::status::required(), paper.soft) } else { - ("Optional", paper.soft) + (tr::permission::onboarding::status::optional(), paper.soft) }; let paper = *paper; div() @@ -447,6 +448,7 @@ impl OnboardingWindow { div() .flex() .flex_col() + .min_w_0() .gap(px(1.)) .child( div() @@ -454,7 +456,8 @@ impl OnboardingWindow { .line_height(px(18.)) .font_weight(FontWeight::MEDIUM) .text_color(paper.ink) - .child(permission.label()), + .truncate() + .child(permission.display_name()), ) .child( div() @@ -462,6 +465,7 @@ impl OnboardingWindow { .line_height(px(16.)) .font_weight(FontWeight::NORMAL) .text_color(caption_color) + .truncate() .child(caption), ), ) @@ -498,15 +502,18 @@ impl OnboardingWindow { 168., 146., )) - .child(stage_title(paper, "One restart and you're set")) + .child(stage_title( + paper, + tr::permission::onboarding::relaunch::title(), + )) .child(stage_body( paper, - "macOS turns on screen recording once Cap restarts. You'll pick up right here.", + tr::permission::onboarding::relaunch::body(), )) .child(div().mt(px(24.)).child(paper_button( paper, "perm-relaunch-done", - "Relaunch Cap", + tr::onboarding::nav::relaunch(), true, true, cx.listener(|_, _, _, cx| permissions::relaunch(cx)), @@ -520,13 +527,16 @@ impl OnboardingWindow { 168., 146., )) - .child(stage_title(paper, "You're all set")) + .child(stage_title( + paper, + tr::permission::onboarding::all_set::title(), + )) .child(stage_body( paper, if self.permissions_only { - "Thanks. Cap can record again." + tr::permission::onboarding::all_set::regained() } else { - "Cap has everything it needs. Next, a quick look at what it can do." + tr::permission::onboarding::all_set::body() }, )), Stage::Permission(permission) => { @@ -548,19 +558,18 @@ impl OnboardingWindow { copy.done_title } else if self.stage.waiting_on == Some(permission) && permission.required() { - "Switch Cap on in System Settings" + tr::permission::onboarding::switch_on_title() } else { copy.title }, )); if granted { - let done_body = if permission == OSPermission::ScreenRecording - && self.needs_relaunch() - { - "macOS applies it after Cap restarts. You can do that once you're done here." - } else { - copy.done_body - }; + let done_body = + if permission == OSPermission::ScreenRecording && self.needs_relaunch() { + tr::permission::onboarding::relaunch::later() + } else { + copy.done_body + }; card.child(stage_body(paper, done_body)).child(allowed_pill( paper, self.stage.marks[index(permission)].1, @@ -590,21 +599,21 @@ impl OnboardingWindow { let guided = crate::permission_guide::eligible(permission); let waiting_required = waiting && permission.required(); let body = if waiting_required && guided { - "Drag Cap from the panel under System Settings into the list. Already listed? Just switch it on." + tr::permission::onboarding::hint::drag_into_list() } else if waiting_required { - "Find Cap in the list and switch it on. If macOS asked first, choose Open System Settings." + tr::permission::onboarding::hint::find_in_list() } else if status == OSPermissionStatus::Denied && !permission.required() { - "It was turned off earlier, so switch Cap on in System Settings. Or skip it for now." + tr::permission::onboarding::hint::turned_off() } else if action == Some(RowAction::OpenSettings) && permission.required() { - "Open System Settings, find Cap in the list and switch it on." + tr::permission::onboarding::hint::open_settings() } else { copy.body }; let primary_label = if waiting_required { - "Open System Settings again" + tr::permission::onboarding::open_settings_again() } else { match action { - Some(RowAction::OpenSettings) => "Open System Settings", + Some(RowAction::OpenSettings) => tr::main::device::access::open_system_settings(), _ => copy.allow, } }; @@ -629,7 +638,7 @@ impl OnboardingWindow { row.child(paper_button( &paper_copy, SharedString::from(format!("perm-skip-{}", permission.label())), - "Skip for now", + tr::permission::onboarding::skip(), false, true, cx.listener(move |this, _, _, cx| this.skip_permission(permission, cx)), @@ -658,12 +667,12 @@ impl OnboardingWindow { .text_size(px(12.)) .font_weight(FontWeight::NORMAL) .text_color(paper.soft) - .child("No prompt showed up? Switch Cap on in System Settings."), + .child(tr::permission::onboarding::hint::no_prompt()), ) .child(paper_button( paper, SharedString::from(format!("perm-settings-{}", permission.label())), - "Open System Settings", + tr::main::device::access::open_system_settings(), false, true, cx.listener(move |this, _, _, cx| { @@ -675,9 +684,9 @@ impl OnboardingWindow { ) } else if waiting { let hint = if permission.required() || action == Some(RowAction::OpenSettings) { - "Waiting for System Settings" + tr::permission::onboarding::waiting::system_settings() } else { - "Choose Allow in the macOS prompt" + tr::permission::onboarding::waiting::prompt() }; card.child( div() @@ -728,12 +737,12 @@ impl OnboardingWindow { .line_height(px(17.)) .font_weight(FontWeight::NORMAL) .text_color(paper.soft) - .child("Switched on but still waiting? Restart Cap to apply it."), + .child(tr::permission::onboarding::relaunch::hint()), ) .child(paper_button( paper, "perm-relaunch", - "Relaunch", + tr::permission::onboarding::relaunch::button(), false, true, cx.listener(|_, _, _, cx| permissions::relaunch(cx)), @@ -790,7 +799,7 @@ fn allowed_pill(paper: &Paper, shown_at: Instant, still: bool) -> Div { 22., 22., )) - .child("Allowed") + .child(tr::permission::onboarding::status::allowed()) } /// The extension's `.cta`: an ink pill. Primary is filled; secondary is the diff --git a/apps/desktop-gpui/src/permission_guide.rs b/apps/desktop-gpui/src/permission_guide.rs index 5bd32bdcb28..0bd4458c6a6 100644 --- a/apps/desktop-gpui/src/permission_guide.rs +++ b/apps/desktop-gpui/src/permission_guide.rs @@ -22,6 +22,7 @@ use gpui::{ }; use crate::doodle; +use crate::i18n::tr; use crate::onboarding_doodles as art; use crate::permissions::OSPermission; use crate::platform; @@ -325,9 +326,9 @@ impl Render for PermissionGuide { let palette = paper.palette(); // The arrow bobs forever; a hidden panel must not keep drawing. let still = self.hidden; - let pane = match self.permission { - OSPermission::Accessibility => "Accessibility", - _ => "Screen & System Audio Recording", + let body = match self.permission { + OSPermission::Accessibility => tr::permission::guide::accessibility_body(), + _ => tr::permission::guide::screen_body(), }; div() .size_full() @@ -362,16 +363,14 @@ impl Render for PermissionGuide { } }), ) - .child( - div().absolute().inset_0().child(doodle::doodle( - &art::DRAG_FRAME, - palette, - self.shown_at, - still, - 80., - 80., - )), - ) + .child(div().absolute().inset_0().child(doodle::doodle( + &art::DRAG_FRAME, + palette, + self.shown_at, + still, + 80., + 80., + ))) .child( div() .absolute() @@ -395,7 +394,7 @@ impl Render for PermissionGuide { .line_height(px(19.)) .font_weight(FontWeight::MEDIUM) .text_color(paper.ink) - .child("Drag Cap into the list"), + .child(tr::permission::guide::title()), ) .child( div() @@ -403,9 +402,7 @@ impl Render for PermissionGuide { .line_height(px(17.)) .font_weight(FontWeight::NORMAL) .text_color(paper.soft) - .child(format!( - "Drop it on the {pane} list above, then make sure its switch is on." - )), + .child(body), ), ) .child(div().flex_none().child(doodle::doodle( diff --git a/apps/desktop-gpui/src/permissions.rs b/apps/desktop-gpui/src/permissions.rs index 7a89abd4d35..b41ad8b8891 100644 --- a/apps/desktop-gpui/src/permissions.rs +++ b/apps/desktop-gpui/src/permissions.rs @@ -28,6 +28,8 @@ //! actually failed -- the per-permission `attempted` flags in //! [`classify`] are this module's version of Tauri's `initial_check`. +use crate::i18n::tr; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OSPermission { ScreenRecording, @@ -53,6 +55,15 @@ impl OSPermission { } } + pub fn display_name(self) -> &'static str { + match self { + Self::ScreenRecording => tr::permission::name::screen_recording(), + Self::Accessibility => tr::permission::name::accessibility(), + Self::Microphone => tr::permission::name::microphone(), + Self::Camera => tr::permission::name::camera(), + } + } + /// Screen recording and accessibility gate the app; mic and camera are /// optional. Same split as the Tauri app's `necessary_granted`. pub fn required(self) -> bool { diff --git a/apps/desktop-gpui/src/platform.rs b/apps/desktop-gpui/src/platform.rs index 27df7a67dc5..94276a40d23 100644 --- a/apps/desktop-gpui/src/platform.rs +++ b/apps/desktop-gpui/src/platform.rs @@ -29,9 +29,13 @@ pub(crate) enum LinuxFileDialogError { impl LinuxFileDialogError { pub(crate) fn message(&self) -> String { match self { - Self::Busy => "A file dialog is already open. Finish or cancel it before trying again.".into(), - Self::BeforeDispatch(error) | Self::NativeResponse(error) => format!("The file dialog could not complete: {error}"), - Self::Indeterminate(_) | Self::RestartRequired => "Cap could not confirm that the file dialog closed. Close any remaining file dialog and restart Cap before trying again.".into(), + Self::Busy => crate::i18n::tr::dialog::file::busy().into(), + Self::BeforeDispatch(error) | Self::NativeResponse(error) => { + crate::i18n::tr::dialog::file::failed(error) + } + Self::Indeterminate(_) | Self::RestartRequired => { + crate::i18n::tr::dialog::file::unconfirmed().into() + } } } } @@ -313,7 +317,7 @@ async fn linux_save_file_panel( let filters = if extensions.is_empty() { Vec::new() } else { - portal_filters(&[("Export", extensions)]) + portal_filters(&[(crate::i18n::tr::dialog::file::export_filter(), extensions)]) }; run_linux_file_dialog(cx, owner, parent, move |identifier| async move { let request = ashpd::desktop::file_chooser::SaveFileRequest::default() @@ -355,7 +359,7 @@ async fn finish_linux_file_dialog( return None; } Some(crate::editor_modal::informational_alert( - "File dialog unavailable", + crate::i18n::tr::dialog::file::unavailable_title(), &message, window, cx, @@ -553,7 +557,7 @@ fn clipboard_file_path(path: &std::path::Path) -> Result Result<(), String> { cx.try_write_to_clipboard(image.0) - .map_err(|error| format!("Could not copy the image to the clipboard: {error}")) + .map_err(crate::i18n::tr::dialog::clipboard::copy_image) } pub fn desktop_picture_path() -> Option { #[cfg(target_os = "linux")] diff --git a/apps/desktop-gpui/src/platform/windows.rs b/apps/desktop-gpui/src/platform/windows.rs index a0468465b38..d7e5f693f66 100644 --- a/apps/desktop-gpui/src/platform/windows.rs +++ b/apps/desktop-gpui/src/platform/windows.rs @@ -558,14 +558,17 @@ pub fn window_number(window: &Window) -> Option { pub fn open_image_panel(extensions: &[&str]) -> Option { let mut dialog = rfd::FileDialog::new(); if !extensions.is_empty() { - dialog = dialog.add_filter("Images", extensions); + dialog = dialog.add_filter(crate::i18n::tr::dialog::file::images_filter(), extensions); } dialog.pick_file() } pub fn open_audio_panel() -> Option { rfd::FileDialog::new() - .add_filter("Audio", &["mp3", "wav", "m4a", "ogg", "flac", "aac"]) + .add_filter( + crate::i18n::tr::dialog::file::audio_filter(), + &["mp3", "wav", "m4a", "ogg", "flac", "aac"], + ) .pick_file() } @@ -629,7 +632,7 @@ pub fn set_dock_icon(_png: &[u8]) {} pub fn save_file_panel(suggested: &str, extensions: &[&str]) -> Option { let mut dialog = rfd::FileDialog::new().set_file_name(suggested); if !extensions.is_empty() { - dialog = dialog.add_filter("Export", extensions); + dialog = dialog.add_filter(crate::i18n::tr::dialog::file::export_filter(), extensions); } dialog.save_file() } @@ -642,9 +645,9 @@ pub fn copy_file_to_clipboard(path: &Path, _cx: &gpui::App) -> Result<(), String .to_str() .ok_or("The clipboard file path is not valid Unicode")?; ClipboardContext::new() - .map_err(|error| format!("Clipboard unavailable: {error}"))? + .map_err(crate::i18n::tr::dialog::clipboard::unavailable_error)? .set_files(vec![path.to_owned()]) - .map_err(|error| format!("Could not copy the file to the clipboard: {error}")) + .map_err(crate::i18n::tr::dialog::clipboard::copy_file) } pub struct ClipboardImage(clipboard_rs::RustImageData); @@ -662,9 +665,9 @@ pub fn write_clipboard_image(image: ClipboardImage, _cx: &gpui::App) -> Result<( use clipboard_rs::{Clipboard, ClipboardContext}; ClipboardContext::new() - .map_err(|error| format!("Clipboard unavailable: {error}"))? + .map_err(crate::i18n::tr::dialog::clipboard::unavailable_error)? .set_image(image.0) - .map_err(|error| format!("Could not copy the image to the clipboard: {error}")) + .map_err(crate::i18n::tr::dialog::clipboard::copy_image) } pub fn desktop_picture_path() -> Option { diff --git a/apps/desktop-gpui/src/session.rs b/apps/desktop-gpui/src/session.rs index 20ef9f7ecc3..910daf8732f 100644 --- a/apps/desktop-gpui/src/session.rs +++ b/apps/desktop-gpui/src/session.rs @@ -15,6 +15,7 @@ use cap_utils::disk_space::{DiskSpaceStatus, RecordingStorageMonitor}; use gpui::{App, AppContext as _, Context, Entity, Global, Task}; use crate::app_sounds::AppSound; +use crate::i18n::tr; use crate::recording::{self, ActiveRecording, StartConfig}; /// `Idle -> Starting -> Recording -> Stopping -> Idle`; pause is a flag on @@ -1077,8 +1078,7 @@ impl RecordingSession { DiskSpaceStatus::Exhausted => { this.storage_warning = true; this.stopped_for_low_storage = true; - this.storage_notice = - Some("Low storage. Stopping and saving your recording…".into()); + this.storage_notice = Some(tr::dialog::storage::stopping().into()); this.stop(cx); false } @@ -1366,7 +1366,7 @@ impl RecordingSession { tracing::info!(dir = %project_dir.display(), "recording finished"); stop_sound.call_once(|| AppSound::StopRecording.play()); if low_storage { - this.storage_notice = Some("Recording stopped because storage is low. Your recording was saved.".into()); + this.storage_notice = Some(tr::dialog::storage::stopped_saved().into()); } if this.mode() == Some(crate::recording::RecordingMode::Studio) && !low_storage { this.finished_studio = Some(project_dir); @@ -1383,12 +1383,12 @@ impl RecordingSession { } Ok(Err(error)) => { tracing::error!("recording failed to stop cleanly: {error:#}"); - this.storage_notice = low_storage.then(|| "Recording stopped because storage is low. Your recording files were kept. Free up space, then recover the recording below.".into()); + this.storage_notice = low_storage.then(|| tr::dialog::storage::stopped_kept().into()); this.error = Some(format!("{error:#}")); } Err(join_error) => { tracing::error!("recording stop task died: {join_error}"); - this.storage_notice = low_storage.then(|| "Recording stopped because storage is low. Your recording files were kept. Free up space, then recover the recording below.".into()); + this.storage_notice = low_storage.then(|| tr::dialog::storage::stopped_kept().into()); this.error = Some("Stop task failed.".into()); } } diff --git a/apps/desktop-gpui/src/sign_in.rs b/apps/desktop-gpui/src/sign_in.rs index dd49f994a21..a87b53c03c4 100644 --- a/apps/desktop-gpui/src/sign_in.rs +++ b/apps/desktop-gpui/src/sign_in.rs @@ -10,6 +10,7 @@ use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task}; use crate::auth; +use crate::i18n::tr; use crate::main_window::TargetType; use crate::platform; @@ -115,7 +116,7 @@ impl SignIn { match &outcome { SignInOutcome::SignedIn => platform::activate_app(), SignInOutcome::Failed(error) if !matches!(origin, SignInOrigin::Overlay(_)) => { - platform::alert_dialog("Sign in failed", error) + platform::alert_dialog(tr::auth::sign_in_failed(), error) } _ => {} } diff --git a/apps/desktop-gpui/src/target_overlay.rs b/apps/desktop-gpui/src/target_overlay.rs index 430a049bf19..9046af06e9c 100644 --- a/apps/desktop-gpui/src/target_overlay.rs +++ b/apps/desktop-gpui/src/target_overlay.rs @@ -37,6 +37,7 @@ use scap_targets::{ bounds::{LogicalBounds, LogicalPosition, LogicalSize}, }; +use crate::i18n::tr; use crate::theme::LocaleFont as _; use crate::{ app_windows, @@ -160,11 +161,11 @@ fn area_controls_position(crop: AreaRect, available: AreaRect, cluster_height: f ) } -const COUNTDOWN_OPTIONS: &[(u32, &str)] = &[ - (0, "Off"), - (3, "3 seconds"), - (5, "5 seconds"), - (10, "10 seconds"), +const COUNTDOWN_OPTIONS: &[(u32, fn() -> &'static str)] = &[ + (0, tr::settings::general::recording::countdown::off), + (3, tr::settings::general::recording::countdown::seconds_3), + (5, tr::settings::general::recording::countdown::seconds_5), + (10, tr::settings::general::recording::countdown::seconds_10), ]; const SELECTION_HINT_CYCLE: Duration = Duration::from_millis(2800); @@ -867,16 +868,16 @@ struct OverlayDeviceMenu { /// `menuModes` (`target-select-overlay.tsx:2064-2090`): three check items, in /// this order, checked on the current mode. Picking one switches the mode and /// never starts a recording. -const MODE_MENU: [(&str, Mode); 3] = [ - ("Studio Mode", Mode::Studio), - ("Instant Mode", Mode::Instant), - ("Screenshot Mode", Mode::Screenshot), +const MODE_MENU: [(fn() -> &'static str, Mode); 3] = [ + (tr::main::mode::header::studio, Mode::Studio), + (tr::main::mode::header::instant, Mode::Instant), + (tr::main::mode::header::screenshot, Mode::Screenshot), ]; fn mode_menu_items(current: Mode) -> Vec { MODE_MENU .into_iter() - .map(|(label, mode)| crate::ui::MenuItem::new(label, mode == current)) + .map(|(label, mode)| crate::ui::MenuItem::new(label(), mode == current)) .collect() } @@ -963,7 +964,7 @@ impl OverlayWindow { display_id: display.id(), display_name: display .name() - .unwrap_or_else(|| format!("Display {}", display.id())), + .unwrap_or_else(|| tr::overlay::display_fallback(display.id())), display_size: logical, capture_scale, controls_work_area, @@ -1361,10 +1362,10 @@ impl OverlayWindow { access.read(cx).status(permission) == crate::permissions::OSPermissionStatus::Denied, ) { - (true, true) => "Turn on camera access in Settings…", - (true, false) => "Allow camera access…", - (false, true) => "Turn on microphone access in Settings…", - (false, false) => "Allow microphone access…", + (true, true) => tr::overlay::device::camera_turn_on(), + (true, false) => tr::overlay::device::camera_allow(), + (false, true) => tr::overlay::device::microphone_turn_on(), + (false, false) => tr::overlay::device::microphone_allow(), }; ( crate::ui::MenuItem::new(label, false), @@ -1373,7 +1374,7 @@ impl OverlayWindow { }); let (mut items, mut choices) = if camera { let mut items = vec![crate::ui::MenuItem::new( - "No Camera", + tr::main::device::no_camera(), view.camera_selection().is_none(), )]; let mut choices = vec![OverlayDeviceChoice::Camera(None)]; @@ -1388,7 +1389,7 @@ impl OverlayWindow { (items, choices) } else { let mut items = vec![crate::ui::MenuItem::new( - "No Microphone", + tr::main::device::no_microphone(), view.microphone_selection().is_none(), )]; let mut choices = vec![OverlayDeviceChoice::Microphone(None)]; @@ -1439,13 +1440,17 @@ impl OverlayWindow { let access = access.read(cx); let blocked = enabled && !access.allowed(permission); let pill_label = if !blocked { - if enabled { "On" } else { "Off" } + if enabled { + tr::main::device::pill::on() + } else { + tr::main::device::pill::off() + } } else if access.requesting(permission) { - "Asking…" + tr::main::device::pill::asking() } else if access.status(permission) == crate::permissions::OSPermissionStatus::Denied { - "Allow in Settings" + tr::main::device::pill::allow_in_settings() } else { - "Allow" + tr::main::device::pill::allow() }; div() .id(if camera { @@ -1496,7 +1501,12 @@ impl OverlayWindow { .truncate() .text_size(px(14.)) .child(selected.unwrap_or_else(|| { - if camera { "No Camera" } else { "No Microphone" }.into() + if camera { + tr::main::device::no_camera() + } else { + tr::main::device::no_microphone() + } + .into() })), ) .child( @@ -1584,7 +1594,7 @@ impl OverlayWindow { .unwrap_or(0); let items = COUNTDOWN_OPTIONS .iter() - .map(|(value, label)| crate::ui::MenuItem::new(*label, *value == current)) + .map(|(value, label)| crate::ui::MenuItem::new(label(), *value == current)) .collect(); Some( crate::ui::Menu::plain(&self.theme, "overlay-countdown-menu", items, state) @@ -1813,7 +1823,7 @@ impl OverlayWindow { .text_size(px(14.)) .text_color(self.theme.gray_11) .child(svg().path("icons/camera.svg").size(px(32.))) - .child("Please select a camera"), + .child(tr::overlay::camera_only::select_camera()), ) .into_any_element() }; @@ -1840,13 +1850,13 @@ impl OverlayWindow { // (`target-select-overlay.tsx:392`): renders 700, // no 600 face loaded. .font_weight(FontWeight::BOLD) - .child("Camera Only"), + .child(tr::main::target::camera_only()), ) .child( div() .text_size(px(12.)) .text_color(self.theme.gray_11) - .child("Record using only your camera and microphone"), + .child(tr::overlay::camera_only::description()), ), ) .child( @@ -2204,7 +2214,7 @@ impl OverlayWindow { div() .text_size(px(16.)) .font_weight(FontWeight::MEDIUM) - .child("Click and drag to select an area"), + .child(tr::overlay::area::hint()), ), ) } @@ -2221,11 +2231,11 @@ impl OverlayWindow { ) -> impl IntoElement { let label = match crop.filter(|_| valid) { Some(crop) => format!("{} × {}", crop.width.round(), crop.height.round()), - None => "Draw an area".to_string(), + None => tr::overlay::area::draw().to_string(), }; let ratio_label = match self.area_ratio { Some((width, height)) => format!("{width}:{height}"), - None => "Free".to_string(), + None => tr::overlay::area::free_ratio().to_string(), }; let ratio = div() .id("area-aspect-ratio") @@ -2347,11 +2357,10 @@ impl OverlayWindow { .bg(self.theme.red_2) .text_size(px(14.)) .text_color(self.theme.gray_12) - .child(format!("Minimum size is {} x {}", min as u32, min as u32)) - .child(div().text_size(px(11.)).child(format!( - "{} x {} is too small", + .child(tr::overlay::area::minimum_size(min as u32, min as u32)) + .child(div().text_size(px(11.)).child(tr::overlay::area::too_small( crop.width.round(), - crop.height.round() + crop.height.round(), ))), ) }) @@ -2437,7 +2446,11 @@ impl OverlayWindow { ) .hover(|style| style.opacity(0.8)) .tooltip(move |_, cx| { - crate::ui::Tooltip::new(&theme, "Recording countdown").view(cx) + crate::ui::Tooltip::new( + &theme, + tr::overlay::countdown_tooltip(), + ) + .view(cx) }) .on_mouse_down(MouseButton::Left, |_, _, cx| { cx.stop_propagation(); @@ -2450,7 +2463,7 @@ impl OverlayWindow { let items = COUNTDOWN_OPTIONS .iter() .map(|(value, label)| { - crate::ui::MenuItem::new(*label, *value == current) + crate::ui::MenuItem::new(label(), *value == current) }) .collect::>(); this.mode_menu = None; @@ -2523,9 +2536,9 @@ impl OverlayWindow { }; let needs_sign_in = self.needs_sign_in(mode, cx); let label = match mode { - Mode::Screenshot => "Take Screenshot", - _ if needs_sign_in => "Sign In To Use", - _ => "Start Recording", + Mode::Screenshot => tr::overlay::start::take_screenshot(), + _ if needs_sign_in => tr::overlay::start::sign_in(), + _ => tr::overlay::start::start_recording(), }; let icon = match mode { Mode::Instant if needs_sign_in => "icons/user-round.svg", @@ -2534,10 +2547,10 @@ impl OverlayWindow { Mode::Screenshot => "icons/camera.svg", }; let mode_label = match mode { - Mode::Instant if needs_sign_in => "Instant Mode — Sign in to record instantly", - Mode::Instant => "Instant Mode — Record shareable link", - Mode::Studio => "Studio Mode — Record and edit locally", - Mode::Screenshot => "Screenshot Mode", + Mode::Instant if needs_sign_in => tr::overlay::start::instant_sign_in(), + Mode::Instant => tr::overlay::start::instant(), + Mode::Studio => tr::overlay::start::studio(), + Mode::Screenshot => tr::main::mode::header::screenshot(), }; let _ = target; @@ -2600,6 +2613,7 @@ impl OverlayWindow { // `text-[0.95rem] font-medium`. .text_size(px(15.2)) .font_weight(FontWeight::MEDIUM) + .truncate() .child(label), ) .child( @@ -2666,7 +2680,7 @@ impl OverlayWindow { .border_color(gpui::hsla(0., 0., 1., 0.2)) .bg(gpui::hsla(0., 0., 1., 0.05)) .tooltip(move |_, cx| { - crate::ui::Tooltip::new(&theme, "Choose recording mode").view(cx) + crate::ui::Tooltip::new(&theme, tr::overlay::choose_mode()).view(cx) }) .child( svg() diff --git a/apps/desktop-gpui/src/teleprompter_window.rs b/apps/desktop-gpui/src/teleprompter_window.rs index 53fb1440dc3..27d6572ffa4 100644 --- a/apps/desktop-gpui/src/teleprompter_window.rs +++ b/apps/desktop-gpui/src/teleprompter_window.rs @@ -29,6 +29,7 @@ use gpui::{ prelude::FluentBuilder, px, svg, }; +use crate::i18n::tr; use crate::theme::LocaleFont as _; use crate::{ platform, @@ -255,7 +256,6 @@ impl TeleprompterWindow { let script_input = cx.new(|cx| { let mut input = ui::TextInputState::multi_line(window, cx); input.set_text(state.script.clone(), cx); - input.set_placeholder("Paste or type your script\u{2026}"); input }); let script_events = cx.subscribe_in( @@ -576,6 +576,9 @@ fn stepped(fraction: f32, minimum: f32, maximum: f32, step: f32) -> f32 { impl Render for TeleprompterWindow { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { self.sync_appearance(window, cx); + self.script_input.update(cx, |input, _| { + input.set_placeholder(tr::dialog::teleprompter::placeholder()) + }); let theme = self.theme; div() @@ -662,9 +665,9 @@ impl TeleprompterWindow { .text_color(Hsla::from(theme.gray_9)), ) .child(if self.playback_state.recording_paused { - "Scrolling paused with recording" + tr::dialog::teleprompter::paused() } else { - "This window is hidden from Cap recordings" + tr::dialog::teleprompter::hidden() }), ); @@ -1027,7 +1030,7 @@ impl TeleprompterWindow { .child(self.render_setting_toggle( "cue-markers", "icons/chevron-right.svg", - "Cue markers", + tr::dialog::teleprompter::cue_markers(), self.state.show_cue_markers, cx.listener(|this, _, window, cx| { this.state.show_cue_markers = !this.state.show_cue_markers; @@ -1038,7 +1041,7 @@ impl TeleprompterWindow { .child(self.render_setting_toggle( "mirror", "icons/flip-horizontal-2.svg", - "Mirror text", + tr::dialog::teleprompter::mirror(), self.state.mirror, cx.listener(|this, _, window, cx| { // Persisted, but inert: this gpui rev has no flip @@ -1080,6 +1083,7 @@ impl TeleprompterWindow { .flex() .flex_row() .items_center() + .min_w_0() .gap(px(8.)) .child( svg() @@ -1088,7 +1092,7 @@ impl TeleprompterWindow { .flex_shrink_0() .text_color(Hsla::from(theme.gray_10)), ) - .child(label), + .child(div().min_w_0().truncate().child(label)), ) // `` on bare glass. .child(ui::Toggle::glass( diff --git a/apps/desktop-gpui/src/transcription.rs b/apps/desktop-gpui/src/transcription.rs index 92fa942a9cc..524efba08f5 100644 --- a/apps/desktop-gpui/src/transcription.rs +++ b/apps/desktop-gpui/src/transcription.rs @@ -52,6 +52,7 @@ use futures_util::StreamExt; use parakeet_rs::{ParakeetTDT, TimestampMode, Transcriber}; use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters}; +use crate::i18n::tr; use crate::{editor_timeline::clip_timeline_offsets, store}; // --------------------------------------------------------------------------- @@ -261,7 +262,7 @@ pub fn begin_download(model: &str) -> bool { model: model.to_string(), state: DownloadState::Downloading, progress: 0.0, - message: "Preparing model download".to_string(), + message: tr::transcription::download::preparing().to_string(), }); true } @@ -292,7 +293,7 @@ pub async fn run_model_download(model: String) { model: model.clone(), state: DownloadState::Completed, progress: 100.0, - message: "Download complete".to_string(), + message: tr::transcription::download::complete().to_string(), }, Err(error) => { tracing::error!(model, "caption model download failed: {error}"); @@ -300,7 +301,7 @@ pub async fn run_model_download(model: String) { model: model.clone(), state: DownloadState::Failed, progress: 0.0, - message: format!("Download failed: {error}"), + message: tr::transcription::download::failed(error), } } }; @@ -468,7 +469,7 @@ async fn write_whisper_parts( set_download_progress( model, progress, - format!("Downloading model: {progress:.0}%"), + tr::transcription::download::progress(format!("{progress:.0}")), ); } } @@ -749,7 +750,10 @@ async fn download_parakeet_model(model: &str) -> Result<(), String> { set_download_progress( model, progress, - format!("Downloading {filename}: {progress:.0}%"), + tr::transcription::download::file_progress( + filename, + format!("{progress:.0}"), + ), ); } } @@ -1857,8 +1861,8 @@ fn extract_audio_from_video(video_path: &Path, output_path: &Path) -> Result<(), // --------------------------------------------------------------------------- use cap_project::caption_words::{ - caption_text_from_words, caption_word_chunks, normalize_caption_words, - MAX_CAPTION_WORD_DURATION, + MAX_CAPTION_WORD_DURATION, caption_text_from_words, caption_word_chunks, + normalize_caption_words, }; // --------------------------------------------------------------------------- @@ -2123,20 +2127,19 @@ pub fn apply_caption_result( /// `getCaptionGenerationErrorMessage` (`captions.ts:634-662`). pub fn caption_generation_error_message(message: &str) -> String { if message.contains("No audio stream found") { - return "No audio found in the video file".to_string(); + return tr::transcription::error::no_audio().to_string(); } if message.contains("Model file not found") { - return "Caption model not found. Please download it first".to_string(); + return tr::transcription::error::model_missing().to_string(); } if message.contains("Failed to load Whisper model") { - return "Failed to load the caption model. Try downloading it again".to_string(); + return tr::transcription::error::model_load().to_string(); } if message.contains(PARAKEET_UNSUPPORTED_LANGUAGE) { - return "The Recommended and High Accuracy models only transcribe English and 24 other European languages. Download the Small or Medium model to caption this language".to_string(); + return tr::transcription::error::unsupported_language().to_string(); } if message.contains("Parakeet transcription is not available on Intel macOS") { - return "Parakeet models are not available on Intel Macs. Use a Whisper model instead" - .to_string(); + return tr::transcription::error::intel_mac().to_string(); } message.to_string() } diff --git a/apps/desktop-gpui/src/upload/queue.rs b/apps/desktop-gpui/src/upload/queue.rs index 408a1b6a465..f653f9d81c2 100644 --- a/apps/desktop-gpui/src/upload/queue.rs +++ b/apps/desktop-gpui/src/upload/queue.rs @@ -1,4 +1,5 @@ use super::*; +use crate::i18n::tr; use cap_project::{InstantRecordingMeta, RecordingMetaInner}; use cap_recording::upload_resume::{UploadLock, UploadLockError, collect_segment_events}; use cap_recording::upload_verification::{UploadVerification, VerifiedUploadReceipt}; @@ -89,14 +90,14 @@ impl UploadState { pub fn label(&self) -> &'static str { match self.phase { - UploadPhase::Recording => "Upload prepared", - UploadPhase::Pending | UploadPhase::Uploading => "Uploading", - UploadPhase::Processing => "Processing upload", - UploadPhase::Retrying => "Upload will retry", - UploadPhase::NeedsAuthentication => "Sign in to retry upload", - UploadPhase::Failed => "Upload needs attention", - UploadPhase::Cancelled => "Upload paused", - UploadPhase::Verified => "Uploaded", + UploadPhase::Recording => tr::upload::status::prepared(), + UploadPhase::Pending | UploadPhase::Uploading => tr::upload::status::uploading(), + UploadPhase::Processing => tr::upload::status::processing(), + UploadPhase::Retrying => tr::upload::status::retrying(), + UploadPhase::NeedsAuthentication => tr::upload::status::sign_in(), + UploadPhase::Failed => tr::upload::status::failed(), + UploadPhase::Cancelled => tr::upload::status::paused(), + UploadPhase::Verified => tr::upload::status::uploaded(), } } From aee75be0b2b00fd9d07e29d0985530c981f4da3d Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:43:10 +0000 Subject: [PATCH 04/25] feat(desktop-gpui): add dialog surface catalogs and literal tests --- apps/desktop-gpui/locales/de.toml | 379 +++++++ apps/desktop-gpui/locales/en.toml | 379 +++++++ apps/desktop-gpui/locales/es.toml | 379 +++++++ apps/desktop-gpui/locales/fr.toml | 379 +++++++ apps/desktop-gpui/locales/hi.toml | 379 +++++++ apps/desktop-gpui/locales/ja.toml | 379 +++++++ apps/desktop-gpui/locales/zh-CN.toml | 379 +++++++ .../src/i18n/dialogs_surface_tests.rs | 1000 +++++++++++++++++ apps/desktop-gpui/src/i18n/mod.rs | 2 + apps/desktop-gpui/src/i18n/tests.rs | 1 + 10 files changed, 3656 insertions(+) create mode 100644 apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index 6a03591f376..d33914995ee 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -586,3 +586,382 @@ click_or_press = "Hier klicken oder drücken:" get_started = "Los geht’s" space_key = "Leertaste" tagline = "Die Open-Source-App zum Teilen des Bildschirms für alle." + +[auth] +sign_in_failed = "Anmeldung fehlgeschlagen" + +[diagnostics] +already_running = "Es läuft bereits eine Diagnose." +cancelled = "Die Diagnose wurde abgebrochen." +mode_both = "Beide" + +[diagnostics.error] +create_folder = "Der Diagnoseordner konnte nicht erstellt werden: {error}" +exited = "Der Selbsttest wurde beendet mit {status}" +no_output = "Der Selbsttest hat keine Ausgabe geliefert" +serialize = "Der Bericht konnte nicht serialisiert werden: {error}" +start_failed = "{program} konnte nicht gestartet werden: {error}" +upload_logs = "Protokolle konnten nicht hochgeladen werden: {error}" +upload_status = "Upload fehlgeschlagen mit Status: {status}" +wait_failed = "Auf den Selbsttest konnte nicht gewartet werden: {error}" +write_failed = "{path} konnte nicht geschrieben werden: {error}" + +[diagnostics.stage] +analyzing = "Aufnahme wird analysiert..." +analyzing_instant = "Instant-Aufnahme wird analysiert..." +analyzing_studio = "Studio-Aufnahme wird analysiert..." +cancelling = "Wird abgebrochen..." +collecting = "Systeminformationen werden gesammelt..." +done = "Synchronisationstest wird abgeschlossen..." +exporting = "Aufnahme wird exportiert..." +exporting_instant = "Instant-Aufnahme wird exportiert..." +exporting_studio = "Studio-Aufnahme wird exportiert..." +finalizing = "Aufnahme wird fertiggestellt..." +finalizing_instant = "Instant-Aufnahme wird fertiggestellt..." +finalizing_studio = "Studio-Aufnahme wird fertiggestellt..." +pattern = "Testmuster wird abgespielt..." +pattern_instant = "Testmuster wird abgespielt (Instant)..." +pattern_studio = "Testmuster wird abgespielt (Studio)..." +preparing = "Test wird vorbereitet..." +preparing_instant = "Instant-Test wird vorbereitet..." +preparing_studio = "Studio-Test wird vorbereitet..." +recording = "Test wird aufgenommen..." +recording_instant = "Instant-Test wird aufgenommen..." +recording_studio = "Studio-Test wird aufgenommen..." +starting = "Diagnose wird gestartet..." + +[dialog] +no_microphone_body = "Diese Aufnahme enthält Ihre Stimme nicht." +open_recording_failed = "Diese Aufnahme konnte nicht geöffnet werden: {error}" + +[dialog.capture] +approve_shortcut = "Bestätigen Sie ein Aufnahmekürzel oder aktivieren Sie das Cap-Stopp-Symbol in der Taskleiste, sobald es erscheint. Die Aufnahme hat noch nicht begonnen." +area_unavailable = "Der ausgewählte Bereich hat sich geändert oder ist nicht mehr verfügbar. Wählen Sie ihn erneut aus." +camera_needs_x11 = "Instant-Kameraeinblendungen benötigen unter Linux einen X11-Desktop" +camera_notice = "Cap blendet die Kameravorschau und die Aufnahmesteuerung aus. Eine ausgewählte Kamera wird weiterhin als separate, bearbeitbare Spur aufgenommen." +camera_notice_instant = "Cap blendet Vorschau und Steuerung aus. Eine ausgewählte Kamera erscheint an der Position ihrer Vorschau, mit der gewählten Form und den gewählten Effekten. Halten Sie die gesamte Vorschau innerhalb des Aufnahmebereichs. Wenn Sie Cap öffnen, wird diese Instant-Aufnahme beendet, bevor die Steuerung erscheint." +camera_outside_area = "Verschieben Sie die gesamte Kameravorschau in den Aufnahmebereich, bevor Sie die Instant-Aufnahme starten" +camera_preview_closed = "Öffnen Sie die Vorschau der ausgewählten Kamera vor der Instant-Aufnahme" +cleanup_incomplete = "Die Aufräumarbeiten der Aufnahme sind unvollständig: {error}. Eine neue Aufnahme ist gesperrt." +devices_changed = "Die ausgewählten Geräte haben sich geändert. Starten Sie erneut, um die neuen Aufnahmequellen zu bestätigen." +devices_changed_before_start = "Die ausgewählten Geräte haben sich vor Beginn der Aufnahme geändert. Starten Sie erneut, um die Quellen zu bestätigen." +shortcut_conflict = "Ctrl+Shift+F9 ist einer anderen Cap-Aktion zugewiesen. Ändern Sie dieses Kürzel vor der Aufnahme." +shortcut_description = "Diese Cap-Aufnahme starten oder beenden" +shortcut_hint = "Drücken Sie {shortcut} und lassen Sie los, um zu starten. Mit demselben Kürzel beenden Sie die Aufnahme. Dieses vorübergehende Kürzel ändert Ihre Einstellungen nicht." +tray_stop_hint = "Aktivieren Sie das Cap-Stopp-Symbol in der Taskleiste, um zu starten. Aktivieren Sie es erneut, um zu beenden. Die Aufnahme beginnt erst, wenn Sie dieses Steuerelement verwenden." +window_unavailable = "Das ausgewählte Fenster ist nicht mehr verfügbar. Wählen Sie es erneut aus." + +[dialog.clipboard] +copy_file = "Die Datei konnte nicht in die Zwischenablage kopiert werden: {error}" +copy_file_failed = "Die Datei konnte nicht in die Zwischenablage kopiert werden" +copy_image = "Das Bild konnte nicht in die Zwischenablage kopiert werden: {error}" +copy_image_failed = "Das Bild konnte nicht in die Zwischenablage kopiert werden" +not_a_file = "Nur Dateien können in die Zwischenablage kopiert werden" +unavailable = "Zwischenablage nicht verfügbar" +unavailable_error = "Zwischenablage nicht verfügbar: {error}" + +[dialog.delete_recording] +already_deleting = "Diese Aufnahme wird bereits gelöscht." +close_failed = "Der Editor konnte nicht geschlossen werden: {error}" +editor_changed = "Der Editor hat sich während der Bestätigung geändert. Die Aufnahme wurde nicht gelöscht." +editor_closed = "Der Editor wurde bereits geschlossen. Die Aufnahme wurde nicht gelöscht." +failed = "Die Aufnahme konnte nicht gelöscht werden: {error}" +failed_title = "Aufnahme konnte nicht gelöscht werden" +finishing_body = "Cap wartet auf das Ende der Aufräumarbeiten, bevor das Löschen abgeschlossen wird." +finishing_title = "Das Löschen läuft noch" +in_use = "Diese Aufnahme wird noch verwendet. Schließen Sie den Upload ab oder brechen Sie ihn ab und versuchen Sie es erneut: {error}" +outside_library = "Diese Aufnahme liegt außerhalb der Aufnahmebibliothek. Ihre Dateien wurden nicht geändert." +recording_active = "Beenden oder verwerfen Sie die Aufnahme, bevor Sie dieses Projekt löschen." +retained_title = "Aufnahme beibehalten" + +[dialog.file] +audio_filter = "Audio" +busy = "Ein Dateidialog ist bereits geöffnet. Schließen Sie ihn ab oder brechen Sie ihn ab, bevor Sie es erneut versuchen." +export_filter = "Export" +failed = "Der Dateidialog konnte nicht abgeschlossen werden: {error}" +images_filter = "Bilder" +unavailable_title = "Dateidialog nicht verfügbar" +unconfirmed = "Cap konnte nicht bestätigen, dass der Dateidialog geschlossen wurde. Schließen Sie alle verbleibenden Dateidialoge und starten Sie Cap neu, bevor Sie es erneut versuchen." + +[dialog.mode_select] +instant = "Sofort per Link teilen. Lädt während der Aufnahme hoch." +screenshot = "Bildschirmfotos sofort aufnehmen und beschriften." +studio = "Lokale Aufnahme in höchster Qualität zum späteren Bearbeiten." +subtitle = "Wählen Sie, wie Sie Ihren Bildschirm aufnehmen möchten" +title = "Aufnahmemodus wählen" + +[dialog.storage] +stopped_kept = "Die Aufnahme wurde wegen wenig Speicherplatz beendet. Ihre Aufnahmedateien wurden behalten. Geben Sie Speicherplatz frei und stellen Sie die Aufnahme dann unten wieder her." +stopped_saved = "Die Aufnahme wurde wegen wenig Speicherplatz beendet. Ihre Aufnahme wurde gespeichert." +stopping = "Wenig Speicherplatz. Ihre Aufnahme wird beendet und gespeichert…" + +[dialog.teleprompter] +cue_markers = "Cue-Marker" +hidden = "Dieses Fenster ist in Cap-Aufnahmen ausgeblendet" +mirror = "Text spiegeln" +paused = "Scrollen mit der Aufnahme pausiert" +placeholder = "Skript einfügen oder eingeben…" + +[import] +error_title = "Importfehler" +image_failed = "Bild konnte nicht importiert werden: {error}" +unsupported = "Nicht unterstützte Mediendatei." +video_failed = "Video konnte nicht importiert werden: {error}" + +[import.filter] +image = "Bilddateien" +media = "Mediendateien" +video = "Videodateien" + +[import.progress] +analyzing = "Videodatei wird analysiert..." +complete = "Import abgeschlossen!" +converting = "Video wird konvertiert... {percent} %" +image = "Bild wird importiert..." +metadata = "Projektmetadaten werden erstellt..." +starting = "Konvertierung wird gestartet..." + +[overlay] +choose_mode = "Aufnahmemodus wählen" +countdown_tooltip = "Countdown vor der Aufnahme" +display_fallback = "Bildschirm {id}" + +[overlay.area] +draw = "Bereich aufziehen" +free_ratio = "Frei" +hint = "Klicken und ziehen Sie, um einen Bereich auszuwählen" +minimum_size = "Die Mindestgröße beträgt {width} x {height}" +too_small = "{width} x {height} ist zu klein" + +[overlay.camera_only] +description = "Nur mit Kamera und Mikrofon aufnehmen" +select_camera = "Bitte wählen Sie eine Kamera" + +[overlay.device] +camera_allow = "Kamerazugriff erlauben…" +camera_turn_on = "Kamerazugriff in den Einstellungen aktivieren…" +microphone_allow = "Mikrofonzugriff erlauben…" +microphone_turn_on = "Mikrofonzugriff in den Einstellungen aktivieren…" + +[overlay.start] +instant = "Instant-Modus: Link zum Teilen aufnehmen" +instant_sign_in = "Instant-Modus: Zum Aufnehmen anmelden" +sign_in = "Zum Nutzen anmelden" +start_recording = "Aufnahme starten" +studio = "Studio-Modus: Lokal aufnehmen und bearbeiten" +take_screenshot = "Bildschirmfoto aufnehmen" + +[permission.guide] +accessibility_body = "Legen Sie es oben in der Liste „Bedienungshilfen“ ab und achten Sie darauf, dass der Schalter aktiviert ist." +screen_body = "Legen Sie es oben in der Liste „Bildschirm- & Systemaudioaufnahme“ ab und achten Sie darauf, dass der Schalter aktiviert ist." +title = "Ziehen Sie Cap in die Liste" + +[permission.name] +accessibility = "Bedienungshilfen" +camera = "Kamera" +microphone = "Mikrofon" +screen_recording = "Bildschirmaufnahme" + +[permission.onboarding] +open_settings_again = "Systemeinstellungen erneut öffnen" +skip = "Vorerst überspringen" +switch_on_title = "Cap in den Systemeinstellungen aktivieren" + +[permission.onboarding.accessibility] +allow = "Bedienungshilfen erlauben" +body = "Mit Bedienungshilfen sieht Cap Ihre Klicks und Ihren Mauszeiger, nur auf diesem Mac, damit der automatische Zoom weiß, wohin er schauen soll." +done_body = "Der automatische Zoom kann Ihren Klicks folgen." +done_title = "Bedienungshilfen sind aktiviert" +title = "Lassen Sie Cap Ihrem Mauszeiger folgen" + +[permission.onboarding.all_set] +body = "Cap hat alles, was es braucht. Als Nächstes ein kurzer Blick darauf, was es kann." +regained = "Danke. Cap kann wieder aufnehmen." +title = "Alles bereit" + +[permission.onboarding.camera] +allow = "Kamera erlauben" +body = "Zeigen Sie sich in einer Ecke Ihrer Aufnahmen. Überspringen Sie dies, wenn Sie es nicht brauchen." +done_body = "Sie können Ihre Kamera zu jeder Aufnahme hinzufügen." +done_title = "Kamera ist aktiviert" +title = "Lassen Sie Cap Sie sehen" + +[permission.onboarding.checklist] +lede = "macOS bittet Sie zuerst, ein paar Dinge zu erlauben. Das dauert nur eine Minute." +regained_lede = "In den Systemeinstellungen wurde etwas deaktiviert. Aktivieren Sie es wieder, um weiter aufzunehmen." +regained_title = "Cap benötigt eine Berechtigung zurück" +title = "Machen wir Cap startklar" + +[permission.onboarding.hint] +drag_into_list = "Ziehen Sie Cap aus dem Feld unter den Systemeinstellungen in die Liste. Schon aufgeführt? Dann einfach aktivieren." +find_in_list = "Suchen Sie Cap in der Liste und aktivieren Sie es. Wenn macOS zuerst gefragt hat, wählen Sie „Systemeinstellungen öffnen“." +no_prompt = "Keine Abfrage erschienen? Aktivieren Sie Cap in den Systemeinstellungen." +open_settings = "Öffnen Sie die Systemeinstellungen, suchen Sie Cap in der Liste und aktivieren Sie es." +turned_off = "Dies wurde zuvor deaktiviert. Aktivieren Sie Cap daher in den Systemeinstellungen oder überspringen Sie es vorerst." + +[permission.onboarding.microphone] +allow = "Mikrofon erlauben" +body = "Fügen Sie Ihren Aufnahmen Ihre Stimme hinzu. Sie können dies überspringen und später aktivieren." +done_body = "Ihre Stimme kann in Ihren Aufnahmen zu hören sein." +done_title = "Mikrofon ist aktiviert" +title = "Lassen Sie Cap Sie hören" + +[permission.onboarding.relaunch] +body = "macOS aktiviert die Bildschirmaufnahme, sobald Cap neu startet. Sie machen genau hier weiter." +button = "Neu starten" +hint = "Aktiviert, aber es wartet noch? Starten Sie Cap neu, um es zu übernehmen." +later = "macOS übernimmt dies nach einem Neustart von Cap. Das können Sie tun, sobald Sie hier fertig sind." +title = "Ein Neustart, und alles ist bereit" + +[permission.onboarding.screen] +allow = "Bildschirmaufnahme erlauben" +body = "Mit der Bildschirmaufnahme kann Cap Ihre Bildschirme, Fenster und das Systemaudio aufnehmen." +done_body = "Cap kann Ihren Bildschirm aufnehmen." +done_title = "Bildschirmaufnahme ist aktiviert" +title = "Lassen Sie Cap Ihren Bildschirm sehen" + +[permission.onboarding.status] +allowed = "Erlaubt" +off = "In den Systemeinstellungen deaktiviert" +optional = "Optional" +required = "Erforderlich" +skipped = "Vorerst übersprungen" + +[permission.onboarding.waiting] +prompt = "Wählen Sie „Erlauben“ in der macOS-Abfrage" +system_settings = "Warten auf die Systemeinstellungen" + +[screenshot_editor] +loading_preview = "Vorschau wird geladen..." +zoom_in = "Vergrößern" +zoom_out = "Verkleinern" + +[screenshot_editor.annotation] +default_text = "Text" +done = "Fertig" +fill = "Füllung" +intensity = "Stärke" +mask_style = "Stil" +pixelate = "Verpixeln" +stroke = "Kontur" +transparent = "Transparent" + +[screenshot_editor.aspect] +auto = "Auto" +classic = "Klassisch ⋅4:3" +square = "Quadratisch ⋅1:1" +tall = "Hoch ⋅3:4" +vertical = "Vertikal ⋅9:16" +wide = "Breit ⋅16:9" + +[screenshot_editor.background] +animated_gradient = "Der animierte Verlauf wird bei Bildschirmfotos als Standbild dargestellt. Wählen Sie einen anderen Hintergrund, um ihn zu ersetzen." +blur_title = "Hintergrundunschärfe" +choose_image = "Klicken Sie zum Auswählen oder ziehen Sie ein Bild hierher" +color = "Farbe" +gradient = "Verlauf" +image = "Bild" +image_files = "Bild" +image_title = "Hintergrundbild" +wallpaper = "Wallpaper" + +[screenshot_editor.corner] +rounded = "Abgerundet" +squircle = "Squircle" +style = "ECKENSTIL" + +[screenshot_editor.crop] +full = "Ganz" +loading_frame = "Bild wird geladen…" +position = "Position" + +[screenshot_editor.error] +encode = "Das exportierte Bildschirmfoto konnte nicht kodiert werden: {error}" +load_meta = "Metadaten des Bildschirmfotos konnten nicht geladen werden: {error}" +load_metadata = "Metadaten des Bildschirmfotos konnten nicht geladen werden: {error}" +no_image = "Kein PNG in {path} gefunden" +not_screenshot = "Kein Bildschirmfoto-Paket" +open_image = "Bild konnte nicht geöffnet werden: {error}" +renderer_failed = "Der GPU-Renderer konnte nicht starten: {error}" +renderer_not_running = "Der Bildschirmfoto-Renderer läuft nicht" +renderer_stopped = "Der Renderer wurde vor dem Ende des Exports beendet" +save_project = "Fehler beim Speichern des Projekts: {error}" +serialize = "Die Bildschirmfoto-Konfiguration konnte nicht serialisiert werden: {error}" +sign_in_to_share = "Melden Sie sich im Browser bei Cap an und teilen Sie dann erneut" +too_large = "Die Bildgröße überschreitet das Maximum: {width}x{height}" +upgrade_required = "Für diese Funktion ist ein höherer Tarif erforderlich" +upload_task = "Der Upload ist fehlgeschlagen: {error}" + +[screenshot_editor.header] +aspect_ratio = "Seitenverhältnis" +copy = "In die Zwischenablage kopieren" +crop_image = "Bild zuschneiden" +more_actions = "Weitere Aktionen" + +[screenshot_editor.layers] +empty = "Noch keine Ebenen" +empty_hint = "Fügen Sie mit den Werkzeugen oben Anmerkungen hinzu" +reorder_hint = "Zum Sortieren ziehen • Oben = vorne" +title = "Ebenen" + +[screenshot_editor.menu] +delete = "Löschen" +open_folder = "Ordner öffnen" + +[screenshot_editor.share] +create_link = "Link zum Teilen erstellen" +preparing = "Upload wird vorbereitet" +rendering = "Bildschirmfoto wird gerendert" +uploading = "Bildschirmfoto wird hochgeladen" + +[screenshot_editor.style] +advanced_shadow = "Erweiterte Schatteneinstellungen" +background = "Hintergrund" +blur = "Unschärfe" +border = "Rahmen" +color = "Farbe" +corner_rounding = "Eckenrundung" +opacity = "Deckkraft" +padding = "Innenabstand" +rounding = "Rundung" +shadow = "Schatten" +size_label = "Größe" +width = "Breite" + +[screenshot_editor.toast] +copied = "Bildschirmfoto in die Zwischenablage kopiert!" +link_copied = "Link zum Teilen in die Zwischenablage kopiert" +saved = "Bildschirmfoto gespeichert!" + +[screenshot_editor.tool] +arrow = "Pfeil" +circle = "Kreis" +draw = "Zeichnen" +mask = "Maske" +rectangle = "Rechteck" +select = "Auswählen" +text_label = "Text" + +[transcription.download] +complete = "Download abgeschlossen" +failed = "Download fehlgeschlagen: {error}" +file_progress = "{file} wird geladen: {percent} %" +preparing = "Modell-Download wird vorbereitet" +progress = "Modell wird geladen: {percent} %" + +[transcription.error] +intel_mac = "Parakeet-Modelle sind auf Intel-Macs nicht verfügbar. Verwenden Sie stattdessen ein Whisper-Modell" +model_load = "Das Untertitelmodell konnte nicht geladen werden. Laden Sie es erneut herunter" +model_missing = "Untertitelmodell nicht gefunden. Bitte laden Sie es zuerst herunter" +no_audio = "Keine Audiospur in der Videodatei gefunden" +unsupported_language = "Die Modelle „Empfohlen“ und „Hohe Genauigkeit“ transkribieren nur Englisch und 24 weitere europäische Sprachen. Laden Sie das Modell „Klein“ oder „Mittel“ herunter, um diese Sprache zu untertiteln" + +[upload.status] +failed = "Upload benötigt Aufmerksamkeit" +paused = "Upload pausiert" +prepared = "Upload vorbereitet" +processing = "Upload wird verarbeitet" +retrying = "Upload wird wiederholt" +sign_in = "Zum erneuten Hochladen anmelden" +uploaded = "Hochgeladen" +uploading = "Wird hochgeladen" diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index c4415d07108..efb64d20d1e 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -588,3 +588,382 @@ click_or_press = "Click here, or press" get_started = "Get Started" space_key = "Space" tagline = "The open source screen sharing app for everyone." + +[auth] +sign_in_failed = "Sign in failed" + +[diagnostics] +already_running = "A diagnostic is already running." +cancelled = "The diagnostic was cancelled." +mode_both = { text = "Both", context = "Segment beside Studio and Instant: run the sync test in both modes." } + +[diagnostics.error] +create_folder = { text = "Failed to create the diagnostics folder: {error}", context = "{error} is a technical error message, usually in English." } +exited = { text = "The self-test exited with {status}", context = "{status} is a process exit status such as \"exit status: 1\"." } +no_output = "The self-test produced no output stream" +serialize = { text = "Failed to serialize the report: {error}", context = "{error} is a technical error message, usually in English." } +start_failed = { text = "Failed to start {program}: {error}", context = "{program} is a file path; {error} is a technical error message, usually in English." } +upload_logs = { text = "Failed to upload logs: {error}", context = "{error} is a technical error message, usually in English." } +upload_status = { text = "Upload failed with status: {status}", context = "{status} is an HTTP status such as 500 Internal Server Error." } +wait_failed = { text = "The self-test could not be waited on: {error}", context = "{error} is a technical error message, usually in English." } +write_failed = { text = "Failed to write {path}: {error}", context = "{path} is a file path; {error} is a technical error message, usually in English." } + +[diagnostics.stage] +analyzing = "Analyzing the recording..." +analyzing_instant = "Analyzing the instant recording..." +analyzing_studio = "Analyzing the studio recording..." +cancelling = "Cancelling..." +collecting = "Collecting system information..." +done = "Wrapping up the sync test..." +exporting = "Exporting the recording..." +exporting_instant = "Exporting the instant recording..." +exporting_studio = "Exporting the studio recording..." +finalizing = "Finalizing the recording..." +finalizing_instant = "Finalizing the instant recording..." +finalizing_studio = "Finalizing the studio recording..." +pattern = "Playing the test pattern..." +pattern_instant = "Playing the test pattern instant..." +pattern_studio = "Playing the test pattern studio..." +preparing = "Preparing the test..." +preparing_instant = "Preparing the instant test..." +preparing_studio = "Preparing the studio test..." +recording = "Recording the test..." +recording_instant = "Recording the instant test..." +recording_studio = "Recording the studio test..." +starting = "Starting the diagnostic..." + +[dialog] +no_microphone_body = "This recording will not include your voice." +open_recording_failed = { text = "Opening this recording failed: {error}", context = "{error} is a technical error message, usually in English." } + +[dialog.capture] +approve_shortcut = "Approve a recording shortcut, or activate the Cap Stop tray icon when it appears. Recording has not started." +area_unavailable = "The selected area changed or is no longer available. Select it again." +camera_needs_x11 = "Instant camera overlays require an X11 desktop on Linux" +camera_notice = "Cap will hide its camera preview and recording controls. Any selected camera will keep recording as a separate editable track." +camera_notice_instant = "Cap will hide its preview and controls. Any selected camera will appear at its preview position with the selected shape and effects. Keep the whole preview inside the capture area. Opening Cap will stop this Instant recording before showing its controls." +camera_outside_area = "Move the whole camera preview inside the capture area before starting Instant recording" +camera_preview_closed = "Open the selected camera preview before Instant recording" +cleanup_incomplete = { text = "Recording cleanup is incomplete: {error}. A new recording is blocked.", context = "{error} is a technical error message, usually in English." } +devices_changed = "Selected devices changed. Start again to confirm the new recording inputs." +devices_changed_before_start = "Selected devices changed before recording started. Start again to confirm the inputs." +shortcut_conflict = "Ctrl+Shift+F9 is assigned to another Cap action. Change that shortcut before recording." +shortcut_description = { text = "Start or stop this Cap recording", context = "Shown by the Linux desktop when it asks the user to approve a global shortcut." } +shortcut_hint = { text = "Press and release {shortcut} to start. Use the same shortcut to stop. This temporary shortcut does not change your settings.", context = "{shortcut} is a key combination such as Ctrl+Shift+F9." } +tray_stop_hint = "Activate the Cap Stop tray icon to start. Activate the same icon again to stop. Recording will not start until you use that control." +window_unavailable = "The selected window is no longer available. Select it again." + +[dialog.clipboard] +copy_file = { text = "Could not copy the file to the clipboard: {error}", context = "{error} is a technical error message, usually in English." } +copy_file_failed = "Failed to copy file to clipboard" +copy_image = { text = "Could not copy the image to the clipboard: {error}", context = "{error} is a technical error message, usually in English." } +copy_image_failed = "Failed to copy image to clipboard" +not_a_file = "Only files can be copied to the clipboard" +unavailable = "Clipboard unavailable" +unavailable_error = { text = "Clipboard unavailable: {error}", context = "{error} is a technical error message, usually in English." } + +[dialog.delete_recording] +already_deleting = "This recording is already being deleted." +close_failed = { text = "Could not close the editor: {error}", context = "{error} is a technical error message, usually in English." } +editor_changed = "The editor changed while deletion was being confirmed. The recording was not deleted." +editor_closed = "The editor has already closed. The recording was not deleted." +failed = { text = "Recording deletion failed: {error}", context = "{error} is a technical error message, usually in English." } +failed_title = "Could not delete recording" +finishing_body = "Cap is waiting for recording cleanup to finish before completing this deletion." +finishing_title = "Deletion is still finishing" +in_use = { text = "This recording is still in use. Finish or cancel its upload and try again: {error}", context = "{error} is a technical error message, usually in English." } +outside_library = "This recording is outside the recordings library. Its files were not changed." +recording_active = "Finish or cancel the recording before deleting this project." +retained_title = "Recording retained" + +[dialog.file] +audio_filter = { text = "Audio", context = "File type name in the file picker's filter list." } +busy = "A file dialog is already open. Finish or cancel it before trying again." +export_filter = { text = "Export", context = "File type name in the save dialog's filter list." } +failed = { text = "The file dialog could not complete: {error}", context = "{error} is a technical error message, usually in English." } +images_filter = { text = "Images", context = "File type name in the file picker's filter list." } +unavailable_title = "File dialog unavailable" +unconfirmed = "Cap could not confirm that the file dialog closed. Close any remaining file dialog and restart Cap before trying again." + +[dialog.mode_select] +instant = "Share instantly with a link. Uploads as you record." +screenshot = "Capture and annotate screenshots instantly." +studio = "Highest quality local recording for editing later." +subtitle = "Select how you want to capture your screen" +title = "Choose Recording Mode" + +[dialog.storage] +stopped_kept = "Recording stopped because storage is low. Your recording files were kept. Free up space, then recover the recording below." +stopped_saved = "Recording stopped because storage is low. Your recording was saved." +stopping = "Low storage. Stopping and saving your recording…" + +[dialog.teleprompter] +cue_markers = { text = "Cue markers", context = "Toggle in a 192px popover." } +hidden = "This window is hidden from Cap recordings" +mirror = { text = "Mirror text", context = "Toggle in a 192px popover." } +paused = "Scrolling paused with recording" +placeholder = "Paste or type your script…" + +[import] +error_title = "Import Error" +image_failed = { text = "Failed to import image: {error}", context = "{error} is a technical error message, usually in English." } +unsupported = "Unsupported media file." +video_failed = { text = "Failed to import video: {error}", context = "{error} is a technical error message, usually in English." } + +[import.filter] +image = { text = "Image Files", context = "File type name in the file picker's filter list." } +media = { text = "Media Files", context = "File type name in the file picker's filter list." } +video = { text = "Video Files", context = "File type name in the file picker's filter list." } + +[import.progress] +analyzing = "Analyzing video file..." +complete = "Import complete!" +converting = { text = "Converting video... {percent}%", context = "{percent} is a whole number." } +image = "Importing image..." +metadata = "Creating project metadata..." +starting = "Starting conversion..." + +[overlay] +choose_mode = "Choose recording mode" +countdown_tooltip = "Recording countdown" +display_fallback = { text = "Display {id}", context = "Name for a display the system does not name; {id} is a number." } + +[overlay.area] +draw = "Draw an area" +free_ratio = { text = "Free", context = "Aspect ratio button with no ratio locked; the button is about 56px wide." } +hint = "Click and drag to select an area" +minimum_size = { text = "Minimum size is {width} x {height}", context = "{width} and {height} are pixel sizes." } +too_small = { text = "{width} x {height} is too small", context = "{width} and {height} are pixel sizes." } + +[overlay.camera_only] +description = "Record using only your camera and microphone" +select_camera = "Please select a camera" + +[overlay.device] +camera_allow = "Allow camera access…" +camera_turn_on = "Turn on camera access in Settings…" +microphone_allow = "Allow microphone access…" +microphone_turn_on = "Turn on microphone access in Settings…" + +[overlay.start] +instant = { text = "Instant Mode — Record shareable link", context = "Second line of the start button, 11px, truncates." } +instant_sign_in = { text = "Instant Mode — Sign in to record instantly", context = "Second line of the start button, 11px, truncates." } +sign_in = "Sign In To Use" +start_recording = "Start Recording" +studio = { text = "Studio Mode — Record and edit locally", context = "Second line of the start button, 11px, truncates." } +take_screenshot = "Take Screenshot" + +[permission.guide] +accessibility_body = { text = "Drop it on the Accessibility list above, then make sure its switch is on.", context = "Accessibility is the name of the macOS System Settings pane; use the name macOS shows in this language." } +screen_body = { text = "Drop it on the Screen & System Audio Recording list above, then make sure its switch is on.", context = "Screen & System Audio Recording is the name of the macOS System Settings pane; use the name macOS shows in this language." } +title = "Drag Cap into the list" + +[permission.name] +accessibility = "Accessibility" +camera = "Camera" +microphone = "Microphone" +screen_recording = "Screen Recording" + +[permission.onboarding] +open_settings_again = "Open System Settings again" +skip = "Skip for now" +switch_on_title = "Switch Cap on in System Settings" + +[permission.onboarding.accessibility] +allow = "Allow accessibility" +body = "Accessibility lets Cap see your clicks and cursor, on this Mac only, so auto zoom knows where to look." +done_body = "Auto zoom can follow your clicks." +done_title = "Accessibility is on" +title = "Let Cap follow your cursor" + +[permission.onboarding.all_set] +body = "Cap has everything it needs. Next, a quick look at what it can do." +regained = "Thanks. Cap can record again." +title = "You're all set" + +[permission.onboarding.camera] +allow = "Allow camera" +body = "Show yourself in a corner of your recordings. Skip it if you don't need it." +done_body = "You can add your camera to any recording." +done_title = "Camera is on" +title = "Let Cap see you" + +[permission.onboarding.checklist] +lede = "macOS asks you to OK a few things first. It only takes a minute." +regained_lede = "Something was switched off in System Settings. Turn it back on to keep recording." +regained_title = "Cap needs a permission back" +title = "Let's get Cap ready" + +[permission.onboarding.hint] +drag_into_list = "Drag Cap from the panel under System Settings into the list. Already listed? Just switch it on." +find_in_list = "Find Cap in the list and switch it on. If macOS asked first, choose Open System Settings." +no_prompt = "No prompt showed up? Switch Cap on in System Settings." +open_settings = "Open System Settings, find Cap in the list and switch it on." +turned_off = "It was turned off earlier, so switch Cap on in System Settings. Or skip it for now." + +[permission.onboarding.microphone] +allow = "Allow microphone" +body = "Add your voice to your recordings. You can skip this and turn it on later." +done_body = "Your voice can be in your recordings." +done_title = "Microphone is on" +title = "Let Cap hear you" + +[permission.onboarding.relaunch] +body = "macOS turns on screen recording once Cap restarts. You'll pick up right here." +button = { text = "Relaunch", context = "Small secondary button beside a hint." } +hint = "Switched on but still waiting? Restart Cap to apply it." +later = "macOS applies it after Cap restarts. You can do that once you're done here." +title = "One restart and you're set" + +[permission.onboarding.screen] +allow = "Allow screen recording" +body = "Screen Recording lets Cap capture your displays, windows and system audio." +done_body = "Cap can capture your screen." +done_title = "Screen recording is on" +title = "Let Cap see your screen" + +[permission.onboarding.status] +allowed = "Allowed" +off = "Off in System Settings" +optional = "Optional" +required = "Required" +skipped = "Skipped for now" + +[permission.onboarding.waiting] +prompt = "Choose Allow in the macOS prompt" +system_settings = "Waiting for System Settings" + +[screenshot_editor] +loading_preview = "Loading preview..." +zoom_in = "Zoom In" +zoom_out = "Zoom Out" + +[screenshot_editor.annotation] +default_text = { text = "Text", context = "Initial content of a new text annotation drawn on a screenshot." } +done = "Done" +fill = "Fill" +intensity = "Intensity" +mask_style = { text = "Style", context = "Picks how a mask hides content: blur or pixelate." } +pixelate = "Pixelate" +stroke = { text = "Stroke", context = "Outline colour of a shape annotation." } +transparent = "Transparent" + +[screenshot_editor.aspect] +auto = { text = "Auto", context = "Aspect ratio follows the image. Shown in an 80px button." } +classic = "Classic ⋅4:3" +square = "Square ⋅1:1" +tall = "Tall ⋅3:4" +vertical = "Vertical ⋅9:16" +wide = "Wide ⋅16:9" + +[screenshot_editor.background] +animated_gradient = "Animated Gradient is rendered as a still image for screenshots. Choose another background to replace it." +blur_title = "Background Blur" +choose_image = "Click to select or drag and drop image" +color = { text = "Color", context = "Background source tab; four tabs share a 400px popover." } +gradient = { text = "Gradient", context = "Background source tab; four tabs share a 400px popover." } +image = { text = "Image", context = "Background source tab; four tabs share a 400px popover." } +image_files = { text = "Image", context = "File type name in the file picker's filter list." } +image_title = "Background Image" +wallpaper = { text = "Wallpaper", context = "Background source tab; four tabs share a 400px popover." } + +[screenshot_editor.corner] +rounded = "Rounded" +squircle = "Squircle" +style = { text = "CORNER STYLE", context = "Small all-caps heading above the corner style picker." } + +[screenshot_editor.crop] +full = "Full" +loading_frame = "Loading frame…" +position = "Position" + +[screenshot_editor.error] +encode = { text = "Failed to encode screenshot export: {error}", context = "{error} is a technical error message, usually in English." } +load_meta = { text = "Failed to load screenshot meta: {error}", context = "{error} is a technical error message, usually in English." } +load_metadata = { text = "Failed to load screenshot metadata: {error}", context = "{error} is a technical error message, usually in English." } +no_image = { text = "No PNG found in {path}", context = "{path} is a folder path." } +not_screenshot = "Not a screenshot bundle" +open_image = { text = "Failed to open image: {error}", context = "{error} is a technical error message, usually in English." } +renderer_failed = { text = "The GPU renderer failed to start: {error}", context = "{error} is a technical error message, usually in English." } +renderer_not_running = "The screenshot renderer is not running" +renderer_stopped = "The renderer stopped before the export finished" +save_project = { text = "Error saving project: {error}", context = "{error} is a technical error message, usually in English." } +serialize = { text = "Failed to serialize screenshot config: {error}", context = "{error} is a technical error message, usually in English." } +sign_in_to_share = "Sign in to Cap in your browser, then share again" +too_large = { text = "Image dimensions exceed maximum: {width}x{height}", context = "{width} and {height} are pixel sizes." } +upgrade_required = "This feature requires an upgraded plan" +upload_task = { text = "The upload task failed: {error}", context = "{error} is a technical error message, usually in English." } + +[screenshot_editor.header] +aspect_ratio = "Aspect Ratio" +copy = "Copy to Clipboard" +crop_image = "Crop Image" +more_actions = "More Actions" + +[screenshot_editor.layers] +empty = "No layers yet" +empty_hint = "Use the tools above to add annotations" +reorder_hint = "Drag to reorder • Top = front" +title = "Layers" + +[screenshot_editor.menu] +delete = "Delete" +open_folder = "Open Folder" + +[screenshot_editor.share] +create_link = "Create shareable link" +preparing = "Preparing upload" +rendering = "Rendering screenshot" +uploading = "Uploading screenshot" + +[screenshot_editor.style] +advanced_shadow = "Advanced shadow settings" +background = "Background" +blur = "Blur" +border = "Border" +color = "Color" +corner_rounding = "Corner Rounding" +opacity = "Opacity" +padding = "Padding" +rounding = "Rounding" +shadow = "Shadow" +size_label = "Size" +width = "Width" + +[screenshot_editor.toast] +copied = "Screenshot copied to clipboard!" +link_copied = "Share link copied to clipboard" +saved = "Screenshot saved!" + +[screenshot_editor.tool] +arrow = "Arrow" +circle = "Circle" +draw = "Draw" +mask = "Mask" +rectangle = "Rectangle" +select = "Select" +text_label = "Text" + +[transcription.download] +complete = "Download complete" +failed = { text = "Download failed: {error}", context = "{error} is a technical error message, usually in English." } +file_progress = { text = "Downloading {file}: {percent}%", context = "{file} is a model file name; {percent} is a whole number." } +preparing = "Preparing model download" +progress = { text = "Downloading model: {percent}%", context = "{percent} is a whole number." } + +[transcription.error] +intel_mac = "Parakeet models are not available on Intel Macs. Use a Whisper model instead" +model_load = "Failed to load the caption model. Try downloading it again" +model_missing = "Caption model not found. Please download it first" +no_audio = "No audio found in the video file" +unsupported_language = "The Recommended and High Accuracy models only transcribe English and 24 other European languages. Download the Small or Medium model to caption this language" + +[upload.status] +failed = "Upload needs attention" +paused = "Upload paused" +prepared = "Upload prepared" +processing = "Processing upload" +retrying = "Upload will retry" +sign_in = "Sign in to retry upload" +uploaded = "Uploaded" +uploading = "Uploading" diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index 00c4c9b74f7..e961fb1744c 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -586,3 +586,382 @@ click_or_press = "Haz clic aquí o pulsa" get_started = "Empezar" space_key = "Espacio" tagline = "La app de código abierto para compartir pantalla, para todo el mundo." + +[auth] +sign_in_failed = "No se pudo iniciar sesión" + +[diagnostics] +already_running = "Ya hay un diagnóstico en curso." +cancelled = "Se canceló el diagnóstico." +mode_both = "Ambos" + +[diagnostics.error] +create_folder = "No se pudo crear la carpeta de diagnóstico: {error}" +exited = "La autoprueba terminó con {status}" +no_output = "La autoprueba no produjo ningún flujo de salida" +serialize = "No se pudo serializar el informe: {error}" +start_failed = "No se pudo iniciar {program}: {error}" +upload_logs = "No se pudieron subir los registros: {error}" +upload_status = "La subida falló con el estado: {status}" +wait_failed = "No se pudo esperar a la autoprueba: {error}" +write_failed = "No se pudo escribir {path}: {error}" + +[diagnostics.stage] +analyzing = "Analizando la grabación..." +analyzing_instant = "Analizando la grabación Instant..." +analyzing_studio = "Analizando la grabación Studio..." +cancelling = "Cancelando..." +collecting = "Recopilando información del sistema..." +done = "Terminando la prueba de sincronización..." +exporting = "Exportando la grabación..." +exporting_instant = "Exportando la grabación Instant..." +exporting_studio = "Exportando la grabación Studio..." +finalizing = "Finalizando la grabación..." +finalizing_instant = "Finalizando la grabación Instant..." +finalizing_studio = "Finalizando la grabación Studio..." +pattern = "Reproduciendo el patrón de prueba..." +pattern_instant = "Reproduciendo el patrón de prueba (Instant)..." +pattern_studio = "Reproduciendo el patrón de prueba (Studio)..." +preparing = "Preparando la prueba..." +preparing_instant = "Preparando la prueba Instant..." +preparing_studio = "Preparando la prueba Studio..." +recording = "Grabando la prueba..." +recording_instant = "Grabando la prueba Instant..." +recording_studio = "Grabando la prueba Studio..." +starting = "Iniciando el diagnóstico..." + +[dialog] +no_microphone_body = "Esta grabación no incluirá tu voz." +open_recording_failed = "No se pudo abrir esta grabación: {error}" + +[dialog.capture] +approve_shortcut = "Aprueba un atajo de grabación o activa el icono de detener de Cap en la bandeja cuando aparezca. La grabación no ha empezado." +area_unavailable = "El área seleccionada cambió o ya no está disponible. Vuelve a seleccionarla." +camera_needs_x11 = "En Linux, las superposiciones de cámara de Instant requieren un escritorio X11" +camera_notice = "Cap ocultará la vista previa de la cámara y los controles de grabación. La cámara seleccionada seguirá grabándose como una pista editable aparte." +camera_notice_instant = "Cap ocultará su vista previa y sus controles. La cámara seleccionada aparecerá en la posición de su vista previa con la forma y los efectos elegidos. Mantén toda la vista previa dentro del área de captura. Abrir Cap detendrá esta grabación Instant antes de mostrar sus controles." +camera_outside_area = "Mueve toda la vista previa de la cámara dentro del área de captura antes de iniciar la grabación Instant" +camera_preview_closed = "Abre la vista previa de la cámara seleccionada antes de grabar en Instant" +cleanup_incomplete = "La limpieza de la grabación no se completó: {error}. Se bloqueó una nueva grabación." +devices_changed = "Los dispositivos seleccionados cambiaron. Vuelve a empezar para confirmar las nuevas entradas de grabación." +devices_changed_before_start = "Los dispositivos seleccionados cambiaron antes de empezar la grabación. Vuelve a empezar para confirmar las entradas." +shortcut_conflict = "Ctrl+Shift+F9 está asignado a otra acción de Cap. Cambia ese atajo antes de grabar." +shortcut_description = "Iniciar o detener esta grabación de Cap" +shortcut_hint = "Pulsa y suelta {shortcut} para empezar. Usa el mismo atajo para detener. Este atajo temporal no cambia tus ajustes." +tray_stop_hint = "Activa el icono de detener de Cap en la bandeja para empezar. Vuelve a activarlo para detener. La grabación no empezará hasta que uses ese control." +window_unavailable = "La ventana seleccionada ya no está disponible. Vuelve a seleccionarla." + +[dialog.clipboard] +copy_file = "No se pudo copiar el archivo al portapapeles: {error}" +copy_file_failed = "No se pudo copiar el archivo al portapapeles" +copy_image = "No se pudo copiar la imagen al portapapeles: {error}" +copy_image_failed = "No se pudo copiar la imagen al portapapeles" +not_a_file = "Solo se pueden copiar archivos al portapapeles" +unavailable = "Portapapeles no disponible" +unavailable_error = "Portapapeles no disponible: {error}" + +[dialog.delete_recording] +already_deleting = "Esta grabación ya se está eliminando." +close_failed = "No se pudo cerrar el editor: {error}" +editor_changed = "El editor cambió mientras se confirmaba la eliminación. La grabación no se eliminó." +editor_closed = "El editor ya se cerró. La grabación no se eliminó." +failed = "No se pudo eliminar la grabación: {error}" +failed_title = "No se pudo eliminar la grabación" +finishing_body = "Cap está esperando a que termine la limpieza de la grabación antes de completar la eliminación." +finishing_title = "La eliminación aún está terminando" +in_use = "Esta grabación aún está en uso. Termina o cancela su subida e inténtalo de nuevo: {error}" +outside_library = "Esta grabación está fuera de la biblioteca de grabaciones. Sus archivos no se modificaron." +recording_active = "Termina o cancela la grabación antes de eliminar este proyecto." +retained_title = "Grabación conservada" + +[dialog.file] +audio_filter = "Audio" +busy = "Ya hay un cuadro de diálogo de archivos abierto. Termina o cancélalo antes de volver a intentarlo." +export_filter = "Exportación" +failed = "El cuadro de diálogo de archivos no pudo completarse: {error}" +images_filter = "Imágenes" +unavailable_title = "Cuadro de diálogo de archivos no disponible" +unconfirmed = "Cap no pudo confirmar que el cuadro de diálogo de archivos se cerró. Cierra cualquier cuadro de diálogo abierto y reinicia Cap antes de volver a intentarlo." + +[dialog.mode_select] +instant = "Comparte al instante con un enlace. Se sube mientras grabas." +screenshot = "Haz y anota capturas de pantalla al instante." +studio = "Grabación local de máxima calidad para editar después." +subtitle = "Elige cómo quieres capturar tu pantalla" +title = "Elegir modo de grabación" + +[dialog.storage] +stopped_kept = "La grabación se detuvo porque queda poco espacio. Tus archivos de grabación se conservaron. Libera espacio y recupera la grabación abajo." +stopped_saved = "La grabación se detuvo porque queda poco espacio. Tu grabación se guardó." +stopping = "Queda poco espacio. Deteniendo y guardando tu grabación…" + +[dialog.teleprompter] +cue_markers = "Marcadores" +hidden = "Esta ventana no aparece en las grabaciones de Cap" +mirror = "Texto en espejo" +paused = "Desplazamiento en pausa junto con la grabación" +placeholder = "Pega o escribe tu guion…" + +[import] +error_title = "Error de importación" +image_failed = "No se pudo importar la imagen: {error}" +unsupported = "Archivo multimedia no compatible." +video_failed = "No se pudo importar el video: {error}" + +[import.filter] +image = "Archivos de imagen" +media = "Archivos multimedia" +video = "Archivos de video" + +[import.progress] +analyzing = "Analizando el archivo de video..." +complete = "¡Importación completada!" +converting = "Convirtiendo el video... {percent} %" +image = "Importando la imagen..." +metadata = "Creando los metadatos del proyecto..." +starting = "Iniciando la conversión..." + +[overlay] +choose_mode = "Elegir modo de grabación" +countdown_tooltip = "Cuenta atrás de la grabación" +display_fallback = "Pantalla {id}" + +[overlay.area] +draw = "Dibuja un área" +free_ratio = "Libre" +hint = "Haz clic y arrastra para seleccionar un área" +minimum_size = "El tamaño mínimo es {width} x {height}" +too_small = "{width} x {height} es demasiado pequeño" + +[overlay.camera_only] +description = "Graba solo con tu cámara y tu micrófono" +select_camera = "Selecciona una cámara" + +[overlay.device] +camera_allow = "Permitir acceso a la cámara…" +camera_turn_on = "Activar el acceso a la cámara en Ajustes…" +microphone_allow = "Permitir acceso al micrófono…" +microphone_turn_on = "Activar el acceso al micrófono en Ajustes…" + +[overlay.start] +instant = "Modo Instant: graba un enlace para compartir" +instant_sign_in = "Modo Instant: inicia sesión para grabar al instante" +sign_in = "Inicia sesión para usar" +start_recording = "Iniciar grabación" +studio = "Modo Studio: graba y edita en local" +take_screenshot = "Hacer captura de pantalla" + +[permission.guide] +accessibility_body = "Suéltalo en la lista «Accesibilidad» de arriba y asegúrate de que su interruptor esté activado." +screen_body = "Suéltalo en la lista «Grabación de pantalla y audio del sistema» de arriba y asegúrate de que su interruptor esté activado." +title = "Arrastra Cap a la lista" + +[permission.name] +accessibility = "Accesibilidad" +camera = "Cámara" +microphone = "Micrófono" +screen_recording = "Grabación de pantalla" + +[permission.onboarding] +open_settings_again = "Abrir Ajustes del Sistema otra vez" +skip = "Omitir por ahora" +switch_on_title = "Activa Cap en Ajustes del Sistema" + +[permission.onboarding.accessibility] +allow = "Permitir accesibilidad" +body = "La accesibilidad permite que Cap vea tus clics y tu cursor, solo en este Mac, para que el zoom automático sepa dónde mirar." +done_body = "El zoom automático puede seguir tus clics." +done_title = "La accesibilidad está activada" +title = "Deja que Cap siga tu cursor" + +[permission.onboarding.all_set] +body = "Cap tiene todo lo que necesita. Ahora, un vistazo rápido a lo que puede hacer." +regained = "Gracias. Cap ya puede volver a grabar." +title = "Todo listo" + +[permission.onboarding.camera] +allow = "Permitir cámara" +body = "Aparece en una esquina de tus grabaciones. Omítelo si no lo necesitas." +done_body = "Puedes añadir tu cámara a cualquier grabación." +done_title = "La cámara está activada" +title = "Deja que Cap te vea" + +[permission.onboarding.checklist] +lede = "macOS te pedirá primero que autorices algunas cosas. Solo lleva un minuto." +regained_lede = "Algo se desactivó en Ajustes del Sistema. Vuelve a activarlo para seguir grabando." +regained_title = "Cap necesita recuperar un permiso" +title = "Preparemos Cap" + +[permission.onboarding.hint] +drag_into_list = "Arrastra Cap desde el panel bajo Ajustes del Sistema a la lista. ¿Ya aparece? Solo actívalo." +find_in_list = "Busca Cap en la lista y actívalo. Si macOS te preguntó antes, elige «Abrir Ajustes del Sistema»." +no_prompt = "¿No apareció ningún aviso? Activa Cap en Ajustes del Sistema." +open_settings = "Abre Ajustes del Sistema, busca Cap en la lista y actívalo." +turned_off = "Se desactivó antes, así que activa Cap en Ajustes del Sistema. O omítelo por ahora." + +[permission.onboarding.microphone] +allow = "Permitir micrófono" +body = "Añade tu voz a tus grabaciones. Puedes omitirlo y activarlo más tarde." +done_body = "Tu voz puede aparecer en tus grabaciones." +done_title = "El micrófono está activado" +title = "Deja que Cap te oiga" + +[permission.onboarding.relaunch] +body = "macOS activa la grabación de pantalla cuando Cap se reinicia. Continuarás justo aquí." +button = "Reiniciar" +hint = "¿Lo activaste y sigue esperando? Reinicia Cap para aplicarlo." +later = "macOS lo aplica cuando Cap se reinicia. Puedes hacerlo cuando termines aquí." +title = "Un reinicio y listo" + +[permission.onboarding.screen] +allow = "Permitir grabación de pantalla" +body = "La grabación de pantalla permite que Cap capture tus pantallas, ventanas y el audio del sistema." +done_body = "Cap puede capturar tu pantalla." +done_title = "La grabación de pantalla está activada" +title = "Deja que Cap vea tu pantalla" + +[permission.onboarding.status] +allowed = "Permitido" +off = "Desactivado en Ajustes del Sistema" +optional = "Opcional" +required = "Obligatorio" +skipped = "Omitido por ahora" + +[permission.onboarding.waiting] +prompt = "Elige «Permitir» en el aviso de macOS" +system_settings = "Esperando a Ajustes del Sistema" + +[screenshot_editor] +loading_preview = "Cargando vista previa..." +zoom_in = "Acercar" +zoom_out = "Alejar" + +[screenshot_editor.annotation] +default_text = "Texto" +done = "Listo" +fill = "Relleno" +intensity = "Intensidad" +mask_style = "Estilo" +pixelate = "Pixelar" +stroke = "Trazo" +transparent = "Transparente" + +[screenshot_editor.aspect] +auto = "Auto" +classic = "Clásico ⋅4:3" +square = "Cuadrado ⋅1:1" +tall = "Alto ⋅3:4" +vertical = "Vertical ⋅9:16" +wide = "Panorámico ⋅16:9" + +[screenshot_editor.background] +animated_gradient = "El degradado animado se muestra como imagen fija en las capturas de pantalla. Elige otro fondo para reemplazarlo." +blur_title = "Desenfoque del fondo" +choose_image = "Haz clic para elegir o arrastra y suelta una imagen" +color = "Color" +gradient = "Degradado" +image = "Imagen" +image_files = "Imagen" +image_title = "Imagen de fondo" +wallpaper = "Fondo" + +[screenshot_editor.corner] +rounded = "Redondeado" +squircle = "Squircle" +style = "ESTILO DE ESQUINAS" + +[screenshot_editor.crop] +full = "Completo" +loading_frame = "Cargando fotograma…" +position = "Posición" + +[screenshot_editor.error] +encode = "No se pudo codificar la captura de pantalla exportada: {error}" +load_meta = "No se pudieron cargar los metadatos de la captura de pantalla: {error}" +load_metadata = "No se pudieron cargar los metadatos de la captura de pantalla: {error}" +no_image = "No se encontró ningún PNG en {path}" +not_screenshot = "No es un paquete de captura de pantalla" +open_image = "No se pudo abrir la imagen: {error}" +renderer_failed = "No se pudo iniciar el renderizador de GPU: {error}" +renderer_not_running = "El renderizador de capturas de pantalla no está en ejecución" +renderer_stopped = "El renderizador se detuvo antes de terminar la exportación" +save_project = "Error al guardar el proyecto: {error}" +serialize = "No se pudo serializar la configuración de la captura de pantalla: {error}" +sign_in_to_share = "Inicia sesión en Cap en tu navegador y vuelve a compartir" +too_large = "Las dimensiones de la imagen superan el máximo: {width}x{height}" +upgrade_required = "Esta función requiere un plan superior" +upload_task = "La tarea de subida falló: {error}" + +[screenshot_editor.header] +aspect_ratio = "Relación de aspecto" +copy = "Copiar al portapapeles" +crop_image = "Recortar imagen" +more_actions = "Más acciones" + +[screenshot_editor.layers] +empty = "Aún no hay capas" +empty_hint = "Usa las herramientas de arriba para añadir anotaciones" +reorder_hint = "Arrastra para reordenar • Arriba = delante" +title = "Capas" + +[screenshot_editor.menu] +delete = "Eliminar" +open_folder = "Abrir carpeta" + +[screenshot_editor.share] +create_link = "Crear enlace para compartir" +preparing = "Preparando la subida" +rendering = "Renderizando la captura de pantalla" +uploading = "Subiendo la captura de pantalla" + +[screenshot_editor.style] +advanced_shadow = "Ajustes avanzados de sombra" +background = "Fondo" +blur = "Desenfoque" +border = "Borde" +color = "Color" +corner_rounding = "Redondeo de esquinas" +opacity = "Opacidad" +padding = "Margen" +rounding = "Redondeo" +shadow = "Sombra" +size_label = "Tamaño" +width = "Ancho" + +[screenshot_editor.toast] +copied = "¡Captura de pantalla copiada al portapapeles!" +link_copied = "Enlace para compartir copiado al portapapeles" +saved = "¡Captura de pantalla guardada!" + +[screenshot_editor.tool] +arrow = "Flecha" +circle = "Círculo" +draw = "Dibujar" +mask = "Máscara" +rectangle = "Rectángulo" +select = "Seleccionar" +text_label = "Texto" + +[transcription.download] +complete = "Descarga completada" +failed = "La descarga falló: {error}" +file_progress = "Descargando {file}: {percent} %" +preparing = "Preparando la descarga del modelo" +progress = "Descargando el modelo: {percent} %" + +[transcription.error] +intel_mac = "Los modelos Parakeet no están disponibles en Mac con Intel. Usa un modelo Whisper" +model_load = "No se pudo cargar el modelo de subtítulos. Intenta descargarlo de nuevo" +model_missing = "No se encontró el modelo de subtítulos. Descárgalo primero" +no_audio = "No se encontró audio en el archivo de video" +unsupported_language = "Los modelos Recomendado y Alta precisión solo transcriben inglés y otros 24 idiomas europeos. Descarga el modelo Pequeño o Mediano para subtitular este idioma" + +[upload.status] +failed = "La subida necesita atención" +paused = "Subida en pausa" +prepared = "Subida preparada" +processing = "Procesando la subida" +retrying = "La subida se reintentará" +sign_in = "Inicia sesión para reintentar la subida" +uploaded = "Subido" +uploading = "Subiendo" diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index e70141f4c4e..36126cda6dc 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -586,3 +586,382 @@ click_or_press = "Cliquez ici ou appuyez sur" get_started = "Commencer" space_key = "Espace" tagline = "L'app open source de partage d'écran pour tout le monde." + +[auth] +sign_in_failed = "Échec de la connexion" + +[diagnostics] +already_running = "Un diagnostic est déjà en cours." +cancelled = "Le diagnostic a été annulé." +mode_both = "Les deux" + +[diagnostics.error] +create_folder = "Impossible de créer le dossier de diagnostic : {error}" +exited = "L'autotest s'est arrêté avec {status}" +no_output = "L'autotest n'a produit aucun flux de sortie" +serialize = "Impossible de sérialiser le rapport : {error}" +start_failed = "Impossible de lancer {program} : {error}" +upload_logs = "Impossible d'envoyer les journaux : {error}" +upload_status = "Échec de l'envoi, statut : {status}" +wait_failed = "Impossible d'attendre la fin de l'autotest : {error}" +write_failed = "Impossible d'écrire {path} : {error}" + +[diagnostics.stage] +analyzing = "Analyse de l'enregistrement..." +analyzing_instant = "Analyse de l'enregistrement Instant..." +analyzing_studio = "Analyse de l'enregistrement Studio..." +cancelling = "Annulation..." +collecting = "Collecte des informations système..." +done = "Finalisation du test de synchronisation..." +exporting = "Export de l'enregistrement..." +exporting_instant = "Export de l'enregistrement Instant..." +exporting_studio = "Export de l'enregistrement Studio..." +finalizing = "Finalisation de l'enregistrement..." +finalizing_instant = "Finalisation de l'enregistrement Instant..." +finalizing_studio = "Finalisation de l'enregistrement Studio..." +pattern = "Lecture de la mire de test..." +pattern_instant = "Lecture de la mire de test (Instant)..." +pattern_studio = "Lecture de la mire de test (Studio)..." +preparing = "Préparation du test..." +preparing_instant = "Préparation du test Instant..." +preparing_studio = "Préparation du test Studio..." +recording = "Enregistrement du test..." +recording_instant = "Enregistrement du test Instant..." +recording_studio = "Enregistrement du test Studio..." +starting = "Démarrage du diagnostic..." + +[dialog] +no_microphone_body = "Cet enregistrement n'inclura pas votre voix." +open_recording_failed = "Impossible d'ouvrir cet enregistrement : {error}" + +[dialog.capture] +approve_shortcut = "Approuvez un raccourci d'enregistrement, ou activez l'icône d'arrêt de Cap dans la zone de notification lorsqu'elle apparaît. L'enregistrement n'a pas commencé." +area_unavailable = "La zone sélectionnée a changé ou n'est plus disponible. Sélectionnez-la à nouveau." +camera_needs_x11 = "Sous Linux, les incrustations de caméra en mode Instant nécessitent un bureau X11" +camera_notice = "Cap masquera son aperçu de caméra et ses commandes d'enregistrement. La caméra sélectionnée continuera d'être enregistrée sur une piste distincte modifiable." +camera_notice_instant = "Cap masquera son aperçu et ses commandes. La caméra sélectionnée apparaîtra à l'emplacement de son aperçu, avec la forme et les effets choisis. Gardez tout l'aperçu dans la zone de capture. Ouvrir Cap arrêtera cet enregistrement Instant avant d'afficher ses commandes." +camera_outside_area = "Placez tout l'aperçu de la caméra dans la zone de capture avant de lancer l'enregistrement Instant" +camera_preview_closed = "Ouvrez l'aperçu de la caméra sélectionnée avant l'enregistrement Instant" +cleanup_incomplete = "Le nettoyage de l'enregistrement est incomplet : {error}. Un nouvel enregistrement est bloqué." +devices_changed = "Les appareils sélectionnés ont changé. Recommencez pour confirmer les nouvelles entrées d'enregistrement." +devices_changed_before_start = "Les appareils sélectionnés ont changé avant le début de l'enregistrement. Recommencez pour confirmer les entrées." +shortcut_conflict = "Ctrl+Shift+F9 est attribué à une autre action de Cap. Modifiez ce raccourci avant d'enregistrer." +shortcut_description = "Lancer ou arrêter cet enregistrement Cap" +shortcut_hint = "Appuyez sur {shortcut} puis relâchez pour démarrer. Utilisez le même raccourci pour arrêter. Ce raccourci temporaire ne modifie pas vos réglages." +tray_stop_hint = "Activez l'icône d'arrêt de Cap dans la zone de notification pour démarrer. Activez-la à nouveau pour arrêter. L'enregistrement ne commencera pas tant que vous n'aurez pas utilisé cette commande." +window_unavailable = "La fenêtre sélectionnée n'est plus disponible. Sélectionnez-la à nouveau." + +[dialog.clipboard] +copy_file = "Impossible de copier le fichier dans le presse-papiers : {error}" +copy_file_failed = "Impossible de copier le fichier dans le presse-papiers" +copy_image = "Impossible de copier l'image dans le presse-papiers : {error}" +copy_image_failed = "Impossible de copier l'image dans le presse-papiers" +not_a_file = "Seuls des fichiers peuvent être copiés dans le presse-papiers" +unavailable = "Presse-papiers indisponible" +unavailable_error = "Presse-papiers indisponible : {error}" + +[dialog.delete_recording] +already_deleting = "Cet enregistrement est déjà en cours de suppression." +close_failed = "Impossible de fermer l'éditeur : {error}" +editor_changed = "L'éditeur a changé pendant la confirmation de la suppression. L'enregistrement n'a pas été supprimé." +editor_closed = "L'éditeur est déjà fermé. L'enregistrement n'a pas été supprimé." +failed = "La suppression de l'enregistrement a échoué : {error}" +failed_title = "Impossible de supprimer l'enregistrement" +finishing_body = "Cap attend la fin du nettoyage de l'enregistrement avant de terminer cette suppression." +finishing_title = "La suppression est toujours en cours" +in_use = "Cet enregistrement est encore utilisé. Terminez ou annulez son envoi, puis réessayez : {error}" +outside_library = "Cet enregistrement se trouve hors de la bibliothèque d'enregistrements. Ses fichiers n'ont pas été modifiés." +recording_active = "Terminez ou annulez l'enregistrement avant de supprimer ce projet." +retained_title = "Enregistrement conservé" + +[dialog.file] +audio_filter = "Audio" +busy = "Une boîte de dialogue de fichier est déjà ouverte. Terminez-la ou annulez-la avant de réessayer." +export_filter = "Export" +failed = "La boîte de dialogue de fichier n'a pas pu aboutir : {error}" +images_filter = "Images" +unavailable_title = "Boîte de dialogue de fichier indisponible" +unconfirmed = "Cap n'a pas pu confirmer la fermeture de la boîte de dialogue de fichier. Fermez toute boîte de dialogue restante et redémarrez Cap avant de réessayer." + +[dialog.mode_select] +instant = "Partagez instantanément avec un lien. L'envoi se fait pendant l'enregistrement." +screenshot = "Prenez et annotez des captures d'écran instantanément." +studio = "Enregistrement local en qualité maximale, à modifier plus tard." +subtitle = "Choisissez comment capturer votre écran" +title = "Choisir le mode d'enregistrement" + +[dialog.storage] +stopped_kept = "L'enregistrement s'est arrêté, car l'espace de stockage est faible. Vos fichiers ont été conservés. Libérez de l'espace, puis récupérez l'enregistrement ci-dessous." +stopped_saved = "L'enregistrement s'est arrêté, car l'espace de stockage est faible. Votre enregistrement a été sauvegardé." +stopping = "Espace de stockage faible. Arrêt et sauvegarde de votre enregistrement…" + +[dialog.teleprompter] +cue_markers = "Repères" +hidden = "Cette fenêtre est masquée dans les enregistrements Cap" +mirror = "Texte en miroir" +paused = "Défilement en pause avec l'enregistrement" +placeholder = "Collez ou saisissez votre script…" + +[import] +error_title = "Erreur d'importation" +image_failed = "Impossible d'importer l'image : {error}" +unsupported = "Fichier multimédia non pris en charge." +video_failed = "Impossible d'importer la vidéo : {error}" + +[import.filter] +image = "Fichiers image" +media = "Fichiers multimédias" +video = "Fichiers vidéo" + +[import.progress] +analyzing = "Analyse du fichier vidéo..." +complete = "Importation terminée !" +converting = "Conversion de la vidéo... {percent} %" +image = "Importation de l'image..." +metadata = "Création des métadonnées du projet..." +starting = "Démarrage de la conversion..." + +[overlay] +choose_mode = "Choisir le mode d'enregistrement" +countdown_tooltip = "Compte à rebours de l'enregistrement" +display_fallback = "Écran {id}" + +[overlay.area] +draw = "Dessinez une zone" +free_ratio = "Libre" +hint = "Cliquez et faites glisser pour sélectionner une zone" +minimum_size = "La taille minimale est de {width} x {height}" +too_small = "{width} x {height} est trop petit" + +[overlay.camera_only] +description = "Enregistrez uniquement avec votre caméra et votre micro" +select_camera = "Veuillez choisir une caméra" + +[overlay.device] +camera_allow = "Autoriser l'accès à la caméra…" +camera_turn_on = "Activer l'accès à la caméra dans les réglages…" +microphone_allow = "Autoriser l'accès au micro…" +microphone_turn_on = "Activer l'accès au micro dans les réglages…" + +[overlay.start] +instant = "Mode Instant : enregistrer un lien de partage" +instant_sign_in = "Mode Instant : connectez-vous pour enregistrer" +sign_in = "Se connecter pour utiliser" +start_recording = "Lancer l'enregistrement" +studio = "Mode Studio : enregistrer et modifier en local" +take_screenshot = "Prendre une capture d'écran" + +[permission.guide] +accessibility_body = "Déposez-le dans la liste « Accessibilité » ci-dessus, puis vérifiez que son interrupteur est activé." +screen_body = "Déposez-le dans la liste « Enregistrement de l'écran et de l'audio du système » ci-dessus, puis vérifiez que son interrupteur est activé." +title = "Faites glisser Cap dans la liste" + +[permission.name] +accessibility = "Accessibilité" +camera = "Caméra" +microphone = "Micro" +screen_recording = "Enregistrement de l'écran" + +[permission.onboarding] +open_settings_again = "Rouvrir Réglages Système" +skip = "Ignorer pour l'instant" +switch_on_title = "Activez Cap dans Réglages Système" + +[permission.onboarding.accessibility] +allow = "Autoriser l'accessibilité" +body = "L'accessibilité permet à Cap de voir vos clics et votre curseur, uniquement sur ce Mac, pour que le zoom automatique sache où regarder." +done_body = "Le zoom automatique peut suivre vos clics." +done_title = "L'accessibilité est activée" +title = "Laissez Cap suivre votre curseur" + +[permission.onboarding.all_set] +body = "Cap a tout ce qu'il lui faut. Voyons maintenant rapidement ce qu'il sait faire." +regained = "Merci. Cap peut de nouveau enregistrer." +title = "Tout est prêt" + +[permission.onboarding.camera] +allow = "Autoriser la caméra" +body = "Apparaissez dans un coin de vos enregistrements. Ignorez cette étape si vous n'en avez pas besoin." +done_body = "Vous pouvez ajouter votre caméra à n'importe quel enregistrement." +done_title = "La caméra est activée" +title = "Laissez Cap vous voir" + +[permission.onboarding.checklist] +lede = "macOS vous demande d'abord d'autoriser quelques éléments. Cela ne prend qu'une minute." +regained_lede = "Un élément a été désactivé dans Réglages Système. Réactivez-le pour continuer à enregistrer." +regained_title = "Cap a besoin de récupérer une autorisation" +title = "Préparons Cap" + +[permission.onboarding.hint] +drag_into_list = "Faites glisser Cap depuis le panneau sous Réglages Système vers la liste. Déjà dans la liste ? Activez-le simplement." +find_in_list = "Trouvez Cap dans la liste et activez-le. Si macOS vous a d'abord posé la question, choisissez « Ouvrir Réglages Système »." +no_prompt = "Aucune demande n'est apparue ? Activez Cap dans Réglages Système." +open_settings = "Ouvrez Réglages Système, trouvez Cap dans la liste et activez-le." +turned_off = "Cette autorisation a été désactivée auparavant : activez Cap dans Réglages Système, ou ignorez cette étape pour l'instant." + +[permission.onboarding.microphone] +allow = "Autoriser le micro" +body = "Ajoutez votre voix à vos enregistrements. Vous pouvez ignorer cette étape et l'activer plus tard." +done_body = "Votre voix peut figurer dans vos enregistrements." +done_title = "Le micro est activé" +title = "Laissez Cap vous entendre" + +[permission.onboarding.relaunch] +body = "macOS active l'enregistrement de l'écran une fois Cap redémarré. Vous reprendrez ici même." +button = "Relancer" +hint = "Activé mais toujours en attente ? Redémarrez Cap pour l'appliquer." +later = "macOS l'applique après le redémarrage de Cap. Vous pourrez le faire une fois terminé ici." +title = "Un redémarrage et c'est prêt" + +[permission.onboarding.screen] +allow = "Autoriser l'enregistrement de l'écran" +body = "L'enregistrement de l'écran permet à Cap de capturer vos écrans, vos fenêtres et l'audio du système." +done_body = "Cap peut capturer votre écran." +done_title = "L'enregistrement de l'écran est activé" +title = "Laissez Cap voir votre écran" + +[permission.onboarding.status] +allowed = "Autorisé" +off = "Désactivé dans Réglages Système" +optional = "Facultatif" +required = "Obligatoire" +skipped = "Ignoré pour l'instant" + +[permission.onboarding.waiting] +prompt = "Choisissez « Autoriser » dans la demande de macOS" +system_settings = "En attente de Réglages Système" + +[screenshot_editor] +loading_preview = "Chargement de l'aperçu..." +zoom_in = "Zoom avant" +zoom_out = "Zoom arrière" + +[screenshot_editor.annotation] +default_text = "Texte" +done = "Terminé" +fill = "Remplissage" +intensity = "Intensité" +mask_style = "Style" +pixelate = "Pixeliser" +stroke = "Contour" +transparent = "Transparent" + +[screenshot_editor.aspect] +auto = "Auto" +classic = "Classique ⋅4:3" +square = "Carré ⋅1:1" +tall = "Haut ⋅3:4" +vertical = "Vertical ⋅9:16" +wide = "Large ⋅16:9" + +[screenshot_editor.background] +animated_gradient = "Le dégradé animé est rendu comme une image fixe dans les captures d'écran. Choisissez un autre arrière-plan pour le remplacer." +blur_title = "Flou de l'arrière-plan" +choose_image = "Cliquez pour choisir ou glissez-déposez une image" +color = "Couleur" +gradient = "Dégradé" +image = "Image" +image_files = "Image" +image_title = "Image d'arrière-plan" +wallpaper = "Fond d'écran" + +[screenshot_editor.corner] +rounded = "Arrondi" +squircle = "Squircle" +style = "STYLE DES COINS" + +[screenshot_editor.crop] +full = "Entier" +loading_frame = "Chargement de l'image…" +position = "Position" + +[screenshot_editor.error] +encode = "Impossible d'encoder l'export de la capture d'écran : {error}" +load_meta = "Impossible de charger les métadonnées de la capture d'écran : {error}" +load_metadata = "Impossible de charger les métadonnées de la capture d'écran : {error}" +no_image = "Aucun PNG trouvé dans {path}" +not_screenshot = "Ce n'est pas un dossier de capture d'écran" +open_image = "Impossible d'ouvrir l'image : {error}" +renderer_failed = "Le moteur de rendu GPU n'a pas pu démarrer : {error}" +renderer_not_running = "Le moteur de rendu des captures d'écran n'est pas lancé" +renderer_stopped = "Le moteur de rendu s'est arrêté avant la fin de l'export" +save_project = "Erreur lors de l'enregistrement du projet : {error}" +serialize = "Impossible de sérialiser la configuration de la capture d'écran : {error}" +sign_in_to_share = "Connectez-vous à Cap dans votre navigateur, puis partagez à nouveau" +too_large = "Les dimensions de l'image dépassent le maximum : {width}x{height}" +upgrade_required = "Cette fonctionnalité nécessite une offre supérieure" +upload_task = "La tâche d'envoi a échoué : {error}" + +[screenshot_editor.header] +aspect_ratio = "Format" +copy = "Copier dans le presse-papiers" +crop_image = "Recadrer l'image" +more_actions = "Plus d'actions" + +[screenshot_editor.layers] +empty = "Aucun calque pour l'instant" +empty_hint = "Utilisez les outils ci-dessus pour ajouter des annotations" +reorder_hint = "Glissez pour réorganiser • Haut = premier plan" +title = "Calques" + +[screenshot_editor.menu] +delete = "Supprimer" +open_folder = "Ouvrir le dossier" + +[screenshot_editor.share] +create_link = "Créer un lien de partage" +preparing = "Préparation de l'envoi" +rendering = "Rendu de la capture d'écran" +uploading = "Envoi de la capture d'écran" + +[screenshot_editor.style] +advanced_shadow = "Réglages avancés de l'ombre" +background = "Arrière-plan" +blur = "Flou" +border = "Bordure" +color = "Couleur" +corner_rounding = "Arrondi des coins" +opacity = "Opacité" +padding = "Marge" +rounding = "Arrondi" +shadow = "Ombre" +size_label = "Taille" +width = "Largeur" + +[screenshot_editor.toast] +copied = "Capture d'écran copiée dans le presse-papiers !" +link_copied = "Lien de partage copié dans le presse-papiers" +saved = "Capture d'écran enregistrée !" + +[screenshot_editor.tool] +arrow = "Flèche" +circle = "Cercle" +draw = "Dessin" +mask = "Masque" +rectangle = "Rectangle" +select = "Sélection" +text_label = "Texte" + +[transcription.download] +complete = "Téléchargement terminé" +failed = "Échec du téléchargement : {error}" +file_progress = "Téléchargement de {file} : {percent} %" +preparing = "Préparation du téléchargement du modèle" +progress = "Téléchargement du modèle : {percent} %" + +[transcription.error] +intel_mac = "Les modèles Parakeet ne sont pas disponibles sur les Mac Intel. Utilisez plutôt un modèle Whisper" +model_load = "Impossible de charger le modèle de sous-titres. Essayez de le télécharger à nouveau" +model_missing = "Modèle de sous-titres introuvable. Téléchargez-le d'abord" +no_audio = "Aucun audio trouvé dans le fichier vidéo" +unsupported_language = "Les modèles Recommandé et Haute précision ne transcrivent que l'anglais et 24 autres langues européennes. Téléchargez le modèle Petit ou Moyen pour sous-titrer cette langue" + +[upload.status] +failed = "L'envoi nécessite votre attention" +paused = "Envoi en pause" +prepared = "Envoi préparé" +processing = "Traitement de l'envoi" +retrying = "L'envoi va être relancé" +sign_in = "Connectez-vous pour relancer l'envoi" +uploaded = "Envoyé" +uploading = "Envoi en cours" diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index 0ee072f4c59..182aef9d746 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -586,3 +586,382 @@ click_or_press = "यहाँ क्लिक करें, या दबाए get_started = "शुरू करें" space_key = "स्पेस" tagline = "सबके लिए ओपन सोर्स स्क्रीन शेयरिंग ऐप।" + +[auth] +sign_in_failed = "साइन इन विफल रहा" + +[diagnostics] +already_running = "एक डायग्नॉस्टिक पहले से चल रहा है।" +cancelled = "डायग्नॉस्टिक रद्द कर दिया गया।" +mode_both = "दोनों" + +[diagnostics.error] +create_folder = "डायग्नॉस्टिक फ़ोल्डर नहीं बन सका: {error}" +exited = "सेल्फ़-टेस्ट {status} के साथ बंद हुआ" +no_output = "सेल्फ़-टेस्ट से कोई आउटपुट स्ट्रीम नहीं मिली" +serialize = "रिपोर्ट सीरियलाइज़ नहीं हो सकी: {error}" +start_failed = "{program} शुरू नहीं हो सका: {error}" +upload_logs = "लॉग अपलोड नहीं हो सके: {error}" +upload_status = "अपलोड विफल, स्टेटस: {status}" +wait_failed = "सेल्फ़-टेस्ट के पूरा होने का इंतज़ार नहीं हो सका: {error}" +write_failed = "{path} नहीं लिखा जा सका: {error}" + +[diagnostics.stage] +analyzing = "रिकॉर्डिंग का विश्लेषण हो रहा है..." +analyzing_instant = "Instant रिकॉर्डिंग का विश्लेषण हो रहा है..." +analyzing_studio = "Studio रिकॉर्डिंग का विश्लेषण हो रहा है..." +cancelling = "रद्द हो रहा है..." +collecting = "सिस्टम जानकारी इकट्ठा हो रही है..." +done = "सिंक टेस्ट पूरा हो रहा है..." +exporting = "रिकॉर्डिंग एक्सपोर्ट हो रही है..." +exporting_instant = "Instant रिकॉर्डिंग एक्सपोर्ट हो रही है..." +exporting_studio = "Studio रिकॉर्डिंग एक्सपोर्ट हो रही है..." +finalizing = "रिकॉर्डिंग पूरी की जा रही है..." +finalizing_instant = "Instant रिकॉर्डिंग पूरी की जा रही है..." +finalizing_studio = "Studio रिकॉर्डिंग पूरी की जा रही है..." +pattern = "टेस्ट पैटर्न चल रहा है..." +pattern_instant = "टेस्ट पैटर्न चल रहा है (Instant)..." +pattern_studio = "टेस्ट पैटर्न चल रहा है (Studio)..." +preparing = "टेस्ट की तैयारी हो रही है..." +preparing_instant = "Instant टेस्ट की तैयारी हो रही है..." +preparing_studio = "Studio टेस्ट की तैयारी हो रही है..." +recording = "टेस्ट रिकॉर्ड हो रहा है..." +recording_instant = "Instant टेस्ट रिकॉर्ड हो रहा है..." +recording_studio = "Studio टेस्ट रिकॉर्ड हो रहा है..." +starting = "डायग्नॉस्टिक शुरू हो रहा है..." + +[dialog] +no_microphone_body = "इस रिकॉर्डिंग में आपकी आवाज़ शामिल नहीं होगी।" +open_recording_failed = "यह रिकॉर्डिंग नहीं खुल सकी: {error}" + +[dialog.capture] +approve_shortcut = "रिकॉर्डिंग शॉर्टकट को मंज़ूरी दें, या Cap Stop ट्रे आइकन दिखने पर उसे सक्रिय करें। रिकॉर्डिंग अभी शुरू नहीं हुई है।" +area_unavailable = "चुना गया क्षेत्र बदल गया है या अब उपलब्ध नहीं है। इसे फिर से चुनें।" +camera_needs_x11 = "Linux पर Instant कैमरा ओवरले के लिए X11 डेस्कटॉप चाहिए" +camera_notice = "Cap अपना कैमरा प्रीव्यू और रिकॉर्डिंग कंट्रोल छिपा देगा। चुना गया कैमरा एक अलग, एडिट करने लायक ट्रैक के रूप में रिकॉर्ड होता रहेगा।" +camera_notice_instant = "Cap अपना प्रीव्यू और कंट्रोल छिपा देगा। चुना गया कैमरा चुने गए आकार और इफ़ेक्ट के साथ अपने प्रीव्यू की जगह पर दिखेगा। पूरा प्रीव्यू कैप्चर क्षेत्र के अंदर रखें। Cap खोलने पर कंट्रोल दिखाने से पहले यह Instant रिकॉर्डिंग रुक जाएगी।" +camera_outside_area = "Instant रिकॉर्डिंग शुरू करने से पहले पूरा कैमरा प्रीव्यू कैप्चर क्षेत्र के अंदर ले जाएँ" +camera_preview_closed = "Instant रिकॉर्डिंग से पहले चुने गए कैमरा का प्रीव्यू खोलें" +cleanup_incomplete = "रिकॉर्डिंग की सफ़ाई अधूरी है: {error}। नई रिकॉर्डिंग रोकी गई है।" +devices_changed = "चुने गए डिवाइस बदल गए। नए रिकॉर्डिंग इनपुट की पुष्टि के लिए फिर से शुरू करें।" +devices_changed_before_start = "रिकॉर्डिंग शुरू होने से पहले चुने गए डिवाइस बदल गए। इनपुट की पुष्टि के लिए फिर से शुरू करें।" +shortcut_conflict = "Ctrl+Shift+F9 किसी दूसरे Cap एक्शन को दिया गया है। रिकॉर्ड करने से पहले वह शॉर्टकट बदलें।" +shortcut_description = "यह Cap रिकॉर्डिंग शुरू या बंद करें" +shortcut_hint = "शुरू करने के लिए {shortcut} दबाकर छोड़ें। रोकने के लिए वही शॉर्टकट इस्तेमाल करें। यह अस्थायी शॉर्टकट आपकी सेटिंग्स नहीं बदलता।" +tray_stop_hint = "शुरू करने के लिए Cap Stop ट्रे आइकन सक्रिय करें। रोकने के लिए वही आइकन फिर से सक्रिय करें। जब तक आप उस कंट्रोल का इस्तेमाल नहीं करते, रिकॉर्डिंग शुरू नहीं होगी।" +window_unavailable = "चुनी गई विंडो अब उपलब्ध नहीं है। इसे फिर से चुनें।" + +[dialog.clipboard] +copy_file = "फ़ाइल क्लिपबोर्ड पर कॉपी नहीं हो सकी: {error}" +copy_file_failed = "फ़ाइल क्लिपबोर्ड पर कॉपी नहीं हो सकी" +copy_image = "इमेज क्लिपबोर्ड पर कॉपी नहीं हो सकी: {error}" +copy_image_failed = "इमेज क्लिपबोर्ड पर कॉपी नहीं हो सकी" +not_a_file = "क्लिपबोर्ड पर सिर्फ़ फ़ाइलें कॉपी की जा सकती हैं" +unavailable = "क्लिपबोर्ड उपलब्ध नहीं है" +unavailable_error = "क्लिपबोर्ड उपलब्ध नहीं है: {error}" + +[dialog.delete_recording] +already_deleting = "यह रिकॉर्डिंग पहले से डिलीट हो रही है।" +close_failed = "एडिटर बंद नहीं हो सका: {error}" +editor_changed = "डिलीट की पुष्टि के दौरान एडिटर बदल गया। रिकॉर्डिंग डिलीट नहीं हुई।" +editor_closed = "एडिटर पहले ही बंद हो चुका है। रिकॉर्डिंग डिलीट नहीं हुई।" +failed = "रिकॉर्डिंग डिलीट नहीं हो सकी: {error}" +failed_title = "रिकॉर्डिंग डिलीट नहीं हो सकी" +finishing_body = "यह डिलीट पूरा करने से पहले Cap रिकॉर्डिंग की सफ़ाई पूरी होने का इंतज़ार कर रहा है।" +finishing_title = "डिलीट अभी पूरा हो रहा है" +in_use = "यह रिकॉर्डिंग अभी इस्तेमाल में है। इसका अपलोड पूरा या रद्द करें और फिर कोशिश करें: {error}" +outside_library = "यह रिकॉर्डिंग रिकॉर्डिंग लाइब्रेरी से बाहर है। इसकी फ़ाइलें नहीं बदली गईं।" +recording_active = "यह प्रोजेक्ट डिलीट करने से पहले रिकॉर्डिंग पूरी या रद्द करें।" +retained_title = "रिकॉर्डिंग रखी गई" + +[dialog.file] +audio_filter = "ऑडियो" +busy = "एक फ़ाइल डायलॉग पहले से खुला है। दोबारा कोशिश करने से पहले उसे पूरा या रद्द करें।" +export_filter = "एक्सपोर्ट" +failed = "फ़ाइल डायलॉग पूरा नहीं हो सका: {error}" +images_filter = "इमेज" +unavailable_title = "फ़ाइल डायलॉग उपलब्ध नहीं है" +unconfirmed = "Cap पुष्टि नहीं कर सका कि फ़ाइल डायलॉग बंद हुआ। बचे हुए फ़ाइल डायलॉग बंद करें और दोबारा कोशिश करने से पहले Cap रीस्टार्ट करें।" + +[dialog.mode_select] +instant = "लिंक से तुरंत शेयर करें। रिकॉर्ड करते समय ही अपलोड होता है।" +screenshot = "तुरंत स्क्रीनशॉट लें और उन पर एनोटेशन जोड़ें।" +studio = "बाद में एडिट करने के लिए सबसे अच्छी क्वालिटी में लोकल रिकॉर्डिंग।" +subtitle = "चुनें कि आप अपनी स्क्रीन कैसे कैप्चर करना चाहते हैं" +title = "रिकॉर्डिंग मोड चुनें" + +[dialog.storage] +stopped_kept = "स्टोरेज कम होने की वजह से रिकॉर्डिंग रुक गई। आपकी रिकॉर्डिंग फ़ाइलें रखी गई हैं। जगह खाली करें, फिर नीचे रिकॉर्डिंग रिकवर करें।" +stopped_saved = "स्टोरेज कम होने की वजह से रिकॉर्डिंग रुक गई। आपकी रिकॉर्डिंग सहेज ली गई है।" +stopping = "स्टोरेज कम है। आपकी रिकॉर्डिंग रोककर सहेजी जा रही है…" + +[dialog.teleprompter] +cue_markers = "क्यू मार्कर" +hidden = "यह विंडो Cap रिकॉर्डिंग में नहीं दिखती" +mirror = "टेक्स्ट मिरर करें" +paused = "रिकॉर्डिंग के साथ स्क्रॉलिंग रुकी है" +placeholder = "अपनी स्क्रिप्ट पेस्ट करें या लिखें…" + +[import] +error_title = "इंपोर्ट त्रुटि" +image_failed = "इमेज इंपोर्ट नहीं हो सकी: {error}" +unsupported = "यह मीडिया फ़ाइल समर्थित नहीं है।" +video_failed = "वीडियो इंपोर्ट नहीं हो सका: {error}" + +[import.filter] +image = "इमेज फ़ाइलें" +media = "मीडिया फ़ाइलें" +video = "वीडियो फ़ाइलें" + +[import.progress] +analyzing = "वीडियो फ़ाइल का विश्लेषण हो रहा है..." +complete = "इंपोर्ट पूरा हुआ!" +converting = "वीडियो कन्वर्ट हो रहा है... {percent}%" +image = "इमेज इंपोर्ट हो रही है..." +metadata = "प्रोजेक्ट मेटाडेटा बन रहा है..." +starting = "कन्वर्ज़न शुरू हो रहा है..." + +[overlay] +choose_mode = "रिकॉर्डिंग मोड चुनें" +countdown_tooltip = "रिकॉर्डिंग काउंटडाउन" +display_fallback = "डिस्प्ले {id}" + +[overlay.area] +draw = "एक क्षेत्र बनाएँ" +free_ratio = "फ़्री" +hint = "क्षेत्र चुनने के लिए क्लिक करके खींचें" +minimum_size = "न्यूनतम साइज़ {width} x {height} है" +too_small = "{width} x {height} बहुत छोटा है" + +[overlay.camera_only] +description = "सिर्फ़ अपने कैमरा और माइक्रोफ़ोन से रिकॉर्ड करें" +select_camera = "कृपया एक कैमरा चुनें" + +[overlay.device] +camera_allow = "कैमरा एक्सेस की अनुमति दें…" +camera_turn_on = "सेटिंग्स में कैमरा एक्सेस चालू करें…" +microphone_allow = "माइक्रोफ़ोन एक्सेस की अनुमति दें…" +microphone_turn_on = "सेटिंग्स में माइक्रोफ़ोन एक्सेस चालू करें…" + +[overlay.start] +instant = "Instant मोड: शेयर करने लायक लिंक रिकॉर्ड करें" +instant_sign_in = "Instant मोड: तुरंत रिकॉर्ड करने के लिए साइन इन करें" +sign_in = "इस्तेमाल के लिए साइन इन करें" +start_recording = "रिकॉर्डिंग शुरू करें" +studio = "Studio मोड: लोकल रूप से रिकॉर्ड और एडिट करें" +take_screenshot = "स्क्रीनशॉट लें" + +[permission.guide] +accessibility_body = "इसे ऊपर दी गई ऐक्सेसिबिलिटी लिस्ट में छोड़ें, फिर पक्का करें कि इसका स्विच चालू है।" +screen_body = "इसे ऊपर दी गई स्क्रीन और सिस्टम ऑडियो रिकॉर्डिंग लिस्ट में छोड़ें, फिर पक्का करें कि इसका स्विच चालू है।" +title = "Cap को लिस्ट में खींचें" + +[permission.name] +accessibility = "ऐक्सेसिबिलिटी" +camera = "कैमरा" +microphone = "माइक्रोफ़ोन" +screen_recording = "स्क्रीन रिकॉर्डिंग" + +[permission.onboarding] +open_settings_again = "सिस्टम सेटिंग्स फिर से खोलें" +skip = "अभी के लिए छोड़ें" +switch_on_title = "सिस्टम सेटिंग्स में Cap चालू करें" + +[permission.onboarding.accessibility] +allow = "ऐक्सेसिबिलिटी की अनुमति दें" +body = "ऐक्सेसिबिलिटी से Cap सिर्फ़ इसी Mac पर आपके क्लिक और कर्सर देख पाता है, ताकि ऑटो ज़ूम को पता रहे कि कहाँ देखना है।" +done_body = "ऑटो ज़ूम अब आपके क्लिक फ़ॉलो कर सकता है।" +done_title = "ऐक्सेसिबिलिटी चालू है" +title = "Cap को अपना कर्सर फ़ॉलो करने दें" + +[permission.onboarding.all_set] +body = "Cap के पास ज़रूरत की हर चीज़ है। अब एक झलक देखें कि यह क्या-क्या कर सकता है।" +regained = "धन्यवाद। Cap फिर से रिकॉर्ड कर सकता है।" +title = "सब तैयार है" + +[permission.onboarding.camera] +allow = "कैमरा की अनुमति दें" +body = "अपनी रिकॉर्डिंग के एक कोने में खुद को दिखाएँ। ज़रूरत न हो तो इसे छोड़ दें।" +done_body = "आप किसी भी रिकॉर्डिंग में अपना कैमरा जोड़ सकते हैं।" +done_title = "कैमरा चालू है" +title = "Cap को आपको देखने दें" + +[permission.onboarding.checklist] +lede = "macOS पहले कुछ चीज़ों की अनुमति माँगता है। इसमें बस एक मिनट लगता है।" +regained_lede = "सिस्टम सेटिंग्स में कुछ बंद कर दिया गया था। रिकॉर्डिंग जारी रखने के लिए इसे फिर से चालू करें।" +regained_title = "Cap को एक अनुमति वापस चाहिए" +title = "चलिए Cap को तैयार करें" + +[permission.onboarding.hint] +drag_into_list = "सिस्टम सेटिंग्स के नीचे वाले पैनल से Cap को लिस्ट में खींचें। पहले से लिस्ट में है? बस उसे चालू कर दें।" +find_in_list = "लिस्ट में Cap ढूँढें और उसे चालू करें। अगर macOS ने पहले पूछा था, तो ‘सिस्टम सेटिंग्स खोलें’ चुनें।" +no_prompt = "कोई प्रॉम्प्ट नहीं दिखा? सिस्टम सेटिंग्स में Cap चालू करें।" +open_settings = "सिस्टम सेटिंग्स खोलें, लिस्ट में Cap ढूँढें और उसे चालू करें।" +turned_off = "इसे पहले बंद कर दिया गया था, इसलिए सिस्टम सेटिंग्स में Cap चालू करें। या अभी के लिए इसे छोड़ दें।" + +[permission.onboarding.microphone] +allow = "माइक्रोफ़ोन की अनुमति दें" +body = "अपनी रिकॉर्डिंग में अपनी आवाज़ जोड़ें। आप इसे अभी छोड़कर बाद में चालू कर सकते हैं।" +done_body = "अब आपकी आवाज़ आपकी रिकॉर्डिंग में आ सकती है।" +done_title = "माइक्रोफ़ोन चालू है" +title = "Cap को आपको सुनने दें" + +[permission.onboarding.relaunch] +body = "Cap के रीस्टार्ट होते ही macOS स्क्रीन रिकॉर्डिंग चालू कर देता है। आप यहीं से आगे बढ़ेंगे।" +button = "रीलॉन्च" +hint = "चालू कर दिया पर अब भी इंतज़ार? लागू करने के लिए Cap रीस्टार्ट करें।" +later = "macOS इसे Cap के रीस्टार्ट होने के बाद लागू करता है। यहाँ का काम पूरा होने पर आप ऐसा कर सकते हैं।" +title = "बस एक रीस्टार्ट और सब तैयार" + +[permission.onboarding.screen] +allow = "स्क्रीन रिकॉर्डिंग की अनुमति दें" +body = "स्क्रीन रिकॉर्डिंग से Cap आपके डिस्प्ले, विंडो और सिस्टम ऑडियो कैप्चर कर पाता है।" +done_body = "Cap अब आपकी स्क्रीन कैप्चर कर सकता है।" +done_title = "स्क्रीन रिकॉर्डिंग चालू है" +title = "Cap को अपनी स्क्रीन देखने दें" + +[permission.onboarding.status] +allowed = "अनुमति है" +off = "सिस्टम सेटिंग्स में बंद" +optional = "वैकल्पिक" +required = "ज़रूरी" +skipped = "अभी के लिए छोड़ा गया" + +[permission.onboarding.waiting] +prompt = "macOS प्रॉम्प्ट में ‘अनुमति दें’ चुनें" +system_settings = "सिस्टम सेटिंग्स का इंतज़ार है" + +[screenshot_editor] +loading_preview = "प्रीव्यू लोड हो रहा है..." +zoom_in = "ज़ूम इन" +zoom_out = "ज़ूम आउट" + +[screenshot_editor.annotation] +default_text = "टेक्स्ट" +done = "हो गया" +fill = "फ़िल" +intensity = "तीव्रता" +mask_style = "स्टाइल" +pixelate = "पिक्सेलेट" +stroke = "स्ट्रोक" +transparent = "पारदर्शी" + +[screenshot_editor.aspect] +auto = "ऑटो" +classic = "क्लासिक ⋅4:3" +square = "स्क्वेयर ⋅1:1" +tall = "लंबा ⋅3:4" +vertical = "वर्टिकल ⋅9:16" +wide = "वाइड ⋅16:9" + +[screenshot_editor.background] +animated_gradient = "स्क्रीनशॉट में एनिमेटेड ग्रेडिएंट एक स्थिर इमेज के रूप में दिखता है। इसे बदलने के लिए कोई दूसरा बैकग्राउंड चुनें।" +blur_title = "बैकग्राउंड ब्लर" +choose_image = "चुनने के लिए क्लिक करें या इमेज खींचकर छोड़ें" +color = "रंग" +gradient = "ग्रेडिएंट" +image = "इमेज" +image_files = "इमेज" +image_title = "बैकग्राउंड इमेज" +wallpaper = "वॉलपेपर" + +[screenshot_editor.corner] +rounded = "गोल" +squircle = "स्क्वर्कल" +style = "कोने की स्टाइल" + +[screenshot_editor.crop] +full = "पूरा" +loading_frame = "फ़्रेम लोड हो रहा है…" +position = "पोज़िशन" + +[screenshot_editor.error] +encode = "स्क्रीनशॉट एक्सपोर्ट एनकोड नहीं हो सका: {error}" +load_meta = "स्क्रीनशॉट मेटाडेटा लोड नहीं हो सका: {error}" +load_metadata = "स्क्रीनशॉट मेटाडेटा लोड नहीं हो सका: {error}" +no_image = "{path} में कोई PNG नहीं मिला" +not_screenshot = "यह स्क्रीनशॉट बंडल नहीं है" +open_image = "इमेज नहीं खुल सकी: {error}" +renderer_failed = "GPU रेंडरर शुरू नहीं हो सका: {error}" +renderer_not_running = "स्क्रीनशॉट रेंडरर नहीं चल रहा है" +renderer_stopped = "एक्सपोर्ट पूरा होने से पहले रेंडरर रुक गया" +save_project = "प्रोजेक्ट सहेजते समय त्रुटि: {error}" +serialize = "स्क्रीनशॉट कॉन्फ़िग सीरियलाइज़ नहीं हो सका: {error}" +sign_in_to_share = "अपने ब्राउज़र में Cap में साइन इन करें, फिर दोबारा शेयर करें" +too_large = "इमेज का आकार सीमा से ज़्यादा है: {width}x{height}" +upgrade_required = "इस फ़ीचर के लिए अपग्रेड किया गया प्लान चाहिए" +upload_task = "अपलोड टास्क विफल रहा: {error}" + +[screenshot_editor.header] +aspect_ratio = "आस्पेक्ट रेशियो" +copy = "क्लिपबोर्ड पर कॉपी करें" +crop_image = "इमेज क्रॉप करें" +more_actions = "और कार्रवाइयाँ" + +[screenshot_editor.layers] +empty = "अभी कोई लेयर नहीं" +empty_hint = "एनोटेशन जोड़ने के लिए ऊपर दिए टूल इस्तेमाल करें" +reorder_hint = "क्रम बदलने के लिए खींचें • ऊपर = सबसे आगे" +title = "लेयर" + +[screenshot_editor.menu] +delete = "डिलीट करें" +open_folder = "फ़ोल्डर खोलें" + +[screenshot_editor.share] +create_link = "शेयर करने लायक लिंक बनाएँ" +preparing = "अपलोड की तैयारी हो रही है" +rendering = "स्क्रीनशॉट रेंडर हो रहा है" +uploading = "स्क्रीनशॉट अपलोड हो रहा है" + +[screenshot_editor.style] +advanced_shadow = "एडवांस्ड शैडो सेटिंग्स" +background = "बैकग्राउंड" +blur = "ब्लर" +border = "बॉर्डर" +color = "रंग" +corner_rounding = "कोनों की गोलाई" +opacity = "अपारदर्शिता" +padding = "पैडिंग" +rounding = "गोलाई" +shadow = "शैडो" +size_label = "साइज़" +width = "चौड़ाई" + +[screenshot_editor.toast] +copied = "स्क्रीनशॉट क्लिपबोर्ड पर कॉपी हो गया!" +link_copied = "शेयर लिंक क्लिपबोर्ड पर कॉपी हो गया" +saved = "स्क्रीनशॉट सहेज लिया गया!" + +[screenshot_editor.tool] +arrow = "ऐरो" +circle = "सर्कल" +draw = "ड्रॉ" +mask = "मास्क" +rectangle = "रेक्टेंगल" +select = "चुनें" +text_label = "टेक्स्ट" + +[transcription.download] +complete = "डाउनलोड पूरा हुआ" +failed = "डाउनलोड विफल: {error}" +file_progress = "{file} डाउनलोड हो रहा है: {percent}%" +preparing = "मॉडल डाउनलोड की तैयारी हो रही है" +progress = "मॉडल डाउनलोड हो रहा है: {percent}%" + +[transcription.error] +intel_mac = "Parakeet मॉडल Intel Mac पर उपलब्ध नहीं हैं। इसके बजाय Whisper मॉडल इस्तेमाल करें" +model_load = "कैप्शन मॉडल लोड नहीं हो सका। इसे फिर से डाउनलोड करके देखें" +model_missing = "कैप्शन मॉडल नहीं मिला। कृपया पहले इसे डाउनलोड करें" +no_audio = "वीडियो फ़ाइल में कोई ऑडियो नहीं मिला" +unsupported_language = "सुझाया गया और हाई एक्यूरेसी मॉडल सिर्फ़ अंग्रेज़ी और 24 दूसरी यूरोपीय भाषाएँ ट्रांसक्राइब करते हैं। इस भाषा के कैप्शन के लिए Small या Medium मॉडल डाउनलोड करें" + +[upload.status] +failed = "अपलोड पर ध्यान देने की ज़रूरत है" +paused = "अपलोड रुका हुआ है" +prepared = "अपलोड तैयार है" +processing = "अपलोड प्रोसेस हो रहा है" +retrying = "अपलोड फिर से कोशिश करेगा" +sign_in = "अपलोड दोबारा करने के लिए साइन इन करें" +uploaded = "अपलोड हो गया" +uploading = "अपलोड हो रहा है" diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 44dd3f99308..27e9da0c380 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -586,3 +586,382 @@ click_or_press = "ここをクリックするか、次のキーを押す:" get_started = "はじめる" space_key = "スペース" tagline = "誰もが使えるオープンソースの画面共有アプリ。" + +[auth] +sign_in_failed = "サインインに失敗しました" + +[diagnostics] +already_running = "診断はすでに実行中です。" +cancelled = "診断はキャンセルされました。" +mode_both = "両方" + +[diagnostics.error] +create_folder = "診断フォルダを作成できませんでした:{error}" +exited = "セルフテストが終了しました:{status}" +no_output = "セルフテストから出力が得られませんでした" +serialize = "レポートをシリアライズできませんでした:{error}" +start_failed = "{program} を起動できませんでした:{error}" +upload_logs = "ログをアップロードできませんでした:{error}" +upload_status = "アップロードに失敗しました。ステータス:{status}" +wait_failed = "セルフテストの終了を待てませんでした:{error}" +write_failed = "{path} に書き込めませんでした:{error}" + +[diagnostics.stage] +analyzing = "録画を分析中..." +analyzing_instant = "Instant録画を分析中..." +analyzing_studio = "Studio録画を分析中..." +cancelling = "キャンセル中..." +collecting = "システム情報を収集中..." +done = "同期テストを仕上げ中..." +exporting = "録画を書き出し中..." +exporting_instant = "Instant録画を書き出し中..." +exporting_studio = "Studio録画を書き出し中..." +finalizing = "録画を仕上げ中..." +finalizing_instant = "Instant録画を仕上げ中..." +finalizing_studio = "Studio録画を仕上げ中..." +pattern = "テストパターンを再生中..." +pattern_instant = "テストパターンを再生中(Instant)..." +pattern_studio = "テストパターンを再生中(Studio)..." +preparing = "テストを準備中..." +preparing_instant = "Instantテストを準備中..." +preparing_studio = "Studioテストを準備中..." +recording = "テストを録画中..." +recording_instant = "Instantテストを録画中..." +recording_studio = "Studioテストを録画中..." +starting = "診断を開始中..." + +[dialog] +no_microphone_body = "この録画にはあなたの声は入りません。" +open_recording_failed = "この録画を開けませんでした:{error}" + +[dialog.capture] +approve_shortcut = "録画用のショートカットを承認するか、Cap の停止トレイアイコンが表示されたらそれをクリックしてください。録画はまだ始まっていません。" +area_unavailable = "選択した範囲が変更されたか、使用できなくなりました。もう一度選択してください。" +camera_needs_x11 = "Linux で Instant のカメラオーバーレイを使うには X11 デスクトップが必要です" +camera_notice = "Cap はカメラのプレビューと録画コントロールを非表示にします。選択したカメラは、個別に編集できるトラックとして録画され続けます。" +camera_notice_instant = "Cap はプレビューとコントロールを非表示にします。選択したカメラは、選んだ形とエフェクトでプレビューの位置に表示されます。プレビュー全体をキャプチャ範囲内に収めてください。Cap を開くと、コントロールを表示する前にこの Instant 録画が停止します。" +camera_outside_area = "Instant 録画を始める前に、カメラのプレビュー全体をキャプチャ範囲内に移動してください" +camera_preview_closed = "Instant 録画の前に、選択したカメラのプレビューを開いてください" +cleanup_incomplete = "録画のクリーンアップが完了していません:{error}。新しい録画は開始できません。" +devices_changed = "選択したデバイスが変更されました。新しい録画の入力を確認するため、もう一度開始してください。" +devices_changed_before_start = "録画の開始前に選択したデバイスが変更されました。入力を確認するため、もう一度開始してください。" +shortcut_conflict = "Ctrl+Shift+F9 は Cap の別の操作に割り当てられています。録画の前にそのショートカットを変更してください。" +shortcut_description = "この Cap 録画を開始または停止" +shortcut_hint = "{shortcut} を押して離すと開始します。同じショートカットで停止できます。この一時的なショートカットは設定を変更しません。" +tray_stop_hint = "Cap の停止トレイアイコンをクリックすると開始します。同じアイコンをもう一度クリックすると停止します。このコントロールを使うまで録画は始まりません。" +window_unavailable = "選択したウィンドウは使用できなくなりました。もう一度選択してください。" + +[dialog.clipboard] +copy_file = "ファイルをクリップボードにコピーできませんでした:{error}" +copy_file_failed = "ファイルをクリップボードにコピーできませんでした" +copy_image = "画像をクリップボードにコピーできませんでした:{error}" +copy_image_failed = "画像をクリップボードにコピーできませんでした" +not_a_file = "クリップボードにコピーできるのはファイルだけです" +unavailable = "クリップボードを使用できません" +unavailable_error = "クリップボードを使用できません:{error}" + +[dialog.delete_recording] +already_deleting = "この録画はすでに削除中です。" +close_failed = "エディタを閉じられませんでした:{error}" +editor_changed = "削除の確認中にエディタが変更されました。録画は削除されていません。" +editor_closed = "エディタはすでに閉じられています。録画は削除されていません。" +failed = "録画を削除できませんでした:{error}" +failed_title = "録画を削除できませんでした" +finishing_body = "Cap は録画のクリーンアップが終わるのを待ってから、この削除を完了します。" +finishing_title = "削除はまだ完了していません" +in_use = "この録画はまだ使用中です。アップロードを完了またはキャンセルしてから、もう一度お試しください:{error}" +outside_library = "この録画は録画ライブラリの外にあります。ファイルは変更されていません。" +recording_active = "このプロジェクトを削除する前に、録画を終了またはキャンセルしてください。" +retained_title = "録画は残されています" + +[dialog.file] +audio_filter = "オーディオ" +busy = "ファイルダイアログがすでに開いています。完了するかキャンセルしてから、もう一度お試しください。" +export_filter = "書き出し" +failed = "ファイルダイアログを完了できませんでした:{error}" +images_filter = "画像" +unavailable_title = "ファイルダイアログを使用できません" +unconfirmed = "Cap はファイルダイアログが閉じたことを確認できませんでした。残っているファイルダイアログを閉じ、Cap を再起動してからもう一度お試しください。" + +[dialog.mode_select] +instant = "リンクですぐに共有。録画しながらアップロードします。" +screenshot = "スクリーンショットを撮ってすぐに注釈を付けられます。" +studio = "あとで編集するための最高画質のローカル録画です。" +subtitle = "画面のキャプチャ方法を選んでください" +title = "録画モードを選択" + +[dialog.storage] +stopped_kept = "ストレージが不足しているため録画を停止しました。録画ファイルは残されています。空き容量を確保してから、下で録画を復元してください。" +stopped_saved = "ストレージが不足しているため録画を停止しました。録画は保存されています。" +stopping = "ストレージが不足しています。録画を停止して保存しています…" + +[dialog.teleprompter] +cue_markers = "キューマーカー" +hidden = "このウィンドウは Cap の録画に映りません" +mirror = "テキストを反転" +paused = "録画とともにスクロールを一時停止中" +placeholder = "台本を貼り付けるか入力してください…" + +[import] +error_title = "読み込みエラー" +image_failed = "画像を読み込めませんでした:{error}" +unsupported = "サポートされていないメディアファイルです。" +video_failed = "動画を読み込めませんでした:{error}" + +[import.filter] +image = "画像ファイル" +media = "メディアファイル" +video = "動画ファイル" + +[import.progress] +analyzing = "動画ファイルを分析中..." +complete = "読み込みが完了しました!" +converting = "動画を変換中... {percent}%" +image = "画像を読み込み中..." +metadata = "プロジェクトのメタデータを作成中..." +starting = "変換を開始しています..." + +[overlay] +choose_mode = "録画モードを選択" +countdown_tooltip = "録画のカウントダウン" +display_fallback = "ディスプレイ {id}" + +[overlay.area] +draw = "範囲を描く" +free_ratio = "自由" +hint = "クリックしてドラッグし、範囲を選択" +minimum_size = "最小サイズは {width} x {height} です" +too_small = "{width} x {height} は小さすぎます" + +[overlay.camera_only] +description = "カメラとマイクだけで録画します" +select_camera = "カメラを選択してください" + +[overlay.device] +camera_allow = "カメラへのアクセスを許可…" +camera_turn_on = "設定でカメラへのアクセスをオンにする…" +microphone_allow = "マイクへのアクセスを許可…" +microphone_turn_on = "設定でマイクへのアクセスをオンにする…" + +[overlay.start] +instant = "Instantモード:共有リンクを録画" +instant_sign_in = "Instantモード:サインインしてすぐに録画" +sign_in = "サインインして使用" +start_recording = "録画を開始" +studio = "Studioモード:ローカルで録画して編集" +take_screenshot = "スクリーンショットを撮る" + +[permission.guide] +accessibility_body = "上の「アクセシビリティ」のリストにドロップし、スイッチがオンになっていることを確認してください。" +screen_body = "上の「画面収録とシステムオーディオ録音」のリストにドロップし、スイッチがオンになっていることを確認してください。" +title = "Cap をリストにドラッグ" + +[permission.name] +accessibility = "アクセシビリティ" +camera = "カメラ" +microphone = "マイク" +screen_recording = "画面収録" + +[permission.onboarding] +open_settings_again = "システム設定をもう一度開く" +skip = "今はスキップ" +switch_on_title = "システム設定で Cap をオンにする" + +[permission.onboarding.accessibility] +allow = "アクセシビリティを許可" +body = "アクセシビリティを許可すると、Cap がこの Mac 上だけでクリックとカーソルを把握し、自動ズームがどこを映すべきかわかるようになります。" +done_body = "自動ズームがクリックを追えるようになりました。" +done_title = "アクセシビリティはオンです" +title = "Cap がカーソルを追えるようにする" + +[permission.onboarding.all_set] +body = "Cap に必要なものはすべてそろいました。次は、できることをざっと見てみましょう。" +regained = "ありがとうございます。Cap で再び録画できます。" +title = "準備完了です" + +[permission.onboarding.camera] +allow = "カメラを許可" +body = "録画の隅に自分を映せます。必要なければスキップしてください。" +done_body = "どの録画にもカメラを追加できます。" +done_title = "カメラはオンです" +title = "Cap にあなたを映させる" + +[permission.onboarding.checklist] +lede = "最初に macOS がいくつかの許可を求めます。1分ほどで終わります。" +regained_lede = "システム設定でオフにされた項目があります。録画を続けるにはオンに戻してください。" +regained_title = "Cap に権限を戻してください" +title = "Cap の準備をしましょう" + +[permission.onboarding.hint] +drag_into_list = "システム設定の下にあるパネルから Cap をリストにドラッグしてください。すでにリストにある場合は、オンにするだけです。" +find_in_list = "リストで Cap を見つけてオンにしてください。macOS から先に確認された場合は、「システム設定を開く」を選んでください。" +no_prompt = "確認ダイアログが表示されませんか?システム設定で Cap をオンにしてください。" +open_settings = "システム設定を開き、リストで Cap を見つけてオンにしてください。" +turned_off = "以前オフにされているため、システム設定で Cap をオンにしてください。今はスキップすることもできます。" + +[permission.onboarding.microphone] +allow = "マイクを許可" +body = "録画に声を入れられます。スキップして後でオンにすることもできます。" +done_body = "録画に声を入れられるようになりました。" +done_title = "マイクはオンです" +title = "Cap に声を届ける" + +[permission.onboarding.relaunch] +body = "Cap を再起動すると macOS が画面収録をオンにします。この画面から続けられます。" +button = "再起動" +hint = "オンにしたのにまだ待機中ですか?Cap を再起動すると反映されます。" +later = "macOS は Cap の再起動後にこれを反映します。ここでの設定が終わってから再起動できます。" +title = "再起動すれば準備完了です" + +[permission.onboarding.screen] +allow = "画面収録を許可" +body = "画面収録を許可すると、Cap がディスプレイ、ウィンドウ、システムオーディオをキャプチャできます。" +done_body = "Cap で画面をキャプチャできるようになりました。" +done_title = "画面収録はオンです" +title = "Cap に画面を見せる" + +[permission.onboarding.status] +allowed = "許可済み" +off = "システム設定でオフ" +optional = "任意" +required = "必須" +skipped = "今はスキップ" + +[permission.onboarding.waiting] +prompt = "macOS の確認ダイアログで「許可」を選択" +system_settings = "システム設定を待っています" + +[screenshot_editor] +loading_preview = "プレビューを読み込み中..." +zoom_in = "拡大" +zoom_out = "縮小" + +[screenshot_editor.annotation] +default_text = "テキスト" +done = "完了" +fill = "塗りつぶし" +intensity = "強さ" +mask_style = "スタイル" +pixelate = "ピクセル化" +stroke = "線" +transparent = "透明" + +[screenshot_editor.aspect] +auto = "自動" +classic = "クラシック ⋅4:3" +square = "正方形 ⋅1:1" +tall = "縦長 ⋅3:4" +vertical = "縦 ⋅9:16" +wide = "ワイド ⋅16:9" + +[screenshot_editor.background] +animated_gradient = "スクリーンショットではアニメーショングラデーションは静止画として描画されます。置き換えるには別の背景を選んでください。" +blur_title = "背景のぼかし" +choose_image = "クリックして選択するか、画像をドラッグ&ドロップ" +color = "カラー" +gradient = "グラデーション" +image = "画像" +image_files = "画像" +image_title = "背景画像" +wallpaper = "壁紙" + +[screenshot_editor.corner] +rounded = "角丸" +squircle = "スクワークル" +style = "角のスタイル" + +[screenshot_editor.crop] +full = "全体" +loading_frame = "フレームを読み込み中…" +position = "位置" + +[screenshot_editor.error] +encode = "スクリーンショットの書き出しをエンコードできませんでした:{error}" +load_meta = "スクリーンショットのメタデータを読み込めませんでした:{error}" +load_metadata = "スクリーンショットのメタデータを読み込めませんでした:{error}" +no_image = "{path} に PNG が見つかりません" +not_screenshot = "スクリーンショットのバンドルではありません" +open_image = "画像を開けませんでした:{error}" +renderer_failed = "GPU レンダラーを起動できませんでした:{error}" +renderer_not_running = "スクリーンショットのレンダラーが動作していません" +renderer_stopped = "書き出しが終わる前にレンダラーが停止しました" +save_project = "プロジェクトの保存中にエラーが発生しました:{error}" +serialize = "スクリーンショットの設定をシリアライズできませんでした:{error}" +sign_in_to_share = "ブラウザで Cap にサインインしてから、もう一度共有してください" +too_large = "画像サイズが上限を超えています:{width}x{height}" +upgrade_required = "この機能を使うにはプランのアップグレードが必要です" +upload_task = "アップロード処理に失敗しました:{error}" + +[screenshot_editor.header] +aspect_ratio = "アスペクト比" +copy = "クリップボードにコピー" +crop_image = "画像を切り抜く" +more_actions = "その他の操作" + +[screenshot_editor.layers] +empty = "レイヤーはまだありません" +empty_hint = "上のツールで注釈を追加できます" +reorder_hint = "ドラッグで並べ替え • 上 = 最前面" +title = "レイヤー" + +[screenshot_editor.menu] +delete = "削除" +open_folder = "フォルダを開く" + +[screenshot_editor.share] +create_link = "共有リンクを作成" +preparing = "アップロードを準備中" +rendering = "スクリーンショットを描画中" +uploading = "スクリーンショットをアップロード中" + +[screenshot_editor.style] +advanced_shadow = "シャドウの詳細設定" +background = "背景" +blur = "ぼかし" +border = "枠線" +color = "色" +corner_rounding = "角の丸み" +opacity = "不透明度" +padding = "余白" +rounding = "角の丸み" +shadow = "シャドウ" +size_label = "サイズ" +width = "幅" + +[screenshot_editor.toast] +copied = "スクリーンショットをクリップボードにコピーしました!" +link_copied = "共有リンクをクリップボードにコピーしました" +saved = "スクリーンショットを保存しました!" + +[screenshot_editor.tool] +arrow = "矢印" +circle = "円" +draw = "描画" +mask = "マスク" +rectangle = "四角形" +select = "選択" +text_label = "テキスト" + +[transcription.download] +complete = "ダウンロード完了" +failed = "ダウンロードに失敗しました:{error}" +file_progress = "{file} をダウンロード中:{percent}%" +preparing = "モデルのダウンロードを準備中" +progress = "モデルをダウンロード中:{percent}%" + +[transcription.error] +intel_mac = "Parakeet モデルは Intel Mac では使えません。代わりに Whisper モデルを使ってください" +model_load = "字幕モデルを読み込めませんでした。もう一度ダウンロードしてください" +model_missing = "字幕モデルが見つかりません。先にダウンロードしてください" +no_audio = "動画ファイルに音声がありません" +unsupported_language = "「推奨」モデルと「高精度」モデルは英語とその他24のヨーロッパ言語のみ文字起こしできます。この言語の字幕を作るには「小」または「中」モデルをダウンロードしてください" + +[upload.status] +failed = "アップロードに対応が必要です" +paused = "アップロードを一時停止中" +prepared = "アップロードの準備完了" +processing = "アップロードを処理中" +retrying = "アップロードを再試行します" +sign_in = "サインインしてアップロードを再試行" +uploaded = "アップロード済み" +uploading = "アップロード中" diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index faaf888950e..29f3695825a 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -586,3 +586,382 @@ click_or_press = "点击此处,或按下" get_started = "开始使用" space_key = "空格" tagline = "人人可用的开源屏幕分享应用。" + +[auth] +sign_in_failed = "登录失败" + +[diagnostics] +already_running = "已有一项诊断正在运行。" +cancelled = "诊断已取消。" +mode_both = "两者" + +[diagnostics.error] +create_folder = "无法创建诊断文件夹:{error}" +exited = "自检已退出,状态:{status}" +no_output = "自检没有产生输出流" +serialize = "无法序列化报告:{error}" +start_failed = "无法启动 {program}:{error}" +upload_logs = "无法上传日志:{error}" +upload_status = "上传失败,状态:{status}" +wait_failed = "无法等待自检结束:{error}" +write_failed = "无法写入 {path}:{error}" + +[diagnostics.stage] +analyzing = "正在分析录制..." +analyzing_instant = "正在分析 Instant 录制..." +analyzing_studio = "正在分析 Studio 录制..." +cancelling = "正在取消..." +collecting = "正在收集系统信息..." +done = "正在完成同步测试..." +exporting = "正在导出录制..." +exporting_instant = "正在导出 Instant 录制..." +exporting_studio = "正在导出 Studio 录制..." +finalizing = "正在完成录制..." +finalizing_instant = "正在完成 Instant 录制..." +finalizing_studio = "正在完成 Studio 录制..." +pattern = "正在播放测试图案..." +pattern_instant = "正在播放测试图案(Instant)..." +pattern_studio = "正在播放测试图案(Studio)..." +preparing = "正在准备测试..." +preparing_instant = "正在准备 Instant 测试..." +preparing_studio = "正在准备 Studio 测试..." +recording = "正在录制测试..." +recording_instant = "正在录制 Instant 测试..." +recording_studio = "正在录制 Studio 测试..." +starting = "正在启动诊断..." + +[dialog] +no_microphone_body = "此录制将不包含你的声音。" +open_recording_failed = "无法打开此录制:{error}" + +[dialog.capture] +approve_shortcut = "请批准一个录制快捷键,或在 Cap 停止托盘图标出现时点按它。录制尚未开始。" +area_unavailable = "所选区域已变化或不再可用。请重新选择。" +camera_needs_x11 = "在 Linux 上,Instant 摄像头叠加层需要 X11 桌面" +camera_notice = "Cap 将隐藏摄像头预览和录制控件。所选摄像头会继续作为单独的可编辑轨道录制。" +camera_notice_instant = "Cap 将隐藏预览和控件。所选摄像头会以所选的形状和效果显示在其预览位置。请让整个预览保持在捕获区域内。打开 Cap 会先停止此 Instant 录制,然后再显示控件。" +camera_outside_area = "开始 Instant 录制前,请将整个摄像头预览移到捕获区域内" +camera_preview_closed = "Instant 录制前,请先打开所选摄像头的预览" +cleanup_incomplete = "录制清理未完成:{error}。无法开始新的录制。" +devices_changed = "所选设备已更改。请重新开始以确认新的录制输入。" +devices_changed_before_start = "录制开始前所选设备已更改。请重新开始以确认输入。" +shortcut_conflict = "Ctrl+Shift+F9 已分配给另一个 Cap 操作。请在录制前更改该快捷键。" +shortcut_description = "开始或停止此 Cap 录制" +shortcut_hint = "按下并松开 {shortcut} 开始录制。再次使用同一快捷键即可停止。此临时快捷键不会更改你的设置。" +tray_stop_hint = "点按 Cap 停止托盘图标即可开始。再次点按同一图标即可停止。在你使用该控件之前,录制不会开始。" +window_unavailable = "所选窗口已不可用。请重新选择。" + +[dialog.clipboard] +copy_file = "无法将文件复制到剪贴板:{error}" +copy_file_failed = "无法将文件复制到剪贴板" +copy_image = "无法将图像复制到剪贴板:{error}" +copy_image_failed = "无法将图像复制到剪贴板" +not_a_file = "只能将文件复制到剪贴板" +unavailable = "剪贴板不可用" +unavailable_error = "剪贴板不可用:{error}" + +[dialog.delete_recording] +already_deleting = "此录制正在删除中。" +close_failed = "无法关闭编辑器:{error}" +editor_changed = "确认删除期间编辑器发生了变化。录制未被删除。" +editor_closed = "编辑器已关闭。录制未被删除。" +failed = "删除录制失败:{error}" +failed_title = "无法删除录制" +finishing_body = "Cap 正在等待录制清理完成,然后再完成此次删除。" +finishing_title = "删除仍在进行中" +in_use = "此录制仍在使用中。请完成或取消其上传后重试:{error}" +outside_library = "此录制不在录制库中。其文件未被更改。" +recording_active = "请先完成或取消录制,再删除此项目。" +retained_title = "录制已保留" + +[dialog.file] +audio_filter = "音频" +busy = "已有一个文件对话框处于打开状态。请先完成或取消它,然后再试。" +export_filter = "导出" +failed = "文件对话框无法完成:{error}" +images_filter = "图像" +unavailable_title = "文件对话框不可用" +unconfirmed = "Cap 无法确认文件对话框已关闭。请关闭所有剩余的文件对话框并重启 Cap,然后再试。" + +[dialog.mode_select] +instant = "通过链接即时分享。边录边上传。" +screenshot = "即时截图并添加标注。" +studio = "最高画质的本地录制,便于稍后编辑。" +subtitle = "选择你想如何捕获屏幕" +title = "选择录制模式" + +[dialog.storage] +stopped_kept = "存储空间不足,录制已停止。录制文件已保留。请释放空间,然后在下方恢复录制。" +stopped_saved = "存储空间不足,录制已停止。你的录制已保存。" +stopping = "存储空间不足。正在停止并保存你的录制…" + +[dialog.teleprompter] +cue_markers = "提示标记" +hidden = "此窗口不会出现在 Cap 录制中" +mirror = "镜像文字" +paused = "滚动已随录制暂停" +placeholder = "粘贴或输入你的脚本…" + +[import] +error_title = "导入错误" +image_failed = "无法导入图像:{error}" +unsupported = "不支持的媒体文件。" +video_failed = "无法导入视频:{error}" + +[import.filter] +image = "图像文件" +media = "媒体文件" +video = "视频文件" + +[import.progress] +analyzing = "正在分析视频文件..." +complete = "导入完成!" +converting = "正在转换视频... {percent}%" +image = "正在导入图像..." +metadata = "正在创建项目元数据..." +starting = "正在开始转换..." + +[overlay] +choose_mode = "选择录制模式" +countdown_tooltip = "录制倒计时" +display_fallback = "显示器 {id}" + +[overlay.area] +draw = "绘制一个区域" +free_ratio = "自由" +hint = "点击并拖动以选择区域" +minimum_size = "最小尺寸为 {width} x {height}" +too_small = "{width} x {height} 太小" + +[overlay.camera_only] +description = "仅使用摄像头和麦克风录制" +select_camera = "请选择一个摄像头" + +[overlay.device] +camera_allow = "允许访问摄像头…" +camera_turn_on = "在设置中开启摄像头访问…" +microphone_allow = "允许访问麦克风…" +microphone_turn_on = "在设置中开启麦克风访问…" + +[overlay.start] +instant = "Instant 模式:录制分享链接" +instant_sign_in = "Instant 模式:登录后即可即时录制" +sign_in = "登录后使用" +start_recording = "开始录制" +studio = "Studio 模式:在本地录制和编辑" +take_screenshot = "截图" + +[permission.guide] +accessibility_body = "将它拖放到上方的“辅助功能”列表中,然后确认它的开关已打开。" +screen_body = "将它拖放到上方的“录屏与系统录音”列表中,然后确认它的开关已打开。" +title = "将 Cap 拖到列表中" + +[permission.name] +accessibility = "辅助功能" +camera = "摄像头" +microphone = "麦克风" +screen_recording = "屏幕录制" + +[permission.onboarding] +open_settings_again = "再次打开系统设置" +skip = "暂时跳过" +switch_on_title = "在系统设置中打开 Cap" + +[permission.onboarding.accessibility] +allow = "允许辅助功能" +body = "辅助功能让 Cap 能看到你的点击和光标(仅限这台 Mac),这样自动缩放就知道该看哪里。" +done_body = "自动缩放可以跟随你的点击了。" +done_title = "辅助功能已开启" +title = "让 Cap 跟随你的光标" + +[permission.onboarding.all_set] +body = "Cap 已获得所需的一切。接下来,快速了解它能做什么。" +regained = "谢谢。Cap 可以再次录制了。" +title = "一切就绪" + +[permission.onboarding.camera] +allow = "允许摄像头" +body = "在录制画面的一角显示你自己。不需要的话可以跳过。" +done_body = "你可以在任何录制中加入摄像头画面。" +done_title = "摄像头已开启" +title = "让 Cap 看到你" + +[permission.onboarding.checklist] +lede = "macOS 会先请你允许几项权限,只需一分钟。" +regained_lede = "系统设置中有项权限被关闭了。请重新开启以继续录制。" +regained_title = "Cap 需要重新获得一项权限" +title = "让我们准备好 Cap" + +[permission.onboarding.hint] +drag_into_list = "将 Cap 从系统设置下方的面板拖到列表中。已经在列表里了?直接打开即可。" +find_in_list = "在列表中找到 Cap 并打开。如果 macOS 先弹出了询问,请选择“打开系统设置”。" +no_prompt = "没有出现提示?请在系统设置中打开 Cap。" +open_settings = "打开系统设置,在列表中找到 Cap 并打开。" +turned_off = "它之前被关闭了,请在系统设置中打开 Cap,或暂时跳过。" + +[permission.onboarding.microphone] +allow = "允许麦克风" +body = "在录制中加入你的声音。你可以先跳过,之后再开启。" +done_body = "你的声音可以出现在录制中了。" +done_title = "麦克风已开启" +title = "让 Cap 听到你" + +[permission.onboarding.relaunch] +body = "Cap 重启后,macOS 会开启屏幕录制。你会回到这里继续。" +button = "重新启动" +hint = "已经打开但仍在等待?重启 Cap 以生效。" +later = "macOS 会在 Cap 重启后应用此设置。你可以在这里完成后再重启。" +title = "重启一次就好" + +[permission.onboarding.screen] +allow = "允许屏幕录制" +body = "屏幕录制权限让 Cap 能捕获你的显示器、窗口和系统音频。" +done_body = "Cap 可以捕获你的屏幕了。" +done_title = "屏幕录制已开启" +title = "让 Cap 看到你的屏幕" + +[permission.onboarding.status] +allowed = "已允许" +off = "已在系统设置中关闭" +optional = "可选" +required = "必需" +skipped = "暂时跳过" + +[permission.onboarding.waiting] +prompt = "在 macOS 提示中选择“允许”" +system_settings = "正在等待系统设置" + +[screenshot_editor] +loading_preview = "正在加载预览..." +zoom_in = "放大" +zoom_out = "缩小" + +[screenshot_editor.annotation] +default_text = "文本" +done = "完成" +fill = "填充" +intensity = "强度" +mask_style = "样式" +pixelate = "像素化" +stroke = "描边" +transparent = "透明" + +[screenshot_editor.aspect] +auto = "自动" +classic = "经典 ⋅4:3" +square = "方形 ⋅1:1" +tall = "竖版 ⋅3:4" +vertical = "竖屏 ⋅9:16" +wide = "宽屏 ⋅16:9" + +[screenshot_editor.background] +animated_gradient = "截图中的动态渐变会渲染为静态图像。选择其他背景即可替换。" +blur_title = "背景模糊" +choose_image = "点击选择或拖放图像" +color = "颜色" +gradient = "渐变" +image = "图像" +image_files = "图像" +image_title = "背景图像" +wallpaper = "壁纸" + +[screenshot_editor.corner] +rounded = "圆角" +squircle = "超椭圆" +style = "圆角样式" + +[screenshot_editor.crop] +full = "全部" +loading_frame = "正在加载画面…" +position = "位置" + +[screenshot_editor.error] +encode = "无法编码导出的截图:{error}" +load_meta = "无法加载截图元数据:{error}" +load_metadata = "无法加载截图元数据:{error}" +no_image = "在 {path} 中找不到 PNG" +not_screenshot = "这不是截图包" +open_image = "无法打开图像:{error}" +renderer_failed = "GPU 渲染器无法启动:{error}" +renderer_not_running = "截图渲染器未运行" +renderer_stopped = "渲染器在导出完成前停止了" +save_project = "保存项目时出错:{error}" +serialize = "无法序列化截图配置:{error}" +sign_in_to_share = "请在浏览器中登录 Cap,然后再次分享" +too_large = "图像尺寸超过上限:{width}x{height}" +upgrade_required = "此功能需要升级套餐" +upload_task = "上传任务失败:{error}" + +[screenshot_editor.header] +aspect_ratio = "宽高比" +copy = "复制到剪贴板" +crop_image = "裁剪图像" +more_actions = "更多操作" + +[screenshot_editor.layers] +empty = "还没有图层" +empty_hint = "使用上方的工具添加标注" +reorder_hint = "拖动以重新排序 • 顶部 = 最前" +title = "图层" + +[screenshot_editor.menu] +delete = "删除" +open_folder = "打开文件夹" + +[screenshot_editor.share] +create_link = "创建分享链接" +preparing = "正在准备上传" +rendering = "正在渲染截图" +uploading = "正在上传截图" + +[screenshot_editor.style] +advanced_shadow = "高级阴影设置" +background = "背景" +blur = "模糊" +border = "边框" +color = "颜色" +corner_rounding = "圆角" +opacity = "不透明度" +padding = "内边距" +rounding = "圆角" +shadow = "阴影" +size_label = "大小" +width = "宽度" + +[screenshot_editor.toast] +copied = "截图已复制到剪贴板!" +link_copied = "分享链接已复制到剪贴板" +saved = "截图已保存!" + +[screenshot_editor.tool] +arrow = "箭头" +circle = "圆形" +draw = "绘制" +mask = "遮罩" +rectangle = "矩形" +select = "选择" +text_label = "文本" + +[transcription.download] +complete = "下载完成" +failed = "下载失败:{error}" +file_progress = "正在下载 {file}:{percent}%" +preparing = "正在准备下载模型" +progress = "正在下载模型:{percent}%" + +[transcription.error] +intel_mac = "Parakeet 模型不支持 Intel Mac。请改用 Whisper 模型" +model_load = "无法加载字幕模型。请尝试重新下载" +model_missing = "未找到字幕模型。请先下载" +no_audio = "视频文件中没有音频" +unsupported_language = "“推荐”和“高精度”模型只能转写英语和其他 24 种欧洲语言。要为此语言生成字幕,请下载“小”或“中”模型" + +[upload.status] +failed = "上传需要处理" +paused = "上传已暂停" +prepared = "上传已准备" +processing = "正在处理上传" +retrying = "将重试上传" +sign_in = "登录以重试上传" +uploaded = "已上传" +uploading = "正在上传" diff --git a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs new file mode 100644 index 00000000000..e5397c53b46 --- /dev/null +++ b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs @@ -0,0 +1,1000 @@ +use super::*; + +pub(super) fn is_dialogs_surface_key(key: &str) -> bool { + [ + "auth.", + "diagnostics.", + "dialog.", + "import.", + "overlay.", + "permission.", + "screenshot_editor.", + "transcription.", + "upload.", + ] + .iter() + .any(|prefix| key.starts_with(prefix)) +} + +const FORMATTED: &[&str] = &[ + "diagnostics.error.create_folder", + "diagnostics.error.exited", + "diagnostics.error.serialize", + "diagnostics.error.start_failed", + "diagnostics.error.upload_logs", + "diagnostics.error.upload_status", + "diagnostics.error.wait_failed", + "diagnostics.error.write_failed", + "dialog.open_recording_failed", + "dialog.delete_recording.close_failed", + "dialog.delete_recording.failed", + "dialog.delete_recording.in_use", + "dialog.capture.cleanup_incomplete", + "dialog.capture.shortcut_hint", + "dialog.clipboard.copy_file", + "dialog.clipboard.copy_image", + "dialog.clipboard.unavailable_error", + "dialog.file.failed", + "import.image_failed", + "import.video_failed", + "import.progress.converting", + "overlay.display_fallback", + "overlay.area.minimum_size", + "overlay.area.too_small", + "screenshot_editor.error.encode", + "screenshot_editor.error.load_meta", + "screenshot_editor.error.load_metadata", + "screenshot_editor.error.no_image", + "screenshot_editor.error.open_image", + "screenshot_editor.error.renderer_failed", + "screenshot_editor.error.save_project", + "screenshot_editor.error.serialize", + "screenshot_editor.error.too_large", + "screenshot_editor.error.upload_task", + "transcription.download.failed", + "transcription.download.file_progress", + "transcription.download.progress", +]; + +#[test] +fn dialog_surfaces_match_the_literals_they_replaced() { + let pairs: &[(&str, &str)] = &[ + ( + tr::diagnostics::already_running(), + "A diagnostic is already running.", + ), + ( + tr::diagnostics::cancelled(), + "The diagnostic was cancelled.", + ), + (tr::diagnostics::mode_both(), "Both"), + ( + tr::diagnostics::error::no_output(), + "The self-test produced no output stream", + ), + ( + tr::diagnostics::stage::analyzing(), + "Analyzing the recording...", + ), + ( + tr::diagnostics::stage::analyzing_instant(), + "Analyzing the instant recording...", + ), + ( + tr::diagnostics::stage::analyzing_studio(), + "Analyzing the studio recording...", + ), + (tr::diagnostics::stage::cancelling(), "Cancelling..."), + ( + tr::diagnostics::stage::collecting(), + "Collecting system information...", + ), + ( + tr::diagnostics::stage::done(), + "Wrapping up the sync test...", + ), + ( + tr::diagnostics::stage::exporting(), + "Exporting the recording...", + ), + ( + tr::diagnostics::stage::exporting_instant(), + "Exporting the instant recording...", + ), + ( + tr::diagnostics::stage::exporting_studio(), + "Exporting the studio recording...", + ), + ( + tr::diagnostics::stage::finalizing(), + "Finalizing the recording...", + ), + ( + tr::diagnostics::stage::finalizing_instant(), + "Finalizing the instant recording...", + ), + ( + tr::diagnostics::stage::finalizing_studio(), + "Finalizing the studio recording...", + ), + ( + tr::diagnostics::stage::pattern(), + "Playing the test pattern...", + ), + ( + tr::diagnostics::stage::pattern_instant(), + "Playing the test pattern instant...", + ), + ( + tr::diagnostics::stage::pattern_studio(), + "Playing the test pattern studio...", + ), + (tr::diagnostics::stage::preparing(), "Preparing the test..."), + ( + tr::diagnostics::stage::preparing_instant(), + "Preparing the instant test...", + ), + ( + tr::diagnostics::stage::preparing_studio(), + "Preparing the studio test...", + ), + (tr::diagnostics::stage::recording(), "Recording the test..."), + ( + tr::diagnostics::stage::recording_instant(), + "Recording the instant test...", + ), + ( + tr::diagnostics::stage::recording_studio(), + "Recording the studio test...", + ), + ( + tr::diagnostics::stage::starting(), + "Starting the diagnostic...", + ), + ( + tr::dialog::no_microphone_body(), + "This recording will not include your voice.", + ), + ( + tr::dialog::delete_recording::already_deleting(), + "This recording is already being deleted.", + ), + ( + tr::dialog::delete_recording::editor_changed(), + "The editor changed while deletion was being confirmed. The recording was not deleted.", + ), + ( + tr::dialog::delete_recording::editor_closed(), + "The editor has already closed. The recording was not deleted.", + ), + ( + tr::dialog::delete_recording::failed_title(), + "Could not delete recording", + ), + ( + tr::dialog::delete_recording::finishing_body(), + "Cap is waiting for recording cleanup to finish before completing this deletion.", + ), + ( + tr::dialog::delete_recording::finishing_title(), + "Deletion is still finishing", + ), + ( + tr::dialog::delete_recording::outside_library(), + "This recording is outside the recordings library. Its files were not changed.", + ), + ( + tr::dialog::delete_recording::recording_active(), + "Finish or cancel the recording before deleting this project.", + ), + ( + tr::dialog::delete_recording::retained_title(), + "Recording retained", + ), + ( + tr::dialog::capture::approve_shortcut(), + "Approve a recording shortcut, or activate the Cap Stop tray icon when it appears. Recording has not started.", + ), + ( + tr::dialog::capture::area_unavailable(), + "The selected area changed or is no longer available. Select it again.", + ), + ( + tr::dialog::capture::camera_needs_x11(), + "Instant camera overlays require an X11 desktop on Linux", + ), + ( + tr::dialog::capture::camera_notice(), + "Cap will hide its camera preview and recording controls. Any selected camera will keep recording as a separate editable track.", + ), + ( + tr::dialog::capture::camera_notice_instant(), + "Cap will hide its preview and controls. Any selected camera will appear at its preview position with the selected shape and effects. Keep the whole preview inside the capture area. Opening Cap will stop this Instant recording before showing its controls.", + ), + ( + tr::dialog::capture::camera_outside_area(), + "Move the whole camera preview inside the capture area before starting Instant recording", + ), + ( + tr::dialog::capture::camera_preview_closed(), + "Open the selected camera preview before Instant recording", + ), + ( + tr::dialog::capture::devices_changed(), + "Selected devices changed. Start again to confirm the new recording inputs.", + ), + ( + tr::dialog::capture::devices_changed_before_start(), + "Selected devices changed before recording started. Start again to confirm the inputs.", + ), + ( + tr::dialog::capture::shortcut_conflict(), + "Ctrl+Shift+F9 is assigned to another Cap action. Change that shortcut before recording.", + ), + ( + tr::dialog::capture::shortcut_description(), + "Start or stop this Cap recording", + ), + ( + tr::dialog::capture::tray_stop_hint(), + "Activate the Cap Stop tray icon to start. Activate the same icon again to stop. Recording will not start until you use that control.", + ), + ( + tr::dialog::capture::window_unavailable(), + "The selected window is no longer available. Select it again.", + ), + ( + tr::dialog::clipboard::copy_file_failed(), + "Failed to copy file to clipboard", + ), + ( + tr::dialog::clipboard::copy_image_failed(), + "Failed to copy image to clipboard", + ), + ( + tr::dialog::clipboard::not_a_file(), + "Only files can be copied to the clipboard", + ), + ( + tr::dialog::clipboard::unavailable(), + "Clipboard unavailable", + ), + (tr::dialog::file::audio_filter(), "Audio"), + ( + tr::dialog::file::busy(), + "A file dialog is already open. Finish or cancel it before trying again.", + ), + (tr::dialog::file::export_filter(), "Export"), + (tr::dialog::file::images_filter(), "Images"), + ( + tr::dialog::file::unavailable_title(), + "File dialog unavailable", + ), + ( + tr::dialog::file::unconfirmed(), + "Cap could not confirm that the file dialog closed. Close any remaining file dialog and restart Cap before trying again.", + ), + ( + tr::dialog::storage::stopped_kept(), + "Recording stopped because storage is low. Your recording files were kept. Free up space, then recover the recording below.", + ), + ( + tr::dialog::storage::stopped_saved(), + "Recording stopped because storage is low. Your recording was saved.", + ), + ( + tr::dialog::storage::stopping(), + "Low storage. Stopping and saving your recording…", + ), + (tr::dialog::teleprompter::cue_markers(), "Cue markers"), + ( + tr::dialog::teleprompter::hidden(), + "This window is hidden from Cap recordings", + ), + (tr::dialog::teleprompter::mirror(), "Mirror text"), + ( + tr::dialog::teleprompter::paused(), + "Scrolling paused with recording", + ), + ( + tr::dialog::teleprompter::placeholder(), + "Paste or type your script…", + ), + (tr::auth::sign_in_failed(), "Sign in failed"), + (tr::import::error_title(), "Import Error"), + (tr::import::unsupported(), "Unsupported media file."), + (tr::import::filter::image(), "Image Files"), + (tr::import::filter::media(), "Media Files"), + (tr::import::filter::video(), "Video Files"), + (tr::import::progress::analyzing(), "Analyzing video file..."), + (tr::import::progress::complete(), "Import complete!"), + (tr::import::progress::image(), "Importing image..."), + ( + tr::import::progress::metadata(), + "Creating project metadata...", + ), + (tr::import::progress::starting(), "Starting conversion..."), + (tr::upload::status::failed(), "Upload needs attention"), + (tr::upload::status::paused(), "Upload paused"), + (tr::upload::status::prepared(), "Upload prepared"), + (tr::upload::status::processing(), "Processing upload"), + (tr::upload::status::retrying(), "Upload will retry"), + (tr::upload::status::sign_in(), "Sign in to retry upload"), + (tr::upload::status::uploaded(), "Uploaded"), + (tr::upload::status::uploading(), "Uploading"), + (tr::overlay::choose_mode(), "Choose recording mode"), + (tr::overlay::countdown_tooltip(), "Recording countdown"), + (tr::overlay::area::draw(), "Draw an area"), + (tr::overlay::area::free_ratio(), "Free"), + ( + tr::overlay::area::hint(), + "Click and drag to select an area", + ), + ( + tr::overlay::camera_only::description(), + "Record using only your camera and microphone", + ), + ( + tr::overlay::camera_only::select_camera(), + "Please select a camera", + ), + (tr::overlay::device::camera_allow(), "Allow camera access…"), + ( + tr::overlay::device::camera_turn_on(), + "Turn on camera access in Settings…", + ), + ( + tr::overlay::device::microphone_allow(), + "Allow microphone access…", + ), + ( + tr::overlay::device::microphone_turn_on(), + "Turn on microphone access in Settings…", + ), + ( + tr::overlay::start::instant(), + "Instant Mode — Record shareable link", + ), + ( + tr::overlay::start::instant_sign_in(), + "Instant Mode — Sign in to record instantly", + ), + (tr::overlay::start::sign_in(), "Sign In To Use"), + (tr::overlay::start::start_recording(), "Start Recording"), + ( + tr::overlay::start::studio(), + "Studio Mode — Record and edit locally", + ), + (tr::overlay::start::take_screenshot(), "Take Screenshot"), + ( + tr::dialog::mode_select::instant(), + "Share instantly with a link. Uploads as you record.", + ), + ( + tr::dialog::mode_select::screenshot(), + "Capture and annotate screenshots instantly.", + ), + ( + tr::dialog::mode_select::studio(), + "Highest quality local recording for editing later.", + ), + ( + tr::dialog::mode_select::subtitle(), + "Select how you want to capture your screen", + ), + (tr::dialog::mode_select::title(), "Choose Recording Mode"), + ( + tr::permission::onboarding::open_settings_again(), + "Open System Settings again", + ), + (tr::permission::onboarding::skip(), "Skip for now"), + ( + tr::permission::onboarding::switch_on_title(), + "Switch Cap on in System Settings", + ), + ( + tr::permission::onboarding::accessibility::allow(), + "Allow accessibility", + ), + ( + tr::permission::onboarding::accessibility::body(), + "Accessibility lets Cap see your clicks and cursor, on this Mac only, so auto zoom knows where to look.", + ), + ( + tr::permission::onboarding::accessibility::done_body(), + "Auto zoom can follow your clicks.", + ), + ( + tr::permission::onboarding::accessibility::done_title(), + "Accessibility is on", + ), + ( + tr::permission::onboarding::accessibility::title(), + "Let Cap follow your cursor", + ), + ( + tr::permission::onboarding::all_set::body(), + "Cap has everything it needs. Next, a quick look at what it can do.", + ), + ( + tr::permission::onboarding::all_set::regained(), + "Thanks. Cap can record again.", + ), + ( + tr::permission::onboarding::all_set::title(), + "You're all set", + ), + (tr::permission::onboarding::camera::allow(), "Allow camera"), + ( + tr::permission::onboarding::camera::body(), + "Show yourself in a corner of your recordings. Skip it if you don't need it.", + ), + ( + tr::permission::onboarding::camera::done_body(), + "You can add your camera to any recording.", + ), + ( + tr::permission::onboarding::camera::done_title(), + "Camera is on", + ), + ( + tr::permission::onboarding::camera::title(), + "Let Cap see you", + ), + ( + tr::permission::onboarding::checklist::lede(), + "macOS asks you to OK a few things first. It only takes a minute.", + ), + ( + tr::permission::onboarding::checklist::regained_lede(), + "Something was switched off in System Settings. Turn it back on to keep recording.", + ), + ( + tr::permission::onboarding::checklist::regained_title(), + "Cap needs a permission back", + ), + ( + tr::permission::onboarding::checklist::title(), + "Let's get Cap ready", + ), + ( + tr::permission::onboarding::hint::drag_into_list(), + "Drag Cap from the panel under System Settings into the list. Already listed? Just switch it on.", + ), + ( + tr::permission::onboarding::hint::find_in_list(), + "Find Cap in the list and switch it on. If macOS asked first, choose Open System Settings.", + ), + ( + tr::permission::onboarding::hint::no_prompt(), + "No prompt showed up? Switch Cap on in System Settings.", + ), + ( + tr::permission::onboarding::hint::open_settings(), + "Open System Settings, find Cap in the list and switch it on.", + ), + ( + tr::permission::onboarding::hint::turned_off(), + "It was turned off earlier, so switch Cap on in System Settings. Or skip it for now.", + ), + ( + tr::permission::onboarding::microphone::allow(), + "Allow microphone", + ), + ( + tr::permission::onboarding::microphone::body(), + "Add your voice to your recordings. You can skip this and turn it on later.", + ), + ( + tr::permission::onboarding::microphone::done_body(), + "Your voice can be in your recordings.", + ), + ( + tr::permission::onboarding::microphone::done_title(), + "Microphone is on", + ), + ( + tr::permission::onboarding::microphone::title(), + "Let Cap hear you", + ), + ( + tr::permission::onboarding::relaunch::body(), + "macOS turns on screen recording once Cap restarts. You'll pick up right here.", + ), + (tr::permission::onboarding::relaunch::button(), "Relaunch"), + ( + tr::permission::onboarding::relaunch::hint(), + "Switched on but still waiting? Restart Cap to apply it.", + ), + ( + tr::permission::onboarding::relaunch::later(), + "macOS applies it after Cap restarts. You can do that once you're done here.", + ), + ( + tr::permission::onboarding::relaunch::title(), + "One restart and you're set", + ), + ( + tr::permission::onboarding::screen::allow(), + "Allow screen recording", + ), + ( + tr::permission::onboarding::screen::body(), + "Screen Recording lets Cap capture your displays, windows and system audio.", + ), + ( + tr::permission::onboarding::screen::done_body(), + "Cap can capture your screen.", + ), + ( + tr::permission::onboarding::screen::done_title(), + "Screen recording is on", + ), + ( + tr::permission::onboarding::screen::title(), + "Let Cap see your screen", + ), + (tr::permission::onboarding::status::allowed(), "Allowed"), + ( + tr::permission::onboarding::status::off(), + "Off in System Settings", + ), + (tr::permission::onboarding::status::optional(), "Optional"), + (tr::permission::onboarding::status::required(), "Required"), + ( + tr::permission::onboarding::status::skipped(), + "Skipped for now", + ), + ( + tr::permission::onboarding::waiting::prompt(), + "Choose Allow in the macOS prompt", + ), + ( + tr::permission::onboarding::waiting::system_settings(), + "Waiting for System Settings", + ), + ( + tr::permission::guide::accessibility_body(), + "Drop it on the Accessibility list above, then make sure its switch is on.", + ), + ( + tr::permission::guide::screen_body(), + "Drop it on the Screen & System Audio Recording list above, then make sure its switch is on.", + ), + (tr::permission::guide::title(), "Drag Cap into the list"), + (tr::permission::name::accessibility(), "Accessibility"), + (tr::permission::name::camera(), "Camera"), + (tr::permission::name::microphone(), "Microphone"), + (tr::permission::name::screen_recording(), "Screen Recording"), + ( + tr::screenshot_editor::loading_preview(), + "Loading preview...", + ), + (tr::screenshot_editor::zoom_in(), "Zoom In"), + (tr::screenshot_editor::zoom_out(), "Zoom Out"), + (tr::screenshot_editor::annotation::default_text(), "Text"), + (tr::screenshot_editor::annotation::done(), "Done"), + (tr::screenshot_editor::annotation::fill(), "Fill"), + (tr::screenshot_editor::annotation::intensity(), "Intensity"), + (tr::screenshot_editor::annotation::mask_style(), "Style"), + (tr::screenshot_editor::annotation::pixelate(), "Pixelate"), + (tr::screenshot_editor::annotation::stroke(), "Stroke"), + ( + tr::screenshot_editor::annotation::transparent(), + "Transparent", + ), + (tr::screenshot_editor::aspect::auto(), "Auto"), + (tr::screenshot_editor::aspect::classic(), "Classic ⋅4:3"), + (tr::screenshot_editor::aspect::square(), "Square ⋅1:1"), + (tr::screenshot_editor::aspect::tall(), "Tall ⋅3:4"), + (tr::screenshot_editor::aspect::vertical(), "Vertical ⋅9:16"), + (tr::screenshot_editor::aspect::wide(), "Wide ⋅16:9"), + ( + tr::screenshot_editor::background::animated_gradient(), + "Animated Gradient is rendered as a still image for screenshots. Choose another background to replace it.", + ), + ( + tr::screenshot_editor::background::blur_title(), + "Background Blur", + ), + ( + tr::screenshot_editor::background::choose_image(), + "Click to select or drag and drop image", + ), + (tr::screenshot_editor::background::color(), "Color"), + (tr::screenshot_editor::background::gradient(), "Gradient"), + (tr::screenshot_editor::background::image(), "Image"), + (tr::screenshot_editor::background::image_files(), "Image"), + ( + tr::screenshot_editor::background::image_title(), + "Background Image", + ), + (tr::screenshot_editor::background::wallpaper(), "Wallpaper"), + (tr::screenshot_editor::corner::rounded(), "Rounded"), + (tr::screenshot_editor::corner::squircle(), "Squircle"), + (tr::screenshot_editor::corner::style(), "CORNER STYLE"), + (tr::screenshot_editor::crop::full(), "Full"), + ( + tr::screenshot_editor::crop::loading_frame(), + "Loading frame…", + ), + (tr::screenshot_editor::crop::position(), "Position"), + ( + tr::screenshot_editor::error::not_screenshot(), + "Not a screenshot bundle", + ), + ( + tr::screenshot_editor::error::renderer_not_running(), + "The screenshot renderer is not running", + ), + ( + tr::screenshot_editor::error::renderer_stopped(), + "The renderer stopped before the export finished", + ), + ( + tr::screenshot_editor::error::sign_in_to_share(), + "Sign in to Cap in your browser, then share again", + ), + ( + tr::screenshot_editor::error::upgrade_required(), + "This feature requires an upgraded plan", + ), + ( + tr::screenshot_editor::header::aspect_ratio(), + "Aspect Ratio", + ), + (tr::screenshot_editor::header::copy(), "Copy to Clipboard"), + (tr::screenshot_editor::header::crop_image(), "Crop Image"), + ( + tr::screenshot_editor::header::more_actions(), + "More Actions", + ), + (tr::screenshot_editor::layers::empty(), "No layers yet"), + ( + tr::screenshot_editor::layers::empty_hint(), + "Use the tools above to add annotations", + ), + ( + tr::screenshot_editor::layers::reorder_hint(), + "Drag to reorder • Top = front", + ), + (tr::screenshot_editor::layers::title(), "Layers"), + (tr::screenshot_editor::menu::delete(), "Delete"), + (tr::screenshot_editor::menu::open_folder(), "Open Folder"), + ( + tr::screenshot_editor::share::create_link(), + "Create shareable link", + ), + ( + tr::screenshot_editor::share::preparing(), + "Preparing upload", + ), + ( + tr::screenshot_editor::share::rendering(), + "Rendering screenshot", + ), + ( + tr::screenshot_editor::share::uploading(), + "Uploading screenshot", + ), + ( + tr::screenshot_editor::style::advanced_shadow(), + "Advanced shadow settings", + ), + (tr::screenshot_editor::style::background(), "Background"), + (tr::screenshot_editor::style::blur(), "Blur"), + (tr::screenshot_editor::style::border(), "Border"), + (tr::screenshot_editor::style::color(), "Color"), + ( + tr::screenshot_editor::style::corner_rounding(), + "Corner Rounding", + ), + (tr::screenshot_editor::style::opacity(), "Opacity"), + (tr::screenshot_editor::style::padding(), "Padding"), + (tr::screenshot_editor::style::rounding(), "Rounding"), + (tr::screenshot_editor::style::shadow(), "Shadow"), + (tr::screenshot_editor::style::size_label(), "Size"), + (tr::screenshot_editor::style::width(), "Width"), + ( + tr::screenshot_editor::toast::copied(), + "Screenshot copied to clipboard!", + ), + ( + tr::screenshot_editor::toast::link_copied(), + "Share link copied to clipboard", + ), + (tr::screenshot_editor::toast::saved(), "Screenshot saved!"), + (tr::screenshot_editor::tool::arrow(), "Arrow"), + (tr::screenshot_editor::tool::circle(), "Circle"), + (tr::screenshot_editor::tool::draw(), "Draw"), + (tr::screenshot_editor::tool::mask(), "Mask"), + (tr::screenshot_editor::tool::rectangle(), "Rectangle"), + (tr::screenshot_editor::tool::select(), "Select"), + (tr::screenshot_editor::tool::text_label(), "Text"), + (tr::transcription::download::complete(), "Download complete"), + ( + tr::transcription::download::preparing(), + "Preparing model download", + ), + ( + tr::transcription::error::intel_mac(), + "Parakeet models are not available on Intel Macs. Use a Whisper model instead", + ), + ( + tr::transcription::error::model_load(), + "Failed to load the caption model. Try downloading it again", + ), + ( + tr::transcription::error::model_missing(), + "Caption model not found. Please download it first", + ), + ( + tr::transcription::error::no_audio(), + "No audio found in the video file", + ), + ( + tr::transcription::error::unsupported_language(), + "The Recommended and High Accuracy models only transcribe English and 24 other European languages. Download the Small or Medium model to caption this language", + ), + ]; + for (translated, literal) in pairs { + assert_eq!(translated, literal); + } + + let plain_keys = KEYS + .iter() + .filter(|key| is_dialogs_surface_key(key)) + .count() + - FORMATTED.len(); + assert_eq!( + pairs.len(), + plain_keys, + "every plain key has a literal above" + ); + for key in FORMATTED { + assert!(KEYS.contains(key), "{key}"); + } +} + +fn old_stage_label(stage: &str, mode: Option<&str>) -> String { + let leg = match mode { + Some("studio") => " studio", + Some("instant") => " instant", + _ => "", + }; + match stage { + "collecting" => format!("Preparing the{leg} test..."), + "recording" => format!("Recording the{leg} test..."), + "pattern-run" => format!("Playing the test pattern{leg}..."), + "remuxing" => format!("Finalizing the{leg} recording..."), + "analyzing" => format!("Analyzing the{leg} recording..."), + "exporting" => format!("Exporting the{leg} recording..."), + "done" => "Wrapping up the sync test...".to_string(), + crate::diagnostics::START_STAGE => "Starting the diagnostic...".to_string(), + crate::diagnostics::COLLECT_STAGE => "Collecting system information...".to_string(), + crate::diagnostics::CANCEL_STAGE => "Cancelling...".to_string(), + other => format!("{other}..."), + } +} + +#[test] +fn assembled_dialog_messages_match_the_code_they_replaced() { + for stage in [ + "collecting", + "recording", + "pattern-run", + "remuxing", + "analyzing", + "exporting", + "done", + crate::diagnostics::START_STAGE, + crate::diagnostics::COLLECT_STAGE, + crate::diagnostics::CANCEL_STAGE, + "uploading", + ] { + for mode in [None, Some("studio"), Some("instant"), Some("both")] { + assert_eq!( + crate::diagnostics::stage_label(stage, mode), + old_stage_label(stage, mode), + "{stage} {mode:?}" + ); + } + } + for (body, pane) in [ + (tr::permission::guide::accessibility_body(), "Accessibility"), + ( + tr::permission::guide::screen_body(), + "Screen & System Audio Recording", + ), + ] { + assert_eq!( + body, + format!("Drop it on the {pane} list above, then make sure its switch is on.") + ); + } + let message = "Invalid data found when processing input"; + for (media, formatted) in [ + ("video", tr::import::video_failed(message)), + ("image", tr::import::image_failed(message)), + ] { + assert_eq!(formatted, format!("Failed to import {media}: {message}")); + } + assert_eq!( + tr::dialog::capture::shortcut_hint("Ctrl+Shift+F9"), + "Press and release Ctrl+Shift+F9 to start. Use the same shortcut to stop. This temporary shortcut does not change your settings." + ); + for permission in crate::permissions::OSPermission::ALL { + assert_eq!(permission.display_name(), permission.label()); + } +} + +#[test] +fn dialog_messages_format_like_the_code_they_replaced() { + let error = "permission denied (os error 13)"; + let path = std::path::Path::new("/tmp/Cap/report.json"); + let pairs: Vec<(String, String)> = vec![ + ( + tr::diagnostics::error::create_folder(error), + format!("Failed to create the diagnostics folder: {error}"), + ), + ( + tr::diagnostics::error::serialize(error), + format!("Failed to serialize the report: {error}"), + ), + ( + tr::diagnostics::error::start_failed(path.display(), error), + format!("Failed to start {}: {error}", path.display()), + ), + ( + tr::diagnostics::error::upload_logs(error), + format!("Failed to upload logs: {error}"), + ), + ( + tr::diagnostics::error::wait_failed(error), + format!("The self-test could not be waited on: {error}"), + ), + ( + tr::diagnostics::error::write_failed(path.display(), error), + format!("Failed to write {}: {error}", path.display()), + ), + ( + tr::dialog::open_recording_failed(error), + format!("Opening this recording failed: {error}"), + ), + ( + tr::dialog::delete_recording::close_failed(error), + format!("Could not close the editor: {error}"), + ), + ( + tr::dialog::delete_recording::failed(error), + format!("Recording deletion failed: {error}"), + ), + ( + tr::dialog::delete_recording::in_use(error), + format!( + "This recording is still in use. Finish or cancel its upload and try again: {error}" + ), + ), + ( + tr::dialog::capture::cleanup_incomplete(error), + format!("Recording cleanup is incomplete: {error}. A new recording is blocked."), + ), + ( + tr::dialog::capture::shortcut_hint("Super+F9"), + format!( + "Press and release {} to start. Use the same shortcut to stop. This temporary shortcut does not change your settings.", + "Super+F9" + ), + ), + ( + tr::dialog::clipboard::copy_file(error), + format!("Could not copy the file to the clipboard: {error}"), + ), + ( + tr::dialog::clipboard::copy_image(error), + format!("Could not copy the image to the clipboard: {error}"), + ), + ( + tr::dialog::clipboard::unavailable_error(error), + format!("Clipboard unavailable: {error}"), + ), + ( + tr::dialog::file::failed(error), + format!("The file dialog could not complete: {error}"), + ), + ( + tr::screenshot_editor::error::encode(error), + format!("Failed to encode screenshot export: {error}"), + ), + ( + tr::screenshot_editor::error::load_meta(error), + format!("Failed to load screenshot meta: {error}"), + ), + ( + tr::screenshot_editor::error::load_metadata(error), + format!("Failed to load screenshot metadata: {error}"), + ), + ( + tr::screenshot_editor::error::no_image(path.display()), + format!("No PNG found in {}", path.display()), + ), + ( + tr::screenshot_editor::error::open_image(error), + format!("Failed to open image: {error}"), + ), + ( + tr::screenshot_editor::error::renderer_failed(error), + format!("The GPU renderer failed to start: {error}"), + ), + ( + tr::screenshot_editor::error::save_project(error), + format!("Error saving project: {error}"), + ), + ( + tr::screenshot_editor::error::serialize(error), + format!("Failed to serialize screenshot config: {error}"), + ), + ( + tr::screenshot_editor::error::upload_task(error), + format!("The upload task failed: {error}"), + ), + ( + tr::transcription::download::failed(error), + format!("Download failed: {error}"), + ), + ]; + for (translated, literal) in &pairs { + assert_eq!(translated, literal); + } + + for status in ["exit status: 1", "signal: 9 (SIGKILL)"] { + assert_eq!( + tr::diagnostics::error::exited(status), + format!("The self-test exited with {status}") + ); + } + for status in ["500 Internal Server Error", "413 Payload Too Large"] { + assert_eq!( + tr::diagnostics::error::upload_status(status), + format!("Upload failed with status: {status}") + ); + } + for (width, height) in [(16_385_u32, 900_u32), (20_000, 20_000)] { + assert_eq!( + tr::screenshot_editor::error::too_large(width, height), + format!("Image dimensions exceed maximum: {width}x{height}") + ); + } + for progress in [0.0_f64, 0.426, 99.6, 100.0] { + assert_eq!( + tr::transcription::download::progress(format!("{progress:.0}")), + format!("Downloading model: {progress:.0}%") + ); + let filename = "ggml-small.bin"; + assert_eq!( + tr::transcription::download::file_progress(filename, format!("{progress:.0}")), + format!("Downloading {filename}: {progress:.0}%") + ); + } + for progress in [0.0_f64, 0.5, 0.999, 1.0] { + assert_eq!( + tr::import::progress::converting((progress * 100.0) as u32), + format!("Converting video... {}%", (progress * 100.0) as u32) + ); + } + for id in [1_u32, 69_733_632] { + assert_eq!(tr::overlay::display_fallback(id), format!("Display {}", id)); + } + for min in [150_f64, 200.] { + assert_eq!( + tr::overlay::area::minimum_size(min as u32, min as u32), + format!("Minimum size is {} x {}", min as u32, min as u32) + ); + } + for (width, height) in [(12.4_f64, 80.6_f64), (100., 3.)] { + assert_eq!( + tr::overlay::area::too_small(width.round(), height.round()), + format!("{} x {} is too small", width.round(), height.round()) + ); + } +} diff --git a/apps/desktop-gpui/src/i18n/mod.rs b/apps/desktop-gpui/src/i18n/mod.rs index d7d487d1a50..8ce0e4a362a 100644 --- a/apps/desktop-gpui/src/i18n/mod.rs +++ b/apps/desktop-gpui/src/i18n/mod.rs @@ -158,6 +158,8 @@ pub fn apply(preference: Preference, cx: &mut App) { cx.refresh_windows(); } +#[cfg(test)] +mod dialogs_surface_tests; #[cfg(test)] mod surface_tests; #[cfg(test)] diff --git a/apps/desktop-gpui/src/i18n/tests.rs b/apps/desktop-gpui/src/i18n/tests.rs index c3b43e28840..cb144b73115 100644 --- a/apps/desktop-gpui/src/i18n/tests.rs +++ b/apps/desktop-gpui/src/i18n/tests.rs @@ -440,6 +440,7 @@ fn english_matches_the_literals_it_replaced() { .filter(|key| { !key.starts_with(NEW_IN_THE_LANGUAGE_PICKER) && !super::surface_tests::is_main_surface_key(key) + && !super::dialogs_surface_tests::is_dialogs_surface_key(key) }) .count() - 3; From 25231420c9dae6a1e0224c5104d2edf56cb04fe0 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:44:09 +0000 Subject: [PATCH 05/25] fix(desktop-gpui): pick screenshot corner styles from the plain table --- apps/desktop-gpui/src/screenshot_editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop-gpui/src/screenshot_editor.rs b/apps/desktop-gpui/src/screenshot_editor.rs index 7d6057f8d41..b550b239155 100644 --- a/apps/desktop-gpui/src/screenshot_editor.rs +++ b/apps/desktop-gpui/src/screenshot_editor.rs @@ -2522,7 +2522,7 @@ impl ScreenshotEditorWindow { } MenuKind::CornerStyle => { self.menu = None; - let Some((style, _)) = Self::CORNER_STYLES.get(index) else { + let Some(style) = Self::CORNER_STYLES.get(index) else { return; }; let style = *style; From 14f3341e5e44cfd5121157294628b18b3eb8527c Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:46:39 +0000 Subject: [PATCH 06/25] fix(desktop-gpui): shorten narrow editor labels and settle the 3D shot terms --- apps/desktop-gpui/locales/GLOSSARY.md | 1 + apps/desktop-gpui/locales/de.toml | 6 +++--- apps/desktop-gpui/locales/es.toml | 14 +++++++------- apps/desktop-gpui/locales/fr.toml | 16 ++++++++-------- apps/desktop-gpui/locales/zh-CN.toml | 12 ++++++------ 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/apps/desktop-gpui/locales/GLOSSARY.md b/apps/desktop-gpui/locales/GLOSSARY.md index 8715a29e252..82849f68627 100644 --- a/apps/desktop-gpui/locales/GLOSSARY.md +++ b/apps/desktop-gpui/locales/GLOSSARY.md @@ -78,6 +78,7 @@ recording engine, which stays in English. | clip | 片段 | クリップ | clip | clip | Clip | क्लिप | | segment (of a recording) | 分段 | セグメント | segment | segmento | Segment | सेगमेंट | | recover / discard | 恢复 / 丢弃 | 復元 / 破棄 | récupérer / supprimer | recuperar / descartar | wiederherstellen / verwerfen | रिकवर करें / हटाएँ | +| shot (3D camera) | 镜头 | ショット | plan | toma | Einstellung | शॉट | | Personal / Commercial plan | 个人版 / 商业版 | 個人 / 商用 | Personnel / Commercial | Personal / Comercial | Privat / Kommerziell | पर्सनल / कमर्शियल | ## Fitting the UI diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index 5a16f5a8eca..710b77bdfaf 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -701,8 +701,8 @@ delete_tooltip = "Diese Einstellung löschen" depth_blur = "Tiefenunschärfe" distance = "Abstand" drag_to_orbit = "Ziehen zum Kreisen" -ease_in = "Einblenden" -ease_out = "Ausblenden" +ease_in = "Anlauf" +ease_out = "Auslauf" end_same_as_start = "Ende · wie Start" fine_tune = "Feinabstimmung" focus = "Fokus" @@ -732,7 +732,7 @@ timing_summary = "{easing} · Objektiv {fov}" [editor.panel.camera3d.blur] angle = "Winkel" falloff = "Verlauf" -focus_size = "Fokusgröße" +focus_size = "Fokusfeld" focus_x = "Fokus X" focus_y = "Fokus Y" position = "Position" diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index 6e0116652f0..aa24b475f5a 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -701,15 +701,15 @@ delete_tooltip = "Eliminar esta toma" depth_blur = "Desenfoque de profundidad" distance = "Distancia" drag_to_orbit = "Arrastra para orbitar" -ease_in = "Entrada suave" -ease_out = "Salida suave" +ease_in = "Entrada" +ease_out = "Salida" end_same_as_start = "Fin · igual al inicio" fine_tune = "Ajuste fino" focus = "Enfoque" header = "Toma 3D · {look} · {duration} s" hold_opening = "Mantener la pose inicial durante toda la toma" lens = "Lente" -look_title = "Toma" +look_title = "Encuadre" motion_style = "Estilo de movimiento" moves = "Movimientos" nothing_to_swap = "No hay nada que intercambiar en una toma fija" @@ -721,8 +721,8 @@ reset_camera = "Restablecer cámara" roll = "Giro" rotate_x = "Rotar X" rotate_y = "Rotar Y" -shift_x = "Desplazar X" -shift_y = "Desplazar Y" +shift_x = "Mover X" +shift_y = "Mover Y" shots = { one = "{count} toma", other = "{count} tomas" } still_shot = "Toma fija" swap = "Intercambiar inicio y fin" @@ -732,7 +732,7 @@ timing_summary = "{easing} · Lente {fov}" [editor.panel.camera3d.blur] angle = "Ángulo" falloff = "Caída" -focus_size = "Tamaño foco" +focus_size = "Área foco" focus_x = "Foco X" focus_y = "Foco Y" position = "Posición" @@ -783,7 +783,7 @@ system_sans = "Sans del sistema" system_serif = "Serif del sistema" [editor.panel.font_weight] -black = "Black" +black = "Negra" bold = "Negrita" custom = "Personalizado ({weight})" extra_bold = "Extranegrita" diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index 4326cbe4021..ed1dd948fb0 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -701,15 +701,15 @@ delete_tooltip = "Supprimer ce plan" depth_blur = "Flou de profondeur" distance = "Distance" drag_to_orbit = "Glisser pour orbiter" -ease_in = "Entrée douce" -ease_out = "Sortie douce" +ease_in = "Entrée" +ease_out = "Sortie" end_same_as_start = "Fin · identique au début" fine_tune = "Ajuster" -focus = "Mise au point" +focus = "Focus" header = "Plan 3D · {look} · {duration} s" hold_opening = "Garder la pose de départ pendant tout le plan" lens = "Objectif" -look_title = "Plan" +look_title = "Cadrage" motion_style = "Style de mouvement" moves = "Mouvements" nothing_to_swap = "Rien à inverser sur un plan fixe" @@ -721,8 +721,8 @@ reset_camera = "Réinitialiser la caméra" roll = "Roulis" rotate_x = "Rotation X" rotate_y = "Rotation Y" -shift_x = "Décalage X" -shift_y = "Décalage Y" +shift_x = "Pan X" +shift_y = "Pan Y" shots = { one = "{count} plan", other = "{count} plans" } still_shot = "Plan fixe" swap = "Inverser le début et la fin" @@ -731,8 +731,8 @@ timing_summary = "{easing} · Objectif {fov}" [editor.panel.camera3d.blur] angle = "Angle" -falloff = "Transition" -focus_size = "Taille nette" +falloff = "Fondu" +focus_size = "Netteté" focus_x = "Point X" focus_y = "Point Y" position = "Position" diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index 3ca660c52cb..0c8e3058529 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -708,8 +708,8 @@ fine_tune = "微调" focus = "焦点" header = "3D 镜头 · {look} · {duration} 秒" hold_opening = "整个镜头保持起始姿态" -lens = "镜头" -look_title = "镜头" +lens = "焦距" +look_title = "取景" motion_style = "运动方式" moves = "运镜" nothing_to_swap = "静止镜头无需交换" @@ -727,7 +727,7 @@ shots = { other = "{count} 个镜头" } still_shot = "静止镜头" swap = "交换开始和结束" timing_advanced = "时间与高级" -timing_summary = "{easing} · 镜头 {fov}" +timing_summary = "{easing} · 焦距 {fov}" [editor.panel.camera3d.blur] angle = "角度" @@ -783,7 +783,7 @@ system_sans = "系统无衬线" system_serif = "系统衬线" [editor.panel.font_weight] -black = "黑体" +black = "特黑" bold = "粗体" custom = "自定义({weight})" extra_bold = "特粗" @@ -882,7 +882,7 @@ overlay = "叠加" all = "全部" callouts = "标注" code = "代码" -lower_thirds = "下三分之一字幕" +lower_thirds = "字幕条" statements = "要点" titles = "标题" @@ -895,7 +895,7 @@ headline = "大标题" highlight = "高亮" kicker = "引题" label = "标签" -lower_third = "下三分之一" +lower_third = "字幕条" name_tag = "姓名牌" neon = "霓虹" quote = "引言" From 83fb58ade7167c4f77136ca3252301998579d3f6 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:49:39 +0000 Subject: [PATCH 07/25] fix(desktop-gpui): keep the diagnostic cancel sentinel in English --- apps/desktop-gpui/locales/de.toml | 1 - apps/desktop-gpui/locales/en.toml | 1 - apps/desktop-gpui/locales/es.toml | 1 - apps/desktop-gpui/locales/fr.toml | 1 - apps/desktop-gpui/locales/hi.toml | 1 - apps/desktop-gpui/locales/ja.toml | 1 - apps/desktop-gpui/locales/zh-CN.toml | 1 - apps/desktop-gpui/src/diagnostics.rs | 6 ++++-- apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs | 4 ---- 9 files changed, 4 insertions(+), 13 deletions(-) diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index d33914995ee..227b18e2277 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -592,7 +592,6 @@ sign_in_failed = "Anmeldung fehlgeschlagen" [diagnostics] already_running = "Es läuft bereits eine Diagnose." -cancelled = "Die Diagnose wurde abgebrochen." mode_both = "Beide" [diagnostics.error] diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index efb64d20d1e..e3b88b5fb41 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -594,7 +594,6 @@ sign_in_failed = "Sign in failed" [diagnostics] already_running = "A diagnostic is already running." -cancelled = "The diagnostic was cancelled." mode_both = { text = "Both", context = "Segment beside Studio and Instant: run the sync test in both modes." } [diagnostics.error] diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index e961fb1744c..56bd568460b 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -592,7 +592,6 @@ sign_in_failed = "No se pudo iniciar sesión" [diagnostics] already_running = "Ya hay un diagnóstico en curso." -cancelled = "Se canceló el diagnóstico." mode_both = "Ambos" [diagnostics.error] diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index 36126cda6dc..93a01ed3eea 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -592,7 +592,6 @@ sign_in_failed = "Échec de la connexion" [diagnostics] already_running = "Un diagnostic est déjà en cours." -cancelled = "Le diagnostic a été annulé." mode_both = "Les deux" [diagnostics.error] diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index 182aef9d746..ae30ca1802f 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -592,7 +592,6 @@ sign_in_failed = "साइन इन विफल रहा" [diagnostics] already_running = "एक डायग्नॉस्टिक पहले से चल रहा है।" -cancelled = "डायग्नॉस्टिक रद्द कर दिया गया।" mode_both = "दोनों" [diagnostics.error] diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 27e9da0c380..dbf645c648b 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -592,7 +592,6 @@ sign_in_failed = "サインインに失敗しました" [diagnostics] already_running = "診断はすでに実行中です。" -cancelled = "診断はキャンセルされました。" mode_both = "両方" [diagnostics.error] diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index 29f3695825a..496123a7771 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -592,7 +592,6 @@ sign_in_failed = "登录失败" [diagnostics] already_running = "已有一项诊断正在运行。" -cancelled = "诊断已取消。" mode_both = "两者" [diagnostics.error] diff --git a/apps/desktop-gpui/src/diagnostics.rs b/apps/desktop-gpui/src/diagnostics.rs index 2b65af2d1b7..3e685de80a2 100644 --- a/apps/desktop-gpui/src/diagnostics.rs +++ b/apps/desktop-gpui/src/diagnostics.rs @@ -489,7 +489,7 @@ pub fn run_sync_test( mut on_stage: impl FnMut(Stage), ) -> Result { if cancel.load(Ordering::Relaxed) { - return Err(tr::diagnostics::cancelled().to_string()); + return Err(CANCELLED.to_string()); } // Wrapped before anything else can fail: every early return from here on @@ -568,7 +568,7 @@ pub fn run_sync_test( .and_then(|mut child| child.0.wait().map_err(|error| error.to_string())); if cancel.load(Ordering::Relaxed) { - return Err(tr::diagnostics::cancelled().to_string()); + return Err(CANCELLED.to_string()); } if let Some(report) = report { return Ok(report); @@ -591,6 +591,8 @@ pub fn run_sync_test( ) } +pub const CANCELLED: &str = "The diagnostic was cancelled."; + // --------------------------------------------------------------------------- // The environment report // --------------------------------------------------------------------------- diff --git a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs index e5397c53b46..1cd744f95c6 100644 --- a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs +++ b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs @@ -63,10 +63,6 @@ fn dialog_surfaces_match_the_literals_they_replaced() { tr::diagnostics::already_running(), "A diagnostic is already running.", ), - ( - tr::diagnostics::cancelled(), - "The diagnostic was cancelled.", - ), (tr::diagnostics::mode_both(), "Both"), ( tr::diagnostics::error::no_output(), From 3044c83cc4ef2725505c7672c6844328a7d5b068 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:54:33 +0000 Subject: [PATCH 08/25] feat(desktop-gpui): localize the editor shell, export dialog, clips, timeline and editor tabs --- apps/desktop-gpui/locales/de.toml | 667 +++++++++++++ apps/desktop-gpui/locales/en.toml | 667 +++++++++++++ apps/desktop-gpui/locales/es.toml | 667 +++++++++++++ apps/desktop-gpui/locales/fr.toml | 667 +++++++++++++ apps/desktop-gpui/locales/hi.toml | 667 +++++++++++++ apps/desktop-gpui/locales/ja.toml | 667 +++++++++++++ apps/desktop-gpui/locales/zh-CN.toml | 667 +++++++++++++ apps/desktop-gpui/src/editor_canvas.rs | 17 +- apps/desktop-gpui/src/editor_clips.rs | 110 ++- apps/desktop-gpui/src/editor_export.rs | 899 +++++++++-------- apps/desktop-gpui/src/editor_modal.rs | 15 +- apps/desktop-gpui/src/editor_tabs.rs | 927 ++++++++++-------- apps/desktop-gpui/src/editor_timeline.rs | 137 +-- apps/desktop-gpui/src/editor_window.rs | 514 ++++++---- apps/desktop-gpui/src/editor_window/frame.rs | 93 +- apps/desktop-gpui/src/editor_window/scenes.rs | 7 +- apps/desktop-gpui/src/ui/segmented.rs | 10 +- apps/desktop-gpui/src/ui/selection_header.rs | 33 +- apps/desktop-gpui/src/ui/tooltip.rs | 3 +- 19 files changed, 6254 insertions(+), 1180 deletions(-) diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index 6a03591f376..242fec5feb7 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -586,3 +586,670 @@ click_or_press = "Hier klicken oder drücken:" get_started = "Los geht’s" space_key = "Leertaste" tagline = "Die Open-Source-App zum Teilen des Bildschirms für alle." + +[editor.canvas] +camera = "Kamera" +camera_follows_zoom = "Die Kameragröße folgt dem Zoom — zum Verschieben ziehen" +edit_zoom = "Zoom bearbeiten" +image = "Bild" +mask = "Maske" +screen = "Bildschirm" +screen_locked = "Der Bildschirm ist gesperrt, solange ein Zoom aktiv ist" +text_item = "Text" + +[editor.clips] +append_error = "Clip konnte nicht hinzugefügt werden: {message}" +append_error_title = "Aufnahmefehler" +back = "Zurück zum Editor" +cap_filter = "Cap-Aufnahme" +clip = "Clip {number}" +empty_description = "Nehmen Sie einen Clip auf oder importieren Sie einen, dann erscheint er hier." +empty_title = "Noch keine Clips" +import = "Importieren" +import_error = "Clip konnte nicht importiert werden: {message}" +import_error_title = "Importfehler" +import_existing = "Vorhandene Aufnahme" +import_mp4 = "MP4-Video…" +importing = "Import läuft…" +mp4_filter = "MP4-Video" +prepare_controls_failed = "Die Aufnahmesteuerung konnte nicht vorbereitet werden: {error}" +record_new = "Neuen Clip aufnehmen" +save_before_recording_failed = "Das aktuelle Projekt konnte vor der Aufnahme nicht gespeichert werden: {error}" +split = "Teil {number}" +title = "Clips" + +[editor.clips.error] +cancelled = "Import abgebrochen" +cannot_decode = "Video {path} kann nicht decodiert werden: {error}" +in_use = "Import nicht möglich, solange diese Aufnahme verwendet wird: {error}" +instant_source = "Instant-Aufnahmen können noch nicht in ein Projekt importiert werden" +instant_target = "Aufnahmen im Instant-Modus können nicht bearbeitet werden" +into_itself = "Eine Aufnahme kann nicht in sich selbst importiert werden" +missing_video = "Videodatei fehlt: {path}" +no_duration = "Das importierte Video hat keine gültige Dauer" +not_a_project = "Wählen Sie einen Cap-Projektordner aus" +source_empty = "Das Cap-Quellprojekt hat keine Aufnahmesegmente" +source_failed = "Das Cap-Quellprojekt ist fehlgeschlagen: {error}" +source_recording = "Das Cap-Quellprojekt wird noch aufgenommen" +source_unimportable = "Das Cap-Quellprojekt hat keine importierbaren Aufnahmesegmente" +unfinished_target = "Schließen Sie diese Aufnahme ab oder stellen Sie sie wieder her, bevor Sie einen Clip importieren" +unsupported_video = "Nicht unterstützte Videodatei: {path}" + +[editor.export] +back = "Zurück zum Editor" +existing_link_description = "Durch erneutes Hochladen wird das Video unter diesem Link durch Ihre neueste Bearbeitung ersetzt. Alle mit dem Link sehen die aktualisierte Version." +existing_link_title = "Bestehenden Link aktualisieren" +failed = "Export fehlgeschlagen" +fps = "{fps} FPS" +frames = { one = "{rendered} / {count} Frame", other = "{rendered} / {count} Frames" } +generating_preview = "Vorschau wird erstellt…" +link_unavailable_cursor = "Exporte nur mit Cursor können nur als Datei oder in die Zwischenablage gespeichert werden." +link_unavailable_transparent = "Transparente Exporte können nur als Datei oder in die Zwischenablage gespeichert werden." +organization = "Organisation" +preview = "Vorschau" +preview_info = "Dies ist ein gerendertes Bild aus Ihrem Video. Passen Sie die Einstellungen an, um die Qualität des fertigen Exports zu sehen." +preview_unavailable = "Vorschau nicht verfügbar" +reupload = "Erneut hochladen" +reupload_tooltip = "Ihre neueste Bearbeitung unter demselben Link hochladen" +reupload_transparent = "Links zum Teilen benötigen einen Hintergrund ohne Transparenz" +sign_in_required = "Sie müssen sich anmelden, um Aufnahmen zu teilen" +task_failed = "Exportvorgang fehlgeschlagen" +title = "Exportieren" +upgrade_message = "Diese Funktion erfordert einen höheren Tarif" +upgrade_title = "Upgrade erforderlich" +upload_failed = "Aufnahme konnte nicht hochgeladen werden" + +[editor.export.advanced] +bits_per_pixel = "Bits pro Pixel" +cursor_only = "Nur Cursor exportieren" +cursor_only_description = "Behält dieselbe Cursorbewegung und dieselben Klicks auf transparentem Hintergrund bei" +custom_bitrate = "Benutzerdefinierte Bitrate aktiv" +force_ffmpeg = "FFmpeg-Decoder erzwingen" +force_ffmpeg_description = "Hardware-Decoder überspringen (automatischer Fallback aktiv)" +title = "Erweitert" +transparent_mov = "Exportiert als transparente MOV-Datei. Die Dateien sind groß und eignen sich am besten zum Compositing oder Bearbeiten." + +[editor.export.cta] +cancel_sign_in = "Anmeldung abbrechen" +clipboard = "In die Zwischenablage exportieren" +create_link = "Link zum Teilen erstellen" +file = "Als Datei exportieren" +reupload = "Unter demselben Link erneut hochladen" +sign_in = "Zum Teilen anmelden" + +[editor.export.destination] +clipboard = "Zwischenablage" +file = "Datei" +link = "Link zum Teilen" + +[editor.export.done] +copy_link = "Link kopieren" +instant_hint = "Verwenden Sie für Ihre nächste Aufnahme den Instant-Modus, wenn Sie direkt nach dem Stoppen einen Link möchten." +link_copied = "Link kopiert" +open_file = "Datei öffnen" +open_link = "Link öffnen" +retry_copy = "Erneut kopieren" +reuploaded = "Ihre neueste Bearbeitung ist unter demselben Link bereit" +uploaded = "Ihr Cap wurde erfolgreich hochgeladen" + +[editor.export.estimate] +gigabytes = "~{value} GB" +gigabytes_range = "~{lower}–{upper} GB" +hours = "~{value} Std." +hours_range = "~{lower}–{upper} Std." +megabytes = "~{value} MB" +megabytes_range = "~{lower}–{upper} MB" +minutes = "~{value} Min." +minutes_range = "~{lower}–{upper} Min." +seconds = "~{value} s" +seconds_range = "~{lower}–{upper} s" +under_megabyte = "< 1 MB" +under_second = "< 1 s" + +[editor.export.optimize] +description = "Kodiert per Software neu für deutlich kleinere Dateien (langsamer)" +title = "Dateigröße optimieren" + +[editor.export.phase] +clipboard_failed = "Export abgeschlossen; Kopieren in die Zwischenablage fehlgeschlagen" +complete = "Export abgeschlossen" +copied = "In die Zwischenablage kopiert" +copying_to_clipboard = "Wird in die Zwischenablage kopiert" +rendering_cursor = "Cursorspur wird gerendert" +rendering_gif = "GIF wird gerendert" +rendering_video = "Video wird gerendert" +reupload_complete = "Erneutes Hochladen abgeschlossen" +reuploading = "Wird erneut unter Ihrem Link hochgeladen" +saving_to_file = "Wird als Datei gespeichert" +starting = "Export wird vorbereitet" +upload_complete = "Hochladen abgeschlossen" +uploading = "Link zum Teilen wird erstellt" + +[editor.export.quality] +larger = "Größere Datei" +maximum = "Maximal" +potato = "Kartoffel" +smaller = "Kleinere Datei" +social = "Social" +web = "Web" + +[editor.export.resources] +proceed = "Trotzdem fortfahren" +title = "Exportressourcen prüfen" + +[editor.export.section] +destination = "Ziel" +format = "Format" +frame_rate = "Bildrate" +quality = "Qualität" +resolution = "Auflösung" + +[editor.export.stats] +calculating = "Wird berechnet…" +duration = "Dauer" +estimated_size = "Geschätzte Größe" +export_time = "Exportdauer" +output = "Ausgabe" +output_value = "{width}×{height} · {fps} fps" +refining_size = "Größe wird präzisiert…" +refining_time = "Dauer wird präzisiert…" +unavailable = "Nicht verfügbar" + +[editor.modal] +cancel_export = "Export abbrechen" +cancel_export_message = "Möchten Sie den Export wirklich abbrechen?" +cancel_export_title = "Export abbrechen?" +keep_exporting = "Weiter exportieren" +recording_retained = "Aufnahme beibehalten" + +[editor.tabs.audio] +isolation_balanced = "Ausgewogen" +isolation_balanced_description = "Klarere Trennung mit natürlichen Stimmdetails." +isolation_light = "Leicht" +isolation_light_description = "Behält mehr von Ihrer ursprünglichen Stimme und dem Raumklang." +isolation_strong = "Stark" +isolation_strong_description = "Mehr Trennung für laute Umgebungen. Kann den Stimmklang verändern." +mic_volume = "Mikrofonlautstärke" +mute = "Audio stummschalten" +stereo_mode = "Stereomodus des Mikrofons" +studio_sound_description = "Reduziert Hintergrundgeräusche und gleicht die Lautstärke Ihrer Stimme aus." +studio_sound_save_failed = "Die Standardeinstellung für Studio Sound konnte nicht gespeichert werden" +system_volume = "Systemaudio-Lautstärke" +title = "Audiosteuerung" +use_for_new = "Für neue Aufnahmen verwenden" + +[editor.tabs.audio.stereo] +mono_left = "Mono L" +mono_right = "Mono R" +stereo = "Stereo" + +[editor.tabs.camera] +advanced_shadow = "Erweiterte Schatteneinstellungen" +background = "Hintergrund" +corner_style = "Eckenstil" +custom_position = "Eigene Position (auf der Leinwand gezogen)" +hide = "Kamera ausblenden" +keep_size_during_zoom = "Originalgröße beim Zoomen beibehalten" +mirror = "Kamera spiegeln" +position = "Position" +reset = "Zurücksetzen" +rounded_corners = "Abgerundete Ecken" +shadow = "Schatten" +shadow_blur = "Unschärfe" +shadow_opacity = "Deckkraft" +shape_label = "Form" +size_during_zoom = "Größe beim Zoomen" +size_label = "Größe" +title = "Kamera" + +[editor.tabs.camera.blur] +heavy = "Starke Unschärfe" +light = "Leichte Unschärfe" +off = "Aus" +remove = "Hintergrund entfernen" + +[editor.tabs.camera.shape] +source = "Original" +square = "Quadrat" + +[editor.tabs.captions] +active_word_highlight = "Aktives Wort hervorheben" +animation_section = "Animation" +animation_style = "Animationsstil" +background_color = "Hintergrundfarbe" +background_opacity = "Hintergrunddeckkraft" +background_settings = "Hintergrundeinstellungen" +beta = "Beta" +delete = "Löschen" +deleting = "Wird gelöscht..." +download = "Modell {model} herunterladen" +download_note = "Einmaliger Download auf Ihr System. Alle Untertitel werden lokal gespeichert." +downloading = "{model} wird heruntergeladen... {percent}%" +fade_duration = "Einblenddauer" +font_family = "Schriftart" +font_settings = "Schrifteinstellungen" +font_weight = "Schriftstärke" +generate = "Untertitel erstellen" +generate_failed = "Untertitel konnten nicht erstellt werden: {reason}" +generating = "Wird erstellt..." +highlight_color = "Hervorhebungsfarbe" +highlight_note = "Dies ist die erste Version der Untertitel in Cap. Die Hervorhebung des aktiven Worts kann in manchen Situationen ungenau sein. Wir arbeiten an einer Lösung, die in kommenden Versionen erscheint." +highlight_style = "Hervorhebungsstil" +horizontal_position = "Horizontale Position" +intel_unavailable = "Parakeet-Untertitelmodelle sind auf Intel-Macs nicht verfügbar. Whisper-Modelle bleiben verfügbar." +keep_open = "Lassen Sie Cap geöffnet, während das Modell heruntergeladen wird. Nach einem Neuladen des Editors wird automatisch wieder verbunden." +language_label = "Sprache" +model_downloaded = "Modell {model} heruntergeladen" +model_label = "Modell" +position = "Position" +regenerate = "Untertitel neu erstellen" +size_label = "Größe" +style = "Stil" +text_color = "Textfarbe" +title = "Untertitel" +uppercase = "Großbuchstaben" +vertical_position = "Vertikale Position" + +[editor.tabs.captions.animation] +bounce = "Hüpfen" +none = "Keine" +pop = "Aufpoppen" + +[editor.tabs.captions.highlight] +color = "Farbe" +pill = "Pille" + +[editor.tabs.captions.language] +ar = "Arabisch" +auto = "Automatisch erkennen" +bn = "Bengalisch" +de = "Deutsch" +en = "Englisch" +es = "Spanisch" +fa = "Persisch" +fr = "Französisch" +gu = "Gujarati" +he = "Hebräisch" +hi = "Hindi" +it = "Italienisch" +ja = "Japanisch" +ko = "Koreanisch" +mr = "Marathi" +nl = "Niederländisch" +pa = "Punjabi" +pl = "Polnisch" +pt = "Portugiesisch" +ru = "Russisch" +sk = "Slowakisch" +ta = "Tamil" +te = "Telugu" +tr = "Türkisch" +ur = "Urdu" +zh = "Chinesisch" + +[editor.tabs.captions.model] +high_accuracy = "Hohe Genauigkeit" +high_accuracy_description = "Größerer Download, höhere Genauigkeit" +medium = "Mittel" +medium_description = "Langsamer, genauer" +recommended = "Empfohlen" +recommended_description = "Beste Balance für die meisten Aufnahmen" +small = "Klein" +small_description = "Kleinster Download" + +[editor.tabs.captions.preset] +classic = "Klassisch" +classic_description = "Klarer Text auf einem einfarbigen, abgerundeten Hintergrund." +highlight = "Hervorgehoben" +highlight_description = "Fette Großbuchstaben mit einer Pille hinter dem aktiven Wort." +karaoke = "Karaoke" +karaoke_description = "Wörter leuchten synchron zur Sprache auf." +minimal = "Minimal" +minimal_description = "Dezenter, umrandeter Text ohne Hintergrund." +pop = "Pop" +pop_description = "Verspielte Großbuchstaben, die mit einem kräftigen Akzent aufpoppen." + +[editor.tabs.corner] +rounded = "Abgerundet" +squircle = "Squircle" + +[editor.tabs.cursor] +friction = "Reibung" +hide_when_idle = "Bei Inaktivität ausblenden" +inactivity_delay = "Verzögerung bei Inaktivität" +mass = "Masse" +movement_style = "Bewegungsstil des Cursors" +show = "Cursor anzeigen" +size_label = "Größe" +smooth_movement = "Gleichmäßige Bewegung" +style_label = "Cursorstil" +svg_cursors = "Hochwertige SVG-Cursor" +tension = "Spannung" +tilt = "Neigung" + +[editor.tabs.cursor.style] +fast = "Schnell" +fast_description = "Zackig, eng folgend" +mellow = "Sanft" +mellow_description = "Ausgewogene, natürliche Bewegung" +slow = "Langsam" +slow_description = "Lange, gleitende Bewegung" +smooth = "Weich" +smooth_description = "Weiche, schwebende Bewegung" + +[editor.tabs.keyboard] +animation = "Animation" +background_color = "Hintergrundfarbe" +background_opacity = "Hintergrunddeckkraft" +background_settings = "Hintergrundeinstellungen" +behavior = "Verhalten" +beta = "Beta" +changed_during_generation = "Die Zeitleiste oder die Tastatureinstellungen haben sich während der Erstellung geändert. Versuchen Sie es erneut." +empty_description = "Klicken Sie auf „Tastatursegmente erstellen“, um Segmente aus aufgezeichneten Tastendrücken zu erstellen." +empty_title = "Noch keine Tastatursegmente." +fade_duration = "Einblenddauer" +font_family = "Schriftart" +font_settings = "Schrifteinstellungen" +font_weight = "Schriftstärke" +generate = "Tastatursegmente erstellen" +generating = "Tastatursegmente werden erstellt..." +grouping_threshold = "Gruppierungsschwelle" +linger_duration = "Anzeigedauer" +load_failed = "Aufnahmedaten konnten nicht geladen werden: {error}" +no_presses = "Es wurden keine aufgezeichneten Tastendrücke gefunden." +position = "Position" +regenerate = "Tastatursegmente neu erstellen" +show_modifiers = "Sondertasten-Modifikatoren anzeigen" +show_special_keys = "Spezialtasten anzeigen" +size_label = "Größe" +text_color = "Textfarbe" +timeline_unavailable = "Die Projektzeitleiste ist nicht verfügbar." +timeline_unreadable = "Die Projektzeitleiste konnte nicht gelesen werden." +title = "Tastatur anzeigen" +uppercase = "Großbuchstaben" + +[editor.tabs.position] +bottom_center = "Unten mittig" +bottom_left = "Unten links" +bottom_right = "Unten rechts" +manual = "Manuell" +top_center = "Oben mittig" +top_left = "Oben links" +top_right = "Oben rechts" + +[editor.tabs.sync] +auto_description = "Cap hat diese Versätze automatisch berechnet, damit der Ton synchron zum Video bleibt. Passen Sie sie an, falls noch etwas nicht passt." +auto_synced = "Automatisch synchronisiert" +camera = "Kameraversatz" +clip = "Clip {number}" +description = "Feinjustieren Sie die Quellversätze, wenn Ton oder Kamera nicht mehr synchron zur Bildschirmaufnahme laufen." +microphone = "Mikrofonversatz" +system_audio = "Systemaudio-Versatz" +title = "Synchronisierung" + +[editor.tabs.weight] +bold = "Fett" +medium = "Mittel" +normal = "Normal" + +[editor.timeline] +add_shot = "Einstellung hinzufügen" +add_shot_with_look = "Einstellung hinzufügen · {look} · {seconds} s" +paused = "Pausiert" + +[editor.timeline.empty] +add_audio = "Audio hinzufügen" +add_scene = "Szene hinzufügen" +audio = "Fügen Sie Hintergrundmusik hinzu oder importieren Sie eigenes Audio" +captions = "Keine Untertitel" +keyboard = "Keine Tastaturereignisse" +scene = "Wechseln Sie zwischen Layouts für Bildschirm und Kamera" + +[editor.timeline.picker] +audio = "Fügen Sie Hintergrundmusik hinzu oder importieren Sie eigenes Audio." +caption = "Transkribieren Sie Ihre Aufnahme automatisch in Untertitel auf dem Bildschirm." +clip = "Ihr aufgenommenes Bildschirmmaterial." +image = "Fügen Sie Ihrer Aufnahme ein Bild hinzu." +keyboard = "Zeigen Sie beim Tippen Tastendrücke auf dem Bildschirm an." +mask = "Verpixeln oder schwärzen Sie private Bereiche des Bildschirms." +scene = "Wechseln Sie zwischen Layouts für Bildschirm und Kamera." +scene_unavailable = "Nehmen Sie mit einer Kamera auf, um Szenen zu verwenden." +style = "Ändern Sie Hintergrund-, Kamera- und Cursoreinstellungen im Zeitverlauf." +text_overlay = "Fügen Sie der Leinwand eigene Texteinblendungen und Titel hinzu." +three_d = "Neigen Sie die Szene in 3D-Perspektive." +zoom = "Sanfte Zooms, die dem Geschehen folgen." + +[editor.timeline.remove] +audio = "Die Audiospur entfernen." +captions = "Die Untertitelspur entfernen." +clip = "Die Clipspur entfernen." +image = "Die Bildspur entfernen." +keyboard = "Die Tastaturspur entfernen." +mask = "Die Maskenspur entfernen." +scene = "Die Szenenspur entfernen." +style = "Die Stilspur entfernen." +text_track = "Die Textspur entfernen." +three_d = "Die 3D-Spur entfernen." +zoom = "Die Zoomspur entfernen." + +[editor.timeline.scene] +camera_only = "Nur Kamera" +default = "Standard" +floating = "Schwebend" +hide_camera = "Kamera ausblenden" +split_screen = "Geteilter Bildschirm" + +[editor.timeline.segment] +audio = "Audio" +automatic_zoom = "Automatischer Zoom" +caption = "Untertitel" +clip = "Clip" +cursor_hidden = "Cursor ausgeblendet" +highlight = "Hervorheben" +label = "Beschriftung" +manual_zoom = "Manueller Zoom" +muted = "Stumm" +numbered_clip = "Clip {number}" +sensitive = "Vertraulich" + +[editor.timeline.track] +audio = "Audio" +captions = "Untertitel" +clip = "Clip" +image = "Bild" +keyboard = "Tastatur" +mask = "Maske" +scene = "Szene" +style = "Stil" +text_track = "Text" +video = "Video" +zoom = "Zoom" + +[editor.window] +audio_filter = "Audio" +audio_segment_changed = "Dieses Audiosegment hat sich geändert. Wählen Sie es aus und wählen Sie erneut einen Ersatz." +crop = "Zuschneiden" +crop_tooltip = "Video zuschneiden" +no_room_for_3d_shot = "Kein Platz für eine weitere 3D-Einstellung" +redo = "Wiederholen" +save_failed = "{path} konnte nicht gespeichert werden. Lassen Sie den Editor geöffnet und versuchen Sie es erneut: {error}" +undo = "Rückgängig" + +[editor.window.add_track] +button = "Spur hinzufügen" +hide_3d = "Die 3D-Spur ausblenden." +hint = "Wählen Sie eine Spur, die Sie Ihrer Zeitleiste hinzufügen möchten." + +[editor.window.aspect] +auto = "Auto" +classic = "Klassisch" +classic_ratio = "Klassisch ⋅4:3" +square = "Quadrat" +square_ratio = "Quadrat ⋅1:1" +tall = "Hoch" +tall_ratio = "Hoch ⋅3:4" +tooltip = "Seitenverhältnis" +vertical = "Vertikal" +vertical_ratio = "Vertikal ⋅9:16" +wide = "Breit" +wide_ratio = "Breit ⋅16:9" + +[editor.window.audio_import] +copy_failed = "Audiodatei konnte nicht kopiert werden: {error}" +directory_failed = "Audioordner konnte nicht erstellt werden: {error}" +no_extension = "Die Audiodatei hat keine Dateiendung" +not_found = "Audiodatei nicht gefunden: {path}" +unsupported = "Nicht unterstütztes Audioformat: .{extension}" + +[editor.window.busy] +clip_import = "Warten Sie, bis der Clip-Import abgeschlossen ist, bevor Sie diese Aufnahme schließen oder löschen." +export = "Warten Sie, bis der Export oder sein Abbruch abgeschlossen ist, bevor Sie diese Aufnahme schließen oder löschen." +opening = "Warten Sie, bis die Aufnahme vollständig geöffnet ist, bevor Sie sie löschen." +save_before_delete_failed = "Die Aufnahme konnte vor dem Löschen nicht gespeichert werden: {error}" + +[editor.window.camera3d_lane] +add_shot = "+ Einstellung hinzufügen" +auto_scene = "Automatische Szene" +how_many = "Wie viele Einstellungen?" +prompt = "Fügen Sie Ihrer Aufnahme filmische 3D-Einstellungen hinzu" + +[editor.window.clip_menu] +audio = "AUDIO" +cursor = "CURSOR" +delete = "Clip löschen" +delete_only_clip = "Der einzige Clip kann nicht gelöscht werden" +hide_cursor = "Cursor ausblenden" +hide_cursor_description = "Wird ausgeblendet, während dieser Clip läuft" +keep_pitch = "Tonhöhe beibehalten" +match_speed = "An Tempo anpassen" +merge_next = "Mit nächstem Clip zusammenführen" +merge_previous = "Mit vorherigem Clip zusammenführen" +mute = "Stumm" +mute_clip = "Clip stummschalten" +mute_clip_description = "Den Ton dieses Clips stummschalten" +rename = "Clip umbenennen" +speed = "TEMPO" +split = "Am Abspielkopf teilen" +split_hint = "Bewegen Sie den Abspielkopf in diesen Clip" +volume = "Lautstärke" + +[editor.window.frame] +add_tooltip = "Rahmen hinzufügen" +browser = "Browser" +browser_description = "Browserfenster mit Adressleiste" +button = "Rahmen" +dark = "Dunkel" +global = "Globaler Rahmen · Gilt überall, wo der Stil den Hintergrund übernimmt." +heading = "Rahmen" +light = "Hell" +macbook_description = "Laptop-Rahmen um die Aufnahme" +macos_description = "Fensterrahmen mit Ampel-Tasten" +none = "Keiner" +none_description = "Die Aufnahme unverändert zeigen" +style_changed = "Dieser Stil hat sich geändert. Schließen und öffnen Sie den Rahmen erneut." +style_only = "Nur Stil {index} · Globale Einstellungen bleiben unverändert." +style_only_override = "Nur Stil {index} · Bearbeiten aktiviert die eigene Hintergrundeinstellung." +theme = "Design" +title = "Titel" +url = "URL" +window_title_placeholder = "Fenstertitel" +windows_description = "Titelleiste mit Fenstersteuerung" + +[editor.window.header] +captions = "Untertitel" +delete_recording = "Aufnahme löschen" +export = "Exportieren" +open_bundle = "Aufnahmepaket öffnen" +presets = "Vorlagen" + +[editor.window.image] +changed = "Dieses Bild hat sich bei der Auswahl eines Ersatzes geändert. Wählen Sie es aus und versuchen Sie es erneut." +filter = "Bilder" +recording_unavailable = "Die Aufnahme ist für dieses Bild nicht mehr verfügbar. Öffnen Sie das Projekt erneut." + +[editor.window.open] +damaged = "Die Videospuren dieser Aufnahme konnten nicht geöffnet werden. Das Paket scheint beschädigt zu sein." +failed = "Diese Aufnahme konnte nicht geöffnet werden" +media_failed = "Die Medien dieser Aufnahme konnten nicht gelesen werden: {error}" +meta_failed = "Die Aufnahme-Metadaten konnten nicht geladen werden: {error}" +no_segments = "Die Aufnahme hat keine Segmente. Möglicherweise muss sie zuerst wiederhergestellt werden." +not_found = "Videopfad {path} nicht gefunden!" +not_studio = "Nur Studio-Aufnahmen können bearbeitet werden" + +[editor.window.presets] +apply = "Anwenden" +create = "Erstellen" +create_new = "Neue Vorlage erstellen" +create_title = "Vorlage erstellen" +default = "Standard" +default_badge = "Standard" +delete = "Löschen" +delete_confirm = "Möchten Sie „{name}“ wirklich löschen?" +delete_title = "Vorlage löschen" +name_placeholder = "Name der Vorlage" +none_saved = "Keine gespeicherten Vorlagen" +rename = "Umbenennen" +rename_title = "Vorlage umbenennen" +save_settings = "Einstellungen in Vorlage speichern" +set_default = "Als Standard festlegen" + +[editor.window.preview] +full = "Voll" +half = "Halb" +label = "Vorschau" +quarter = "Viertel" + +[editor.window.text_menu] +copy_style = "Stil kopieren" +copy_style_first = "Zuerst einen Stil kopieren" +delete_segment = "Textsegment löschen" +delete_segments = "Textsegmente löschen" +paste_style = "Stil einfügen" +paste_style_all = "Stil in alle ausgewählten einfügen" +segments = { one = "{count} Textsegment", other = "{count} Textsegmente" } +untitled = "Text" + +[editor.window.title] +not_saved = "Titel nicht gespeichert" +not_saved_message = "{error}\n\nIhr Titel ist noch im Editor. Versuchen Sie erneut, ihn zu speichern." +read_failed = "Der Aufnahmetitel für {path} konnte nicht gelesen werden: {error}" +save_failed = "Der Aufnahmetitel für {path} konnte nicht gespeichert werden: {error}" + +[editor.window.track] +clear_all = "Alle entfernen" +delete = "Löschen" + +[editor.window.transport] +done_splitting = "Teilen beenden" +play_pause = "Video abspielen/pausieren" +split = "Teilen" +split_tooltip = "Klicken Sie auf ein Segment, um es zu teilen. Esc zum Beenden." +zoom_in = "Vergrößern" +zoom_out = "Verkleinern" + +[editor.window.zoom_lane] +generate = "Erstellen" +generating = "Wird erstellt…" +no_clicks = "Keine Klicks zum Zoomen gefunden. Ziehen Sie über die Spur, um einen hinzuzufügen." +prompt = "Zooms automatisch hinzufügen" + +[ui] +coming_soon = "Demnächst verfügbar" + +[ui.selection] +audio = { one = "{count} Audiosegment ausgewählt", other = "{count} Audiosegmente ausgewählt" } +caption = { one = "{count} Untertitelsegment ausgewählt", other = "{count} Untertitelsegmente ausgewählt" } +delete = "Löschen" +done = "Fertig" +image = { one = "{count} Bildsegment ausgewählt", other = "{count} Bildsegmente ausgewählt" } +keyboard = { one = "{count} Tastatursegment ausgewählt", other = "{count} Tastatursegmente ausgewählt" } +mask = { one = "{count} Maskensegment ausgewählt", other = "{count} Maskensegmente ausgewählt" } +scene = { one = "{count} Szenensegment ausgewählt", other = "{count} Szenensegmente ausgewählt" } +segments = { one = "{count} Segment ausgewählt", other = "{count} Segmente ausgewählt" } +select_all = "Alle auswählen" +style = { one = "{count} Stilsegment ausgewählt", other = "{count} Stilsegmente ausgewählt" } +text_segments = { one = "{count} Textsegment ausgewählt", other = "{count} Textsegmente ausgewählt" } +three_d = { one = "{count} 3D-Segment ausgewählt", other = "{count} 3D-Segmente ausgewählt" } +zoom = { one = "{count} ausgewählt", other = "{count} ausgewählt" } +zoom_all = { one = "Alle {count} ausgewählt", other = "Alle {count} ausgewählt" } +zoom_some = { one = "{count} von {total} ausgewählt", other = "{count} von {total} ausgewählt" } diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index c4415d07108..bd5140ad241 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -588,3 +588,670 @@ click_or_press = "Click here, or press" get_started = "Get Started" space_key = "Space" tagline = "The open source screen sharing app for everyone." + +[editor.canvas] +camera = "Camera" +camera_follows_zoom = "Camera size follows the zoom — drag to move" +edit_zoom = "Edit zoom" +image = "Image" +mask = "Mask" +screen = "Screen" +screen_locked = "Screen is locked while a zoom is active" +text_item = "Text" + +[editor.clips] +append_error = { text = "Failed to add clip: {message}", context = "{message} explains why." } +append_error_title = "Recording Error" +back = "Back to editor" +cap_filter = { text = "Cap Recording", context = "File type filter name for .cap recordings in the file picker." } +clip = { text = "Clip {number}", context = "Default clip name; {number} starts at 1." } +empty_description = "Record or import a clip and it will show up here." +empty_title = "No clips yet" +import = "Import" +import_error = { text = "Failed to import clip: {message}", context = "{message} explains why." } +import_error_title = "Import Error" +import_existing = "Existing recording" +import_mp4 = "MP4 Video…" +importing = "Importing…" +mp4_filter = { text = "MP4 Video", context = "File type filter name in the file picker." } +prepare_controls_failed = "Could not prepare the recording controls: {error}" +record_new = "Record a new clip" +save_before_recording_failed = "Could not save the current project before recording: {error}" +split = { text = "Split {number}", context = "Default name of a later piece cut from the same clip; {number} starts at 1." } +title = "Clips" + +[editor.clips.error] +cancelled = "Import cancelled" +cannot_decode = "Cannot decode video {path}: {error}" +in_use = "Cannot import while this recording is in use: {error}" +instant_source = "Instant recordings can't be imported into a project yet" +instant_target = "Instant mode recordings cannot be edited" +into_itself = "Cannot import a recording into itself" +missing_video = "Missing video file: {path}" +no_duration = "Imported video has no valid duration" +not_a_project = "Select a Cap project folder" +source_empty = "Source Cap project has no recording segments" +source_failed = "Source Cap project failed: {error}" +source_recording = "Source Cap project is still recording" +source_unimportable = "Source Cap project has no importable recording segments" +unfinished_target = "Finish or recover this recording before importing a clip" +unsupported_video = "Unsupported video file: {path}" + +[editor.export] +back = "Back to editor" +existing_link_description = "Reupload replaces the video at this link with your latest edit. Everyone with the link will see the updated version." +existing_link_title = "Update your existing link" +failed = "Export failed" +fps = { text = "{fps} FPS", context = "Frame rate option, for example 30 FPS." } +frames = { one = "{rendered} / {count} frame", other = "{rendered} / {count} frames", context = "Export progress: {rendered} frames done out of {count}." } +generating_preview = "Generating preview…" +link_unavailable_cursor = "Cursor-only exports can only be saved to a file or clipboard." +link_unavailable_transparent = "Transparent exports can only be saved to a file or clipboard." +organization = "Organization" +preview = "Preview" +preview_info = "This is a rendered frame from your video. Adjust the settings to see the quality of the final export." +preview_unavailable = "Preview unavailable" +reupload = "Reupload" +reupload_tooltip = "Upload your latest edit to the same link" +reupload_transparent = "Share links require a background without transparency" +sign_in_required = "You need to sign in to share recordings" +task_failed = "Export task failed" +title = "Export" +upgrade_message = "This feature requires an upgraded plan" +upgrade_title = "Upgrade required" +upload_failed = "Failed to upload recording" + +[editor.export.advanced] +bits_per_pixel = "Bits per pixel" +cursor_only = "Export cursor only" +cursor_only_description = "Keeps the same cursor motion and clicks on a transparent background" +custom_bitrate = "Using a custom bitrate" +force_ffmpeg = "Force FFmpeg decoder" +force_ffmpeg_description = "Skip hardware decoder (auto-fallback enabled)" +title = "Advanced" +transparent_mov = "Exports as a transparent MOV. Files are large and best for compositing or editing." + +[editor.export.cta] +cancel_sign_in = "Cancel Sign In" +clipboard = "Export to Clipboard" +create_link = "Create shareable link" +file = "Export to File" +reupload = "Reupload to same link" +sign_in = "Sign in to share" + +[editor.export.destination] +clipboard = { text = "Clipboard", context = "Export destination." } +file = { text = "File", context = "Export destination." } +link = { text = "Shareable Link", context = "Export destination." } + +[editor.export.done] +copy_link = "Copy link" +instant_hint = "Use Instant Mode for your next recording if you want a link the moment you stop." +link_copied = "Link copied" +open_file = "Open File" +open_link = "Open link" +retry_copy = "Retry Copy" +reuploaded = "Your latest edit is ready at the same link" +uploaded = "Your Cap has been uploaded successfully" + +[editor.export.estimate] +gigabytes = "~{value} GB" +gigabytes_range = "~{lower}–{upper} GB" +hours = "~{value} hr" +hours_range = "~{lower}–{upper} hr" +megabytes = "~{value} MB" +megabytes_range = "~{lower}–{upper} MB" +minutes = "~{value} min" +minutes_range = "~{lower}–{upper} min" +seconds = "~{value} s" +seconds_range = "~{lower}–{upper} s" +under_megabyte = "< 1 MB" +under_second = "< 1s" + +[editor.export.optimize] +description = "Re-encodes with software for much smaller files (slower)" +title = "Optimize file size" + +[editor.export.phase] +clipboard_failed = "Export complete; clipboard copy failed" +complete = "Export complete" +copied = "Copied to clipboard" +copying_to_clipboard = "Copying to clipboard" +rendering_cursor = "Rendering cursor track" +rendering_gif = "Rendering GIF" +rendering_video = "Rendering video" +reupload_complete = "Reupload complete" +reuploading = "Reuploading to your link" +saving_to_file = "Saving to file" +starting = "Preparing export" +upload_complete = "Upload complete" +uploading = "Creating shareable link" + +[editor.export.quality] +larger = "Larger file" +maximum = { text = "Maximum", context = "Export quality preset." } +potato = { text = "Potato", context = "Export quality preset: the lowest quality, said playfully." } +smaller = "Smaller file" +social = { text = "Social", context = "Export quality preset for social media." } +web = { text = "Web", context = "Export quality preset for the web." } + +[editor.export.resources] +proceed = "Proceed anyway" +title = "Check export resources" + +[editor.export.section] +destination = "Destination" +format = "Format" +frame_rate = "Frame rate" +quality = "Quality" +resolution = "Resolution" + +[editor.export.stats] +calculating = "Calculating…" +duration = "Duration" +estimated_size = "Estimated size" +export_time = "Export time" +output = "Output" +output_value = { text = "{width}×{height} · {fps} fps", context = "Output size and frame rate, for example 1920×1080 · 30 fps." } +refining_size = "Refining size…" +refining_time = "Refining time…" +unavailable = "Unavailable" + +[editor.modal] +cancel_export = "Cancel export" +cancel_export_message = "Are you sure you want to cancel the export?" +cancel_export_title = "Cancel export?" +keep_exporting = "Keep exporting" +recording_retained = "Recording retained" + +[editor.tabs.audio] +isolation_balanced = "Balanced" +isolation_balanced_description = "Clearer isolation with natural voice detail." +isolation_light = "Light" +isolation_light_description = "Keep more of your original voice and room sound." +isolation_strong = "Strong" +isolation_strong_description = "More isolation for noisy spaces. May change voice texture." +mic_volume = "Microphone Volume" +mute = "Mute Audio" +stereo_mode = "Microphone Stereo Mode" +studio_sound_description = "Reduces background noise and balances your voice level." +studio_sound_save_failed = "Could not save the Studio Sound default" +system_volume = "System Audio Volume" +title = "Audio Controls" +use_for_new = "Use for new recordings" + +[editor.tabs.audio.stereo] +mono_left = "Mono L" +mono_right = "Mono R" +stereo = "Stereo" + +[editor.tabs.camera] +advanced_shadow = "Advanced shadow settings" +background = "Background" +corner_style = "Corner Style" +custom_position = "Custom position (dragged on canvas)" +hide = "Hide Camera" +keep_size_during_zoom = "Keep original size during zoom" +mirror = "Mirror Camera" +position = "Position" +reset = "Reset" +rounded_corners = "Rounded Corners" +shadow = "Shadow" +shadow_blur = "Blur" +shadow_opacity = "Opacity" +shape_label = "Shape" +size_during_zoom = "Size During Zoom" +size_label = "Size" +title = "Camera" + +[editor.tabs.camera.blur] +heavy = "Heavy Blur" +light = "Light Blur" +off = "Off" +remove = "Remove Background" + +[editor.tabs.camera.shape] +source = { text = "Source", context = "Camera shape that keeps the camera's own aspect ratio." } +square = "Square" + +[editor.tabs.captions] +active_word_highlight = "Active Word Highlight" +animation_section = "Animation" +animation_style = "Animation Style" +background_color = "Background Color" +background_opacity = "Background Opacity" +background_settings = "Background Settings" +beta = "Beta" +delete = "Delete" +deleting = "Deleting..." +download = "Download {model} Model" +download_note = "One time download to your system. All captions are stored locally." +downloading = "Downloading {model}... {percent}%" +fade_duration = "Fade Duration" +font_family = "Font Family" +font_settings = "Font Settings" +font_weight = "Font Weight" +generate = "Generate Captions" +generate_failed = "Failed to generate captions: {reason}" +generating = "Generating..." +highlight_color = "Highlight Color" +highlight_note = "This is the first version of captions in Cap. Active word highlighting may be inaccurate in some situations. We're working on a fix for this and it will be released in upcoming versions." +highlight_style = "Highlight Style" +horizontal_position = "Horizontal Position" +intel_unavailable = "Parakeet caption models are unavailable on Intel Macs. Whisper models remain available." +keep_open = "Keep Cap open while the model downloads. Editor reloads will reconnect automatically." +language_label = "Language" +model_downloaded = "{model} model downloaded" +model_label = "Model" +position = "Position" +regenerate = "Regenerate Captions" +size_label = "Size" +style = "Style" +text_color = "Text Color" +title = "Captions" +uppercase = "Uppercase" +vertical_position = "Vertical Position" + +[editor.tabs.captions.animation] +bounce = "Bounce" +none = "None" +pop = "Pop" + +[editor.tabs.captions.highlight] +color = "Color" +pill = "Pill" + +[editor.tabs.captions.language] +ar = "Arabic" +auto = "Auto Detect" +bn = "Bengali" +de = "German" +en = "English" +es = "Spanish" +fa = "Persian" +fr = "French" +gu = "Gujarati" +he = "Hebrew" +hi = "Hindi" +it = "Italian" +ja = "Japanese" +ko = "Korean" +mr = "Marathi" +nl = "Dutch" +pa = "Punjabi" +pl = "Polish" +pt = "Portuguese" +ru = "Russian" +sk = "Slovak" +ta = "Tamil" +te = "Telugu" +tr = "Turkish" +ur = "Urdu" +zh = "Chinese" + +[editor.tabs.captions.model] +high_accuracy = "High Accuracy" +high_accuracy_description = "Larger download, higher accuracy" +medium = { text = "Medium", context = "Caption model size." } +medium_description = "Slower, more accurate" +recommended = "Recommended" +recommended_description = "Best balance for most recordings" +small = { text = "Small", context = "Caption model size." } +small_description = "Smallest download" + +[editor.tabs.captions.preset] +classic = "Classic" +classic_description = "Clean text on a solid rounded background." +highlight = "Highlight" +highlight_description = "Bold caps with a pill behind the active word." +karaoke = "Karaoke" +karaoke_description = "Words light up in sync with speech." +minimal = "Minimal" +minimal_description = "Subtle outlined text with no background." +pop = "Pop" +pop_description = "Playful caps that pop in with a vibrant accent." + +[editor.tabs.corner] +rounded = "Rounded" +squircle = "Squircle" + +[editor.tabs.cursor] +friction = "Friction" +hide_when_idle = "Hide When Idle" +inactivity_delay = "Inactivity Delay" +mass = "Mass" +movement_style = "Cursor Movement Style" +show = "Show cursor" +size_label = "Size" +smooth_movement = "Smooth Movement" +style_label = "Cursor Style" +svg_cursors = "High Quality SVG Cursors" +tension = "Tension" +tilt = "Tilt" + +[editor.tabs.cursor.style] +fast = "Fast" +fast_description = "Snappy, close tracking" +mellow = "Mellow" +mellow_description = "Balanced, natural movement" +slow = "Slow" +slow_description = "Long, drifting movement" +smooth = "Smooth" +smooth_description = "Soft, floaty movement" + +[editor.tabs.keyboard] +animation = "Animation" +background_color = "Background Color" +background_opacity = "Background Opacity" +background_settings = "Background Settings" +behavior = "Behavior" +beta = "Beta" +changed_during_generation = "The timeline or keyboard settings changed during generation. Try again." +empty_description = "Click \"Generate Keyboard Segments\" to create segments from recorded keyboard presses." +empty_title = "No keyboard segments yet." +fade_duration = "Fade Duration" +font_family = "Font Family" +font_settings = "Font Settings" +font_weight = "Font Weight" +generate = "Generate Keyboard Segments" +generating = "Generating Keyboard Segments..." +grouping_threshold = "Grouping Threshold" +linger_duration = "Linger Duration" +load_failed = "Failed to load recording data: {error}" +no_presses = "No recorded keyboard presses were found." +position = "Position" +regenerate = "Regenerate Keyboard Segments" +show_modifiers = "Show Modifiers" +show_special_keys = "Show Special Keys" +size_label = "Size" +text_color = "Text Color" +timeline_unavailable = "The project timeline is unavailable." +timeline_unreadable = "The project timeline could not be read." +title = "Show keyboard" +uppercase = "Uppercase" + +[editor.tabs.position] +bottom_center = "Bottom Center" +bottom_left = "Bottom Left" +bottom_right = "Bottom Right" +manual = "Manual" +top_center = "Top Center" +top_left = "Top Left" +top_right = "Top Right" + +[editor.tabs.sync] +auto_description = "Cap calculated these offsets automatically to keep audio in sync with the video. Adjust them if anything still sounds off." +auto_synced = "Auto-synced" +camera = "Camera Offset" +clip = "Clip {number}" +description = "Fine-tune source offsets if audio or camera drifts out of sync with the screen recording." +microphone = "Microphone Offset" +system_audio = "System Audio Offset" +title = "Sync" + +[editor.tabs.weight] +bold = "Bold" +medium = "Medium" +normal = "Normal" + +[editor.timeline] +add_shot = "Add shot" +add_shot_with_look = { text = "Add shot · {look} · {seconds}s", context = "Ghost on the 3D track. {look} is the camera move name, {seconds} a length such as 2.5." } +paused = "Paused" + +[editor.timeline.empty] +add_audio = "Add audio" +add_scene = "Add scene" +audio = "Add background music or import your own audio" +captions = "No captions" +keyboard = "No keyboard events" +scene = "Switch layouts between your screen and camera" + +[editor.timeline.picker] +audio = "Add background music or import your own audio." +caption = "Auto-transcribe your recording into on-screen subtitles." +clip = "Your recorded screen footage." +image = "Add an image to your recording." +keyboard = "Display key presses on screen as you type." +mask = "Blur or black out private areas of the screen." +scene = "Switch layouts between your screen and camera." +scene_unavailable = "Record with a camera to use scenes." +style = "Change background, camera and cursor settings over time." +text_overlay = "Add custom text overlays and titles to the canvas." +three_d = "Tilt the scene in 3D perspective." +zoom = "Smooth zoom-ins that follow the action." + +[editor.timeline.remove] +audio = "Remove the Audio track." +captions = "Remove the Captions track." +clip = "Remove the Clip track." +image = "Remove the Image track." +keyboard = "Remove the Keyboard track." +mask = "Remove the Mask track." +scene = "Remove the Scene track." +style = "Remove the Style track." +text_track = "Remove the Text track." +three_d = "Remove the 3D track." +zoom = "Remove the Zoom track." + +[editor.timeline.scene] +camera_only = "Camera Only" +default = "Default" +floating = "Floating" +hide_camera = "Hide Camera" +split_screen = "Split Screen" + +[editor.timeline.segment] +audio = { text = "Audio", context = "Name of an audio segment that has none." } +automatic_zoom = "Automatic Zoom" +caption = { text = "Caption", context = "Caption segment with no text yet." } +clip = "Clip" +cursor_hidden = "Cursor hidden" +highlight = { text = "Highlight", context = "Mask type that dims everything but the area." } +label = { text = "Label", context = "Text segment with no text yet." } +manual_zoom = "Manual Zoom" +muted = "Muted" +numbered_clip = { text = "Clip {number}", context = "{number} is the recording clip number, starting at 1." } +sensitive = { text = "Sensitive", context = "Mask type that blurs a private area." } + +[editor.timeline.track] +audio = "Audio" +captions = "Captions" +clip = { text = "Clip", context = "Track picker name for the video track." } +image = "Image" +keyboard = "Keyboard" +mask = "Mask" +scene = "Scene" +style = "Style" +text_track = "Text" +video = { text = "Video", context = "Timeline gutter label of the recording's video track." } +zoom = "Zoom" + +[editor.window] +audio_filter = { text = "Audio", context = "File type filter name in the audio file picker." } +audio_segment_changed = "This audio segment changed. Select it and choose a replacement again." +crop = "Crop" +crop_tooltip = "Crop Video" +no_room_for_3d_shot = "No room for another 3D shot" +redo = "Redo" +save_failed = { text = "Could not save {path}. Keep the editor open and try again: {error}", context = "{path} is the recording's folder; {error} is a technical message that stays in English." } +undo = "Undo" + +[editor.window.add_track] +button = "Add track" +hide_3d = "Hide the 3D track." +hint = "Choose a track to add to your timeline." + +[editor.window.aspect] +auto = "Auto" +classic = "Classic" +classic_ratio = "Classic ⋅4:3" +square = "Square" +square_ratio = "Square ⋅1:1" +tall = "Tall" +tall_ratio = "Tall ⋅3:4" +tooltip = "Aspect Ratio" +vertical = "Vertical" +vertical_ratio = "Vertical ⋅9:16" +wide = "Wide" +wide_ratio = "Wide ⋅16:9" + +[editor.window.audio_import] +copy_failed = "Failed to copy audio file: {error}" +directory_failed = "Failed to create audio directory: {error}" +no_extension = "Audio file has no extension" +not_found = "Audio file not found: {path}" +unsupported = { text = "Unsupported audio format: .{extension}", context = "{extension} is a file extension such as ogg." } + +[editor.window.busy] +clip_import = "Wait for the clip import to finish before closing or deleting this recording." +export = "Wait for the export or its cancellation to finish before closing or deleting this recording." +opening = "Wait for the recording to finish opening before deleting it." +save_before_delete_failed = "Could not save the recording before deleting it: {error}" + +[editor.window.camera3d_lane] +add_shot = "+ Add shot" +auto_scene = "Auto scene" +how_many = "How many shots?" +prompt = "Add cinematic 3D shots to your recording" + +[editor.window.clip_menu] +audio = { text = "AUDIO", context = "Section heading in the clip menu, written in capitals." } +cursor = { text = "CURSOR", context = "Section heading in the clip menu, written in capitals." } +delete = "Delete clip" +delete_only_clip = "Can't delete the only clip" +hide_cursor = "Hide cursor" +hide_cursor_description = "Fades out while this clip plays" +keep_pitch = "Keep pitch" +match_speed = "Match speed" +merge_next = "Merge with next clip" +merge_previous = "Merge with previous clip" +mute = "Mute" +mute_clip = "Mute clip" +mute_clip_description = "Silence this clip's audio" +rename = "Rename clip" +speed = { text = "SPEED", context = "Section heading in the clip menu, written in capitals." } +split = "Split at playhead" +split_hint = "Move the playhead into this clip" +volume = "Volume" + +[editor.window.frame] +add_tooltip = "Add a frame" +browser = { text = "Browser", context = "Frame style: browser chrome around the recording." } +browser_description = "Browser chrome with address bar" +button = { text = "Frame", context = "Toolbar button that opens the frame picker." } +dark = "Dark" +global = "Global frame · Applies wherever Style inherits background." +heading = { text = "Frame", context = "Heading of the frame picker popover." } +light = "Light" +macbook_description = "Laptop bezel around the recording" +macos_description = "Window chrome with traffic lights" +none = { text = "None", context = "Frame style: no frame." } +none_description = "Show the recording as-is" +style_changed = "This Style changed. Close and reopen Frame." +style_only = { text = "Style {index} only · Global settings stay unchanged.", context = "{index} is the number of a style segment on the timeline, starting at 1." } +style_only_override = { text = "Style {index} only · Editing enables its background override.", context = "{index} is the number of a style segment on the timeline, starting at 1." } +theme = "Theme" +title = "Title" +url = "URL" +window_title_placeholder = "Window title" +windows_description = "Title bar with window controls" + +[editor.window.header] +captions = "Captions" +delete_recording = "Delete recording" +export = "Export" +open_bundle = "Open recording bundle" +presets = "Presets" + +[editor.window.image] +changed = "This image changed while choosing a replacement. Select it and try again." +filter = { text = "Images", context = "File type filter name in the image file picker." } +recording_unavailable = "The recording is no longer available for this image. Please reopen the project." + +[editor.window.open] +damaged = "This recording's video tracks could not be opened. The bundle looks damaged." +failed = "This recording could not be opened" +media_failed = "Failed to read this recording's media: {error}" +meta_failed = "Failed to load recording meta: {error}" +no_segments = "Recording has no segments. It may need to be recovered first." +not_found = "Video path {path} not found!" +not_studio = "Cannot edit non-studio recordings" + +[editor.window.presets] +apply = "Apply" +create = "Create" +create_new = "Create new preset" +create_title = "Create Preset" +default = { text = "Default", context = "Name of the built-in preset." } +default_badge = { text = "Default", context = "Badge on the preset used for new recordings." } +delete = "Delete" +delete_confirm = { text = "Are you sure you want to delete \"{name}\"?", context = "{name} is the preset's name." } +delete_title = "Delete Preset" +name_placeholder = "Preset name" +none_saved = "No saved presets" +rename = "Rename" +rename_title = "Rename Preset" +save_settings = "Save settings to preset" +set_default = "Set as default" + +[editor.window.preview] +full = { text = "Full", context = "Preview quality option." } +half = { text = "Half", context = "Preview quality option." } +label = { text = "Preview", context = "Label in front of the preview quality picker." } +quarter = { text = "Quarter", context = "Preview quality option." } + +[editor.window.text_menu] +copy_style = "Copy style" +copy_style_first = "Copy a style first" +delete_segment = "Delete text segment" +delete_segments = "Delete text segments" +paste_style = "Paste style" +paste_style_all = "Paste style to all selected" +segments = { one = "{count} text segment", other = "{count} text segments", context = "Title of the text menu; only shown for two or more." } +untitled = { text = "Text", context = "Title of the text menu for a segment with no text." } + +[editor.window.title] +not_saved = "Title not saved" +not_saved_message = { text = "{error}\n\nYour title is still in the editor. Try saving it again.", context = "{error} is the reason, shown above." } +read_failed = "Could not read the recording title for {path}: {error}" +save_failed = "Could not save the recording title for {path}: {error}" + +[editor.window.track] +clear_all = "Clear all" +delete = "Delete" + +[editor.window.transport] +done_splitting = "Done splitting" +play_pause = "Play/Pause video" +split = "Split" +split_tooltip = "Click a segment to split. Esc to finish." +zoom_in = "Zoom in" +zoom_out = "Zoom out" + +[editor.window.zoom_lane] +generate = "Generate" +generating = "Generating…" +no_clicks = "No clicks found to zoom into. Drag across the lane to add one." +prompt = "Add zooms automatically" + +[ui] +coming_soon = "Coming Soon" + +[ui.selection] +audio = { one = "{count} audio segment selected", other = "{count} audio segments selected" } +caption = { one = "{count} caption segment selected", other = "{count} caption segments selected" } +delete = "Delete" +done = "Done" +image = { one = "{count} image segment selected", other = "{count} image segments selected" } +keyboard = { one = "{count} keyboard segment selected", other = "{count} keyboard segments selected" } +mask = { one = "{count} mask segment selected", other = "{count} mask segments selected" } +scene = { one = "{count} scene segment selected", other = "{count} scene segments selected" } +segments = { one = "{count} segment selected", other = "{count} segments selected" } +select_all = "Select all" +style = { one = "{count} style segment selected", other = "{count} style segments selected" } +text_segments = { one = "{count} text segment selected", other = "{count} text segments selected" } +three_d = { one = "{count} 3D segment selected", other = "{count} 3D segments selected" } +zoom = { one = "{count} selected", other = "{count} selected", context = "Zoom segments selected when the track has only one." } +zoom_all = { one = "All {count} selected", other = "All {count} selected", context = "Every zoom segment on the track is selected; only shown for two or more." } +zoom_some = { one = "{count} of {total} selected", other = "{count} of {total} selected", context = "Some of the zoom segments on the track are selected." } diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index 00c4c9b74f7..c74e5d3a355 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -586,3 +586,670 @@ click_or_press = "Haz clic aquí o pulsa" get_started = "Empezar" space_key = "Espacio" tagline = "La app de código abierto para compartir pantalla, para todo el mundo." + +[editor.canvas] +camera = "Cámara" +camera_follows_zoom = "El tamaño de la cámara sigue al zoom; arrastra para moverla" +edit_zoom = "Editar zoom" +image = "Imagen" +mask = "Máscara" +screen = "Pantalla" +screen_locked = "La pantalla está bloqueada mientras haya un zoom activo" +text_item = "Texto" + +[editor.clips] +append_error = "No se pudo añadir el clip: {message}" +append_error_title = "Error de grabación" +back = "Volver al editor" +cap_filter = "Grabación de Cap" +clip = "Clip {number}" +empty_description = "Graba o importa un clip y aparecerá aquí." +empty_title = "Aún no hay clips" +import = "Importar" +import_error = "No se pudo importar el clip: {message}" +import_error_title = "Error de importación" +import_existing = "Grabación existente" +import_mp4 = "Vídeo MP4…" +importing = "Importando…" +mp4_filter = "Vídeo MP4" +prepare_controls_failed = "No se pudieron preparar los controles de grabación: {error}" +record_new = "Grabar un clip nuevo" +save_before_recording_failed = "No se pudo guardar el proyecto actual antes de grabar: {error}" +split = "Parte {number}" +title = "Clips" + +[editor.clips.error] +cancelled = "Importación cancelada" +cannot_decode = "No se puede decodificar el vídeo {path}: {error}" +in_use = "No se puede importar mientras esta grabación está en uso: {error}" +instant_source = "Las grabaciones Instant aún no se pueden importar a un proyecto" +instant_target = "Las grabaciones en modo Instant no se pueden editar" +into_itself = "No se puede importar una grabación en sí misma" +missing_video = "Falta el archivo de vídeo: {path}" +no_duration = "El vídeo importado no tiene una duración válida" +not_a_project = "Selecciona una carpeta de proyecto de Cap" +source_empty = "El proyecto de Cap de origen no tiene segmentos de grabación" +source_failed = "El proyecto de Cap de origen falló: {error}" +source_recording = "El proyecto de Cap de origen todavía se está grabando" +source_unimportable = "El proyecto de Cap de origen no tiene segmentos de grabación importables" +unfinished_target = "Termina o recupera esta grabación antes de importar un clip" +unsupported_video = "Archivo de vídeo no compatible: {path}" + +[editor.export] +back = "Volver al editor" +existing_link_description = "Volver a subir reemplaza el vídeo de este enlace con tu última edición. Todas las personas con el enlace verán la versión actualizada." +existing_link_title = "Actualiza tu enlace existente" +failed = "Error al exportar" +fps = "{fps} FPS" +frames = { one = "{rendered} / {count} fotograma", other = "{rendered} / {count} fotogramas" } +generating_preview = "Generando vista previa…" +link_unavailable_cursor = "Las exportaciones solo del cursor solo se pueden guardar en un archivo o en el portapapeles." +link_unavailable_transparent = "Las exportaciones transparentes solo se pueden guardar en un archivo o en el portapapeles." +organization = "Organización" +preview = "Vista previa" +preview_info = "Este es un fotograma renderizado de tu vídeo. Ajusta la configuración para ver la calidad de la exportación final." +preview_unavailable = "Vista previa no disponible" +reupload = "Volver a subir" +reupload_tooltip = "Sube tu última edición al mismo enlace" +reupload_transparent = "Los enlaces para compartir requieren un fondo sin transparencia" +sign_in_required = "Tienes que iniciar sesión para compartir grabaciones" +task_failed = "Falló la tarea de exportación" +title = "Exportar" +upgrade_message = "Esta función requiere un plan superior" +upgrade_title = "Se requiere una mejora" +upload_failed = "No se pudo subir la grabación" + +[editor.export.advanced] +bits_per_pixel = "Bits por píxel" +cursor_only = "Exportar solo el cursor" +cursor_only_description = "Mantiene el mismo movimiento y clics del cursor sobre un fondo transparente" +custom_bitrate = "Usando una tasa de bits personalizada" +force_ffmpeg = "Forzar el decodificador FFmpeg" +force_ffmpeg_description = "Omitir el decodificador por hardware (respaldo automático activado)" +title = "Avanzado" +transparent_mov = "Exporta como MOV transparente. Los archivos son grandes y sirven mejor para composición o edición." + +[editor.export.cta] +cancel_sign_in = "Cancelar inicio de sesión" +clipboard = "Exportar al portapapeles" +create_link = "Crear enlace para compartir" +file = "Exportar a archivo" +reupload = "Volver a subir al mismo enlace" +sign_in = "Inicia sesión para compartir" + +[editor.export.destination] +clipboard = "Portapapeles" +file = "Archivo" +link = "Enlace para compartir" + +[editor.export.done] +copy_link = "Copiar enlace" +instant_hint = "Usa el modo Instant en tu próxima grabación si quieres un enlace en cuanto te detengas." +link_copied = "Enlace copiado" +open_file = "Abrir archivo" +open_link = "Abrir enlace" +retry_copy = "Reintentar copia" +reuploaded = "Tu última edición está lista en el mismo enlace" +uploaded = "Tu Cap se subió correctamente" + +[editor.export.estimate] +gigabytes = "~{value} GB" +gigabytes_range = "~{lower}–{upper} GB" +hours = "~{value} h" +hours_range = "~{lower}–{upper} h" +megabytes = "~{value} MB" +megabytes_range = "~{lower}–{upper} MB" +minutes = "~{value} min" +minutes_range = "~{lower}–{upper} min" +seconds = "~{value} s" +seconds_range = "~{lower}–{upper} s" +under_megabyte = "< 1 MB" +under_second = "< 1 s" + +[editor.export.optimize] +description = "Vuelve a codificar por software para archivos mucho más pequeños (más lento)" +title = "Optimizar tamaño de archivo" + +[editor.export.phase] +clipboard_failed = "Exportación completada; no se pudo copiar al portapapeles" +complete = "Exportación completada" +copied = "Copiado al portapapeles" +copying_to_clipboard = "Copiando al portapapeles" +rendering_cursor = "Renderizando la pista del cursor" +rendering_gif = "Renderizando GIF" +rendering_video = "Renderizando vídeo" +reupload_complete = "Nueva subida completada" +reuploading = "Volviendo a subir a tu enlace" +saving_to_file = "Guardando en archivo" +starting = "Preparando la exportación" +upload_complete = "Subida completada" +uploading = "Creando enlace para compartir" + +[editor.export.quality] +larger = "Archivo más grande" +maximum = "Máxima" +potato = "Patata" +smaller = "Archivo más pequeño" +social = "Redes" +web = "Web" + +[editor.export.resources] +proceed = "Continuar de todos modos" +title = "Comprobar recursos de exportación" + +[editor.export.section] +destination = "Destino" +format = "Formato" +frame_rate = "Velocidad de fotogramas" +quality = "Calidad" +resolution = "Resolución" + +[editor.export.stats] +calculating = "Calculando…" +duration = "Duración" +estimated_size = "Tamaño estimado" +export_time = "Tiempo de exportación" +output = "Salida" +output_value = "{width}×{height} · {fps} fps" +refining_size = "Ajustando tamaño…" +refining_time = "Ajustando tiempo…" +unavailable = "No disponible" + +[editor.modal] +cancel_export = "Cancelar exportación" +cancel_export_message = "¿Seguro que quieres cancelar la exportación?" +cancel_export_title = "¿Cancelar exportación?" +keep_exporting = "Seguir exportando" +recording_retained = "Grabación conservada" + +[editor.tabs.audio] +isolation_balanced = "Equilibrado" +isolation_balanced_description = "Aislamiento más claro con detalle natural de la voz." +isolation_light = "Suave" +isolation_light_description = "Conserva más de tu voz original y del sonido de la sala." +isolation_strong = "Fuerte" +isolation_strong_description = "Más aislamiento para lugares ruidosos. Puede cambiar la textura de la voz." +mic_volume = "Volumen del micrófono" +mute = "Silenciar audio" +stereo_mode = "Modo estéreo del micrófono" +studio_sound_description = "Reduce el ruido de fondo y equilibra el nivel de tu voz." +studio_sound_save_failed = "No se pudo guardar el ajuste predeterminado de Studio Sound" +system_volume = "Volumen del audio del sistema" +title = "Controles de audio" +use_for_new = "Usar en grabaciones nuevas" + +[editor.tabs.audio.stereo] +mono_left = "Mono I" +mono_right = "Mono D" +stereo = "Estéreo" + +[editor.tabs.camera] +advanced_shadow = "Ajustes avanzados de sombra" +background = "Fondo" +corner_style = "Estilo de esquinas" +custom_position = "Posición personalizada (arrastrada en el lienzo)" +hide = "Ocultar cámara" +keep_size_during_zoom = "Mantener el tamaño original durante el zoom" +mirror = "Reflejar cámara" +position = "Posición" +reset = "Restablecer" +rounded_corners = "Esquinas redondeadas" +shadow = "Sombra" +shadow_blur = "Desenfoque" +shadow_opacity = "Opacidad" +shape_label = "Forma" +size_during_zoom = "Tamaño durante el zoom" +size_label = "Tamaño" +title = "Cámara" + +[editor.tabs.camera.blur] +heavy = "Desenfoque intenso" +light = "Desenfoque suave" +off = "Desactivado" +remove = "Quitar fondo" + +[editor.tabs.camera.shape] +source = "Original" +square = "Cuadrada" + +[editor.tabs.captions] +active_word_highlight = "Resaltar palabra activa" +animation_section = "Animación" +animation_style = "Estilo de animación" +background_color = "Color de fondo" +background_opacity = "Opacidad del fondo" +background_settings = "Ajustes de fondo" +beta = "Beta" +delete = "Eliminar" +deleting = "Eliminando..." +download = "Descargar modelo {model}" +download_note = "Descarga única en tu sistema. Todos los subtítulos se guardan localmente." +downloading = "Descargando {model}... {percent}%" +fade_duration = "Duración del fundido" +font_family = "Fuente" +font_settings = "Ajustes de fuente" +font_weight = "Grosor de fuente" +generate = "Generar subtítulos" +generate_failed = "No se pudieron generar los subtítulos: {reason}" +generating = "Generando..." +highlight_color = "Color de resaltado" +highlight_note = "Esta es la primera versión de los subtítulos en Cap. El resaltado de la palabra activa puede ser impreciso en algunas situaciones. Estamos trabajando en una solución que llegará en próximas versiones." +highlight_style = "Estilo de resaltado" +horizontal_position = "Posición horizontal" +intel_unavailable = "Los modelos de subtítulos Parakeet no están disponibles en Mac con Intel. Los modelos Whisper siguen disponibles." +keep_open = "Mantén Cap abierto mientras se descarga el modelo. Si el editor se recarga, se volverá a conectar automáticamente." +language_label = "Idioma" +model_downloaded = "Modelo {model} descargado" +model_label = "Modelo" +position = "Posición" +regenerate = "Regenerar subtítulos" +size_label = "Tamaño" +style = "Estilo" +text_color = "Color del texto" +title = "Subtítulos" +uppercase = "Mayúsculas" +vertical_position = "Posición vertical" + +[editor.tabs.captions.animation] +bounce = "Rebote" +none = "Ninguna" +pop = "Pop" + +[editor.tabs.captions.highlight] +color = "Color" +pill = "Píldora" + +[editor.tabs.captions.language] +ar = "Árabe" +auto = "Detección automática" +bn = "Bengalí" +de = "Alemán" +en = "Inglés" +es = "Español" +fa = "Persa" +fr = "Francés" +gu = "Guyaratí" +he = "Hebreo" +hi = "Hindi" +it = "Italiano" +ja = "Japonés" +ko = "Coreano" +mr = "Maratí" +nl = "Neerlandés" +pa = "Panyabí" +pl = "Polaco" +pt = "Portugués" +ru = "Ruso" +sk = "Eslovaco" +ta = "Tamil" +te = "Telugu" +tr = "Turco" +ur = "Urdu" +zh = "Chino" + +[editor.tabs.captions.model] +high_accuracy = "Alta precisión" +high_accuracy_description = "Descarga más grande, mayor precisión" +medium = "Mediano" +medium_description = "Más lento, más preciso" +recommended = "Recomendado" +recommended_description = "El mejor equilibrio para la mayoría de las grabaciones" +small = "Pequeño" +small_description = "La descarga más pequeña" + +[editor.tabs.captions.preset] +classic = "Clásico" +classic_description = "Texto limpio sobre un fondo sólido redondeado." +highlight = "Resaltado" +highlight_description = "Mayúsculas en negrita con una píldora detrás de la palabra activa." +karaoke = "Karaoke" +karaoke_description = "Las palabras se iluminan al ritmo de la voz." +minimal = "Mínimo" +minimal_description = "Texto sutil con contorno y sin fondo." +pop = "Pop" +pop_description = "Mayúsculas divertidas que aparecen con un acento vibrante." + +[editor.tabs.corner] +rounded = "Redondeadas" +squircle = "Squircle" + +[editor.tabs.cursor] +friction = "Fricción" +hide_when_idle = "Ocultar en reposo" +inactivity_delay = "Retraso de inactividad" +mass = "Masa" +movement_style = "Estilo de movimiento del cursor" +show = "Mostrar cursor" +size_label = "Tamaño" +smooth_movement = "Movimiento suave" +style_label = "Estilo del cursor" +svg_cursors = "Cursores SVG de alta calidad" +tension = "Tensión" +tilt = "Inclinación" + +[editor.tabs.cursor.style] +fast = "Rápido" +fast_description = "Ágil, seguimiento cercano" +mellow = "Tranquilo" +mellow_description = "Movimiento equilibrado y natural" +slow = "Lento" +slow_description = "Movimiento largo y deslizante" +smooth = "Suave" +smooth_description = "Movimiento suave y flotante" + +[editor.tabs.keyboard] +animation = "Animación" +background_color = "Color de fondo" +background_opacity = "Opacidad del fondo" +background_settings = "Ajustes de fondo" +behavior = "Comportamiento" +beta = "Beta" +changed_during_generation = "La línea de tiempo o los ajustes del teclado cambiaron durante la generación. Inténtalo de nuevo." +empty_description = "Haz clic en «Generar segmentos de teclado» para crear segmentos a partir de las pulsaciones grabadas." +empty_title = "Aún no hay segmentos de teclado." +fade_duration = "Duración del fundido" +font_family = "Fuente" +font_settings = "Ajustes de fuente" +font_weight = "Grosor de fuente" +generate = "Generar segmentos de teclado" +generating = "Generando segmentos de teclado..." +grouping_threshold = "Umbral de agrupación" +linger_duration = "Tiempo en pantalla" +load_failed = "No se pudieron cargar los datos de la grabación: {error}" +no_presses = "No se encontraron pulsaciones de teclado grabadas." +position = "Posición" +regenerate = "Regenerar segmentos de teclado" +show_modifiers = "Mostrar modificadores" +show_special_keys = "Mostrar teclas especiales" +size_label = "Tamaño" +text_color = "Color del texto" +timeline_unavailable = "La línea de tiempo del proyecto no está disponible." +timeline_unreadable = "No se pudo leer la línea de tiempo del proyecto." +title = "Mostrar teclado" +uppercase = "Mayúsculas" + +[editor.tabs.position] +bottom_center = "Abajo al centro" +bottom_left = "Abajo a la izquierda" +bottom_right = "Abajo a la derecha" +manual = "Manual" +top_center = "Arriba al centro" +top_left = "Arriba a la izquierda" +top_right = "Arriba a la derecha" + +[editor.tabs.sync] +auto_description = "Cap calculó estos desfases automáticamente para mantener el audio sincronizado con el vídeo. Ajústalos si algo aún no suena bien." +auto_synced = "Sincronizado automáticamente" +camera = "Desfase de la cámara" +clip = "Clip {number}" +description = "Ajusta los desfases de las fuentes si el audio o la cámara se desincronizan de la grabación de pantalla." +microphone = "Desfase del micrófono" +system_audio = "Desfase del audio del sistema" +title = "Sincronización" + +[editor.tabs.weight] +bold = "Negrita" +medium = "Media" +normal = "Normal" + +[editor.timeline] +add_shot = "Añadir plano" +add_shot_with_look = "Añadir plano · {look} · {seconds} s" +paused = "En pausa" + +[editor.timeline.empty] +add_audio = "Añadir audio" +add_scene = "Añadir escena" +audio = "Añade música de fondo o importa tu propio audio" +captions = "Sin subtítulos" +keyboard = "Sin eventos de teclado" +scene = "Cambia de diseño entre tu pantalla y tu cámara" + +[editor.timeline.picker] +audio = "Añade música de fondo o importa tu propio audio." +caption = "Transcribe automáticamente tu grabación en subtítulos en pantalla." +clip = "Las imágenes grabadas de tu pantalla." +image = "Añade una imagen a tu grabación." +keyboard = "Muestra en pantalla las teclas que pulsas mientras escribes." +mask = "Desenfoca u oculta en negro las zonas privadas de la pantalla." +scene = "Cambia de diseño entre tu pantalla y tu cámara." +scene_unavailable = "Graba con una cámara para usar escenas." +style = "Cambia el fondo, la cámara y el cursor a lo largo del tiempo." +text_overlay = "Añade textos y títulos personalizados sobre el lienzo." +three_d = "Inclina la escena en perspectiva 3D." +zoom = "Zooms suaves que siguen la acción." + +[editor.timeline.remove] +audio = "Quitar la pista Audio." +captions = "Quitar la pista Subtítulos." +clip = "Quitar la pista Clip." +image = "Quitar la pista Imagen." +keyboard = "Quitar la pista Teclado." +mask = "Quitar la pista Máscara." +scene = "Quitar la pista Escena." +style = "Quitar la pista Estilo." +text_track = "Quitar la pista Texto." +three_d = "Quitar la pista 3D." +zoom = "Quitar la pista Zoom." + +[editor.timeline.scene] +camera_only = "Solo cámara" +default = "Predeterminada" +floating = "Flotante" +hide_camera = "Ocultar cámara" +split_screen = "Pantalla dividida" + +[editor.timeline.segment] +audio = "Audio" +automatic_zoom = "Zoom automático" +caption = "Subtítulo" +clip = "Clip" +cursor_hidden = "Cursor oculto" +highlight = "Destacar" +label = "Etiqueta" +manual_zoom = "Zoom manual" +muted = "Silenciado" +numbered_clip = "Clip {number}" +sensitive = "Sensible" + +[editor.timeline.track] +audio = "Audio" +captions = "Subtítulos" +clip = "Clip" +image = "Imagen" +keyboard = "Teclado" +mask = "Máscara" +scene = "Escena" +style = "Estilo" +text_track = "Texto" +video = "Vídeo" +zoom = "Zoom" + +[editor.window] +audio_filter = "Audio" +audio_segment_changed = "Este segmento de audio cambió. Selecciónalo y vuelve a elegir un reemplazo." +crop = "Recortar" +crop_tooltip = "Recortar vídeo" +no_room_for_3d_shot = "No hay espacio para otro plano 3D" +redo = "Rehacer" +save_failed = "No se pudo guardar {path}. Mantén el editor abierto e inténtalo de nuevo: {error}" +undo = "Deshacer" + +[editor.window.add_track] +button = "Añadir pista" +hide_3d = "Ocultar la pista 3D." +hint = "Elige una pista para añadir a tu línea de tiempo." + +[editor.window.aspect] +auto = "Auto" +classic = "Clásico" +classic_ratio = "Clásico ⋅4:3" +square = "Cuadrado" +square_ratio = "Cuadrado ⋅1:1" +tall = "Alto" +tall_ratio = "Alto ⋅3:4" +tooltip = "Relación de aspecto" +vertical = "Vertical" +vertical_ratio = "Vertical ⋅9:16" +wide = "Panorámico" +wide_ratio = "Panorámico ⋅16:9" + +[editor.window.audio_import] +copy_failed = "No se pudo copiar el archivo de audio: {error}" +directory_failed = "No se pudo crear la carpeta de audio: {error}" +no_extension = "El archivo de audio no tiene extensión" +not_found = "No se encontró el archivo de audio: {path}" +unsupported = "Formato de audio no compatible: .{extension}" + +[editor.window.busy] +clip_import = "Espera a que termine la importación del clip antes de cerrar o eliminar esta grabación." +export = "Espera a que termine la exportación o su cancelación antes de cerrar o eliminar esta grabación." +opening = "Espera a que la grabación termine de abrirse antes de eliminarla." +save_before_delete_failed = "No se pudo guardar la grabación antes de eliminarla: {error}" + +[editor.window.camera3d_lane] +add_shot = "+ Añadir plano" +auto_scene = "Escena automática" +how_many = "¿Cuántos planos?" +prompt = "Añade planos 3D cinematográficos a tu grabación" + +[editor.window.clip_menu] +audio = "AUDIO" +cursor = "CURSOR" +delete = "Eliminar clip" +delete_only_clip = "No se puede eliminar el único clip" +hide_cursor = "Ocultar cursor" +hide_cursor_description = "Se desvanece mientras se reproduce este clip" +keep_pitch = "Mantener tono" +match_speed = "Ajustar a la velocidad" +merge_next = "Unir con el clip siguiente" +merge_previous = "Unir con el clip anterior" +mute = "Silenciar" +mute_clip = "Silenciar clip" +mute_clip_description = "Silencia el audio de este clip" +rename = "Renombrar clip" +speed = "VELOCIDAD" +split = "Dividir en el cabezal" +split_hint = "Mueve el cabezal dentro de este clip" +volume = "Volumen" + +[editor.window.frame] +add_tooltip = "Añadir un marco" +browser = "Navegador" +browser_description = "Ventana de navegador con barra de direcciones" +button = "Marco" +dark = "Oscuro" +global = "Marco global · Se aplica donde el estilo hereda el fondo." +heading = "Marco" +light = "Claro" +macbook_description = "Bisel de portátil alrededor de la grabación" +macos_description = "Ventana con botones de semáforo" +none = "Ninguno" +none_description = "Mostrar la grabación tal cual" +style_changed = "Este estilo cambió. Cierra y vuelve a abrir el marco." +style_only = "Solo el estilo {index} · Los ajustes globales no cambian." +style_only_override = "Solo el estilo {index} · Al editar se activa su fondo propio." +theme = "Tema" +title = "Título" +url = "URL" +window_title_placeholder = "Título de la ventana" +windows_description = "Barra de título con controles de ventana" + +[editor.window.header] +captions = "Subtítulos" +delete_recording = "Eliminar grabación" +export = "Exportar" +open_bundle = "Abrir paquete de la grabación" +presets = "Preajustes" + +[editor.window.image] +changed = "Esta imagen cambió mientras elegías un reemplazo. Selecciónala e inténtalo de nuevo." +filter = "Imágenes" +recording_unavailable = "La grabación ya no está disponible para esta imagen. Vuelve a abrir el proyecto." + +[editor.window.open] +damaged = "No se pudieron abrir las pistas de vídeo de esta grabación. El paquete parece dañado." +failed = "No se pudo abrir esta grabación" +media_failed = "No se pudieron leer los archivos de esta grabación: {error}" +meta_failed = "No se pudieron cargar los metadatos de la grabación: {error}" +no_segments = "La grabación no tiene segmentos. Puede que primero haya que recuperarla." +not_found = "¡No se encontró la ruta de vídeo {path}!" +not_studio = "Solo se pueden editar grabaciones de Studio" + +[editor.window.presets] +apply = "Aplicar" +create = "Crear" +create_new = "Crear preajuste nuevo" +create_title = "Crear preajuste" +default = "Predeterminado" +default_badge = "Predeterminado" +delete = "Eliminar" +delete_confirm = "¿Seguro que quieres eliminar «{name}»?" +delete_title = "Eliminar preajuste" +name_placeholder = "Nombre del preajuste" +none_saved = "No hay preajustes guardados" +rename = "Renombrar" +rename_title = "Renombrar preajuste" +save_settings = "Guardar ajustes en el preajuste" +set_default = "Establecer como predeterminado" + +[editor.window.preview] +full = "Completa" +half = "Mitad" +label = "Vista previa" +quarter = "Cuarto" + +[editor.window.text_menu] +copy_style = "Copiar estilo" +copy_style_first = "Copia primero un estilo" +delete_segment = "Eliminar segmento de texto" +delete_segments = "Eliminar segmentos de texto" +paste_style = "Pegar estilo" +paste_style_all = "Pegar estilo en toda la selección" +segments = { one = "{count} segmento de texto", other = "{count} segmentos de texto" } +untitled = "Texto" + +[editor.window.title] +not_saved = "Título no guardado" +not_saved_message = "{error}\n\nTu título sigue en el editor. Intenta guardarlo de nuevo." +read_failed = "No se pudo leer el título de la grabación de {path}: {error}" +save_failed = "No se pudo guardar el título de la grabación de {path}: {error}" + +[editor.window.track] +clear_all = "Borrar todo" +delete = "Eliminar" + +[editor.window.transport] +done_splitting = "Terminar de dividir" +play_pause = "Reproducir/pausar vídeo" +split = "Dividir" +split_tooltip = "Haz clic en un segmento para dividirlo. Esc para terminar." +zoom_in = "Acercar" +zoom_out = "Alejar" + +[editor.window.zoom_lane] +generate = "Generar" +generating = "Generando…" +no_clicks = "No se encontraron clics para hacer zoom. Arrastra sobre la pista para añadir uno." +prompt = "Añadir zooms automáticamente" + +[ui] +coming_soon = "Próximamente" + +[ui.selection] +audio = { one = "{count} segmento de audio seleccionado", other = "{count} segmentos de audio seleccionados" } +caption = { one = "{count} segmento de subtítulos seleccionado", other = "{count} segmentos de subtítulos seleccionados" } +delete = "Eliminar" +done = "Listo" +image = { one = "{count} segmento de imagen seleccionado", other = "{count} segmentos de imagen seleccionados" } +keyboard = { one = "{count} segmento de teclado seleccionado", other = "{count} segmentos de teclado seleccionados" } +mask = { one = "{count} segmento de máscara seleccionado", other = "{count} segmentos de máscara seleccionados" } +scene = { one = "{count} segmento de escena seleccionado", other = "{count} segmentos de escena seleccionados" } +segments = { one = "{count} segmento seleccionado", other = "{count} segmentos seleccionados" } +select_all = "Seleccionar todo" +style = { one = "{count} segmento de estilo seleccionado", other = "{count} segmentos de estilo seleccionados" } +text_segments = { one = "{count} segmento de texto seleccionado", other = "{count} segmentos de texto seleccionados" } +three_d = { one = "{count} segmento 3D seleccionado", other = "{count} segmentos 3D seleccionados" } +zoom = { one = "{count} seleccionado", other = "{count} seleccionados" } +zoom_all = { one = "{count} seleccionado", other = "Los {count} seleccionados" } +zoom_some = { one = "{count} de {total} seleccionado", other = "{count} de {total} seleccionados" } diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index e70141f4c4e..b6a44c71595 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -586,3 +586,670 @@ click_or_press = "Cliquez ici ou appuyez sur" get_started = "Commencer" space_key = "Espace" tagline = "L'app open source de partage d'écran pour tout le monde." + +[editor.canvas] +camera = "Caméra" +camera_follows_zoom = "La taille de la caméra suit le zoom ; faites glisser pour la déplacer" +edit_zoom = "Modifier le zoom" +image = "Image" +mask = "Masque" +screen = "Écran" +screen_locked = "L'écran est verrouillé tant qu'un zoom est actif" +text_item = "Texte" + +[editor.clips] +append_error = "Impossible d'ajouter le clip : {message}" +append_error_title = "Erreur d'enregistrement" +back = "Retour à l'éditeur" +cap_filter = "Enregistrement Cap" +clip = "Clip {number}" +empty_description = "Enregistrez ou importez un clip et il apparaîtra ici." +empty_title = "Aucun clip pour l'instant" +import = "Importer" +import_error = "Impossible d'importer le clip : {message}" +import_error_title = "Erreur d'importation" +import_existing = "Enregistrement existant" +import_mp4 = "Vidéo MP4…" +importing = "Importation…" +mp4_filter = "Vidéo MP4" +prepare_controls_failed = "Impossible de préparer les commandes d'enregistrement : {error}" +record_new = "Enregistrer un nouveau clip" +save_before_recording_failed = "Impossible d'enregistrer le projet actuel avant l'enregistrement : {error}" +split = "Partie {number}" +title = "Clips" + +[editor.clips.error] +cancelled = "Importation annulée" +cannot_decode = "Impossible de décoder la vidéo {path} : {error}" +in_use = "Importation impossible tant que cet enregistrement est utilisé : {error}" +instant_source = "Les enregistrements Instant ne peuvent pas encore être importés dans un projet" +instant_target = "Les enregistrements en mode Instant ne peuvent pas être modifiés" +into_itself = "Impossible d'importer un enregistrement dans lui-même" +missing_video = "Fichier vidéo manquant : {path}" +no_duration = "La vidéo importée n'a pas de durée valide" +not_a_project = "Sélectionnez un dossier de projet Cap" +source_empty = "Le projet Cap source n'a aucun segment d'enregistrement" +source_failed = "Le projet Cap source a échoué : {error}" +source_recording = "Le projet Cap source est encore en cours d'enregistrement" +source_unimportable = "Le projet Cap source n'a aucun segment d'enregistrement importable" +unfinished_target = "Terminez ou récupérez cet enregistrement avant d'importer un clip" +unsupported_video = "Fichier vidéo non pris en charge : {path}" + +[editor.export] +back = "Retour à l'éditeur" +existing_link_description = "Une nouvelle mise en ligne remplace la vidéo de ce lien par votre dernière modification. Toutes les personnes disposant du lien verront la version mise à jour." +existing_link_title = "Mettre à jour votre lien existant" +failed = "Échec de l'exportation" +fps = "{fps} i/s" +frames = { one = "{rendered} / {count} image", other = "{rendered} / {count} images" } +generating_preview = "Génération de l'aperçu…" +link_unavailable_cursor = "Les exportations du curseur seul ne peuvent être enregistrées que dans un fichier ou le presse-papiers." +link_unavailable_transparent = "Les exportations transparentes ne peuvent être enregistrées que dans un fichier ou le presse-papiers." +organization = "Organisation" +preview = "Aperçu" +preview_info = "Ceci est une image rendue de votre vidéo. Ajustez les paramètres pour voir la qualité de l'exportation finale." +preview_unavailable = "Aperçu indisponible" +reupload = "Remettre en ligne" +reupload_tooltip = "Mettre en ligne votre dernière modification sur le même lien" +reupload_transparent = "Les liens de partage nécessitent un arrière-plan sans transparence" +sign_in_required = "Vous devez vous connecter pour partager des enregistrements" +task_failed = "Échec de la tâche d'exportation" +title = "Exporter" +upgrade_message = "Cette fonctionnalité nécessite une formule supérieure" +upgrade_title = "Mise à niveau requise" +upload_failed = "Impossible de mettre en ligne l'enregistrement" + +[editor.export.advanced] +bits_per_pixel = "Bits par pixel" +cursor_only = "Exporter le curseur seul" +cursor_only_description = "Conserve les mêmes mouvements et clics du curseur sur un arrière-plan transparent" +custom_bitrate = "Débit personnalisé utilisé" +force_ffmpeg = "Forcer le décodeur FFmpeg" +force_ffmpeg_description = "Ignorer le décodeur matériel (repli automatique activé)" +title = "Avancé" +transparent_mov = "Exporte en MOV transparent. Les fichiers sont volumineux et conviennent surtout à la composition ou au montage." + +[editor.export.cta] +cancel_sign_in = "Annuler la connexion" +clipboard = "Exporter vers le presse-papiers" +create_link = "Créer un lien de partage" +file = "Exporter vers un fichier" +reupload = "Remettre en ligne sur le même lien" +sign_in = "Se connecter pour partager" + +[editor.export.destination] +clipboard = "Presse-papiers" +file = "Fichier" +link = "Lien de partage" + +[editor.export.done] +copy_link = "Copier le lien" +instant_hint = "Utilisez le mode Instant pour votre prochain enregistrement si vous voulez un lien dès que vous arrêtez." +link_copied = "Lien copié" +open_file = "Ouvrir le fichier" +open_link = "Ouvrir le lien" +retry_copy = "Réessayer la copie" +reuploaded = "Votre dernière modification est disponible sur le même lien" +uploaded = "Votre Cap a bien été mis en ligne" + +[editor.export.estimate] +gigabytes = "~{value} Go" +gigabytes_range = "~{lower}–{upper} Go" +hours = "~{value} h" +hours_range = "~{lower}–{upper} h" +megabytes = "~{value} Mo" +megabytes_range = "~{lower}–{upper} Mo" +minutes = "~{value} min" +minutes_range = "~{lower}–{upper} min" +seconds = "~{value} s" +seconds_range = "~{lower}–{upper} s" +under_megabyte = "< 1 Mo" +under_second = "< 1 s" + +[editor.export.optimize] +description = "Réencode par logiciel pour des fichiers bien plus petits (plus lent)" +title = "Optimiser la taille du fichier" + +[editor.export.phase] +clipboard_failed = "Exportation terminée ; échec de la copie dans le presse-papiers" +complete = "Exportation terminée" +copied = "Copié dans le presse-papiers" +copying_to_clipboard = "Copie dans le presse-papiers" +rendering_cursor = "Rendu de la piste du curseur" +rendering_gif = "Rendu du GIF" +rendering_video = "Rendu de la vidéo" +reupload_complete = "Nouvelle mise en ligne terminée" +reuploading = "Nouvelle mise en ligne sur votre lien" +saving_to_file = "Enregistrement dans un fichier" +starting = "Préparation de l'exportation" +upload_complete = "Mise en ligne terminée" +uploading = "Création du lien de partage" + +[editor.export.quality] +larger = "Fichier plus lourd" +maximum = "Maximale" +potato = "Patate" +smaller = "Fichier plus léger" +social = "Réseaux" +web = "Web" + +[editor.export.resources] +proceed = "Continuer quand même" +title = "Vérifier les ressources d'exportation" + +[editor.export.section] +destination = "Destination" +format = "Format" +frame_rate = "Fréquence d'images" +quality = "Qualité" +resolution = "Résolution" + +[editor.export.stats] +calculating = "Calcul…" +duration = "Durée" +estimated_size = "Taille estimée" +export_time = "Durée d'exportation" +output = "Sortie" +output_value = "{width}×{height} · {fps} i/s" +refining_size = "Affinage de la taille…" +refining_time = "Affinage de la durée…" +unavailable = "Indisponible" + +[editor.modal] +cancel_export = "Annuler l'exportation" +cancel_export_message = "Voulez-vous vraiment annuler l'exportation ?" +cancel_export_title = "Annuler l'exportation ?" +keep_exporting = "Continuer l'exportation" +recording_retained = "Enregistrement conservé" + +[editor.tabs.audio] +isolation_balanced = "Équilibrée" +isolation_balanced_description = "Isolation plus nette avec des détails de voix naturels." +isolation_light = "Légère" +isolation_light_description = "Conserve davantage votre voix d'origine et le son de la pièce." +isolation_strong = "Forte" +isolation_strong_description = "Plus d'isolation pour les lieux bruyants. Peut modifier le timbre de la voix." +mic_volume = "Volume du micro" +mute = "Couper le son" +stereo_mode = "Mode stéréo du micro" +studio_sound_description = "Réduit le bruit de fond et équilibre le niveau de votre voix." +studio_sound_save_failed = "Impossible d'enregistrer le réglage par défaut de Studio Sound" +system_volume = "Volume de l'audio système" +title = "Commandes audio" +use_for_new = "Utiliser pour les nouveaux enregistrements" + +[editor.tabs.audio.stereo] +mono_left = "Mono G" +mono_right = "Mono D" +stereo = "Stéréo" + +[editor.tabs.camera] +advanced_shadow = "Paramètres d'ombre avancés" +background = "Arrière-plan" +corner_style = "Style des coins" +custom_position = "Position personnalisée (déplacée sur le canevas)" +hide = "Masquer la caméra" +keep_size_during_zoom = "Conserver la taille d'origine pendant le zoom" +mirror = "Effet miroir" +position = "Position" +reset = "Réinitialiser" +rounded_corners = "Coins arrondis" +shadow = "Ombre" +shadow_blur = "Flou" +shadow_opacity = "Opacité" +shape_label = "Forme" +size_during_zoom = "Taille pendant le zoom" +size_label = "Taille" +title = "Caméra" + +[editor.tabs.camera.blur] +heavy = "Flou intense" +light = "Flou léger" +off = "Désactivé" +remove = "Supprimer l'arrière-plan" + +[editor.tabs.camera.shape] +source = "Source" +square = "Carré" + +[editor.tabs.captions] +active_word_highlight = "Surligner le mot actif" +animation_section = "Animation" +animation_style = "Style d'animation" +background_color = "Couleur d'arrière-plan" +background_opacity = "Opacité de l'arrière-plan" +background_settings = "Paramètres d'arrière-plan" +beta = "Bêta" +delete = "Supprimer" +deleting = "Suppression..." +download = "Télécharger le modèle {model}" +download_note = "Téléchargement unique sur votre système. Tous les sous-titres sont stockés localement." +downloading = "Téléchargement de {model}... {percent} %" +fade_duration = "Durée du fondu" +font_family = "Police" +font_settings = "Paramètres de police" +font_weight = "Graisse" +generate = "Générer les sous-titres" +generate_failed = "Impossible de générer les sous-titres : {reason}" +generating = "Génération..." +highlight_color = "Couleur de surlignage" +highlight_note = "Ceci est la première version des sous-titres dans Cap. Le surlignage du mot actif peut être imprécis dans certains cas. Nous travaillons à un correctif qui sera publié dans une prochaine version." +highlight_style = "Style de surlignage" +horizontal_position = "Position horizontale" +intel_unavailable = "Les modèles de sous-titres Parakeet ne sont pas disponibles sur les Mac Intel. Les modèles Whisper restent disponibles." +keep_open = "Laissez Cap ouvert pendant le téléchargement du modèle. Les rechargements de l'éditeur se reconnectent automatiquement." +language_label = "Langue" +model_downloaded = "Modèle {model} téléchargé" +model_label = "Modèle" +position = "Position" +regenerate = "Régénérer les sous-titres" +size_label = "Taille" +style = "Style" +text_color = "Couleur du texte" +title = "Sous-titres" +uppercase = "Majuscules" +vertical_position = "Position verticale" + +[editor.tabs.captions.animation] +bounce = "Rebond" +none = "Aucune" +pop = "Pop" + +[editor.tabs.captions.highlight] +color = "Couleur" +pill = "Pastille" + +[editor.tabs.captions.language] +ar = "Arabe" +auto = "Détection automatique" +bn = "Bengali" +de = "Allemand" +en = "Anglais" +es = "Espagnol" +fa = "Persan" +fr = "Français" +gu = "Gujarati" +he = "Hébreu" +hi = "Hindi" +it = "Italien" +ja = "Japonais" +ko = "Coréen" +mr = "Marathi" +nl = "Néerlandais" +pa = "Pendjabi" +pl = "Polonais" +pt = "Portugais" +ru = "Russe" +sk = "Slovaque" +ta = "Tamoul" +te = "Télougou" +tr = "Turc" +ur = "Ourdou" +zh = "Chinois" + +[editor.tabs.captions.model] +high_accuracy = "Haute précision" +high_accuracy_description = "Téléchargement plus lourd, précision accrue" +medium = "Moyen" +medium_description = "Plus lent, plus précis" +recommended = "Recommandé" +recommended_description = "Meilleur équilibre pour la plupart des enregistrements" +small = "Petit" +small_description = "Téléchargement le plus léger" + +[editor.tabs.captions.preset] +classic = "Classique" +classic_description = "Texte net sur un fond uni arrondi." +highlight = "Surligné" +highlight_description = "Majuscules grasses avec une pastille derrière le mot actif." +karaoke = "Karaoké" +karaoke_description = "Les mots s'illuminent au rythme de la parole." +minimal = "Minimal" +minimal_description = "Texte discret avec contour, sans arrière-plan." +pop = "Pop" +pop_description = "Majuscules ludiques qui apparaissent avec une touche de couleur vive." + +[editor.tabs.corner] +rounded = "Arrondi" +squircle = "Squircle" + +[editor.tabs.cursor] +friction = "Friction" +hide_when_idle = "Masquer en cas d'inactivité" +inactivity_delay = "Délai d'inactivité" +mass = "Masse" +movement_style = "Style de mouvement du curseur" +show = "Afficher le curseur" +size_label = "Taille" +smooth_movement = "Mouvement fluide" +style_label = "Style du curseur" +svg_cursors = "Curseurs SVG haute qualité" +tension = "Tension" +tilt = "Inclinaison" + +[editor.tabs.cursor.style] +fast = "Rapide" +fast_description = "Vif, suivi au plus près" +mellow = "Doux" +mellow_description = "Mouvement équilibré et naturel" +slow = "Lent" +slow_description = "Mouvement long et glissant" +smooth = "Fluide" +smooth_description = "Mouvement souple et flottant" + +[editor.tabs.keyboard] +animation = "Animation" +background_color = "Couleur d'arrière-plan" +background_opacity = "Opacité de l'arrière-plan" +background_settings = "Paramètres d'arrière-plan" +behavior = "Comportement" +beta = "Bêta" +changed_during_generation = "La timeline ou les paramètres du clavier ont changé pendant la génération. Réessayez." +empty_description = "Cliquez sur « Générer les segments clavier » pour créer des segments à partir des touches enregistrées." +empty_title = "Aucun segment clavier pour l'instant." +fade_duration = "Durée du fondu" +font_family = "Police" +font_settings = "Paramètres de police" +font_weight = "Graisse" +generate = "Générer les segments clavier" +generating = "Génération des segments clavier..." +grouping_threshold = "Seuil de regroupement" +linger_duration = "Durée d'affichage" +load_failed = "Impossible de charger les données d'enregistrement : {error}" +no_presses = "Aucune frappe enregistrée n'a été trouvée." +position = "Position" +regenerate = "Régénérer les segments clavier" +show_modifiers = "Afficher les modificateurs" +show_special_keys = "Afficher les touches spéciales" +size_label = "Taille" +text_color = "Couleur du texte" +timeline_unavailable = "La timeline du projet n'est pas disponible." +timeline_unreadable = "Impossible de lire la timeline du projet." +title = "Afficher le clavier" +uppercase = "Majuscules" + +[editor.tabs.position] +bottom_center = "En bas au centre" +bottom_left = "En bas à gauche" +bottom_right = "En bas à droite" +manual = "Manuelle" +top_center = "En haut au centre" +top_left = "En haut à gauche" +top_right = "En haut à droite" + +[editor.tabs.sync] +auto_description = "Cap a calculé ces décalages automatiquement pour garder l'audio synchronisé avec la vidéo. Ajustez-les si quelque chose sonne encore faux." +auto_synced = "Synchronisé automatiquement" +camera = "Décalage de la caméra" +clip = "Clip {number}" +description = "Ajustez finement les décalages des sources si l'audio ou la caméra se désynchronise de l'enregistrement d'écran." +microphone = "Décalage du micro" +system_audio = "Décalage de l'audio système" +title = "Synchronisation" + +[editor.tabs.weight] +bold = "Gras" +medium = "Moyen" +normal = "Normal" + +[editor.timeline] +add_shot = "Ajouter un plan" +add_shot_with_look = "Ajouter un plan · {look} · {seconds} s" +paused = "En pause" + +[editor.timeline.empty] +add_audio = "Ajouter de l'audio" +add_scene = "Ajouter une scène" +audio = "Ajoutez une musique de fond ou importez votre propre audio" +captions = "Aucun sous-titre" +keyboard = "Aucun événement clavier" +scene = "Alternez les dispositions entre votre écran et votre caméra" + +[editor.timeline.picker] +audio = "Ajoutez une musique de fond ou importez votre propre audio." +caption = "Transcrivez automatiquement votre enregistrement en sous-titres à l'écran." +clip = "Les images de votre écran enregistré." +image = "Ajoutez une image à votre enregistrement." +keyboard = "Affichez les touches à l'écran pendant que vous tapez." +mask = "Floutez ou noircissez les zones privées de l'écran." +scene = "Alternez les dispositions entre votre écran et votre caméra." +scene_unavailable = "Enregistrez avec une caméra pour utiliser les scènes." +style = "Modifiez l'arrière-plan, la caméra et le curseur au fil du temps." +text_overlay = "Ajoutez des textes et des titres personnalisés sur le canevas." +three_d = "Inclinez la scène en perspective 3D." +zoom = "Des zooms fluides qui suivent l'action." + +[editor.timeline.remove] +audio = "Supprimer la piste Audio." +captions = "Supprimer la piste Sous-titres." +clip = "Supprimer la piste Clip." +image = "Supprimer la piste Image." +keyboard = "Supprimer la piste Clavier." +mask = "Supprimer la piste Masque." +scene = "Supprimer la piste Scène." +style = "Supprimer la piste Style." +text_track = "Supprimer la piste Texte." +three_d = "Supprimer la piste 3D." +zoom = "Supprimer la piste Zoom." + +[editor.timeline.scene] +camera_only = "Caméra seule" +default = "Par défaut" +floating = "Flottante" +hide_camera = "Masquer la caméra" +split_screen = "Écran partagé" + +[editor.timeline.segment] +audio = "Audio" +automatic_zoom = "Zoom automatique" +caption = "Sous-titre" +clip = "Clip" +cursor_hidden = "Curseur masqué" +highlight = "Mise en valeur" +label = "Libellé" +manual_zoom = "Zoom manuel" +muted = "Muet" +numbered_clip = "Clip {number}" +sensitive = "Sensible" + +[editor.timeline.track] +audio = "Audio" +captions = "Sous-titres" +clip = "Clip" +image = "Image" +keyboard = "Clavier" +mask = "Masque" +scene = "Scène" +style = "Style" +text_track = "Texte" +video = "Vidéo" +zoom = "Zoom" + +[editor.window] +audio_filter = "Audio" +audio_segment_changed = "Ce segment audio a changé. Sélectionnez-le et choisissez de nouveau un remplacement." +crop = "Recadrer" +crop_tooltip = "Recadrer la vidéo" +no_room_for_3d_shot = "Plus de place pour un autre plan 3D" +redo = "Rétablir" +save_failed = "Impossible d'enregistrer {path}. Gardez l'éditeur ouvert et réessayez : {error}" +undo = "Annuler" + +[editor.window.add_track] +button = "Ajouter une piste" +hide_3d = "Masquer la piste 3D." +hint = "Choisissez une piste à ajouter à votre timeline." + +[editor.window.aspect] +auto = "Auto" +classic = "Classique" +classic_ratio = "Classique ⋅4:3" +square = "Carré" +square_ratio = "Carré ⋅1:1" +tall = "Haut" +tall_ratio = "Haut ⋅3:4" +tooltip = "Format d'image" +vertical = "Vertical" +vertical_ratio = "Vertical ⋅9:16" +wide = "Large" +wide_ratio = "Large ⋅16:9" + +[editor.window.audio_import] +copy_failed = "Impossible de copier le fichier audio : {error}" +directory_failed = "Impossible de créer le dossier audio : {error}" +no_extension = "Le fichier audio n'a pas d'extension" +not_found = "Fichier audio introuvable : {path}" +unsupported = "Format audio non pris en charge : .{extension}" + +[editor.window.busy] +clip_import = "Attendez la fin de l'importation du clip avant de fermer ou de supprimer cet enregistrement." +export = "Attendez la fin de l'exportation ou de son annulation avant de fermer ou de supprimer cet enregistrement." +opening = "Attendez que l'enregistrement soit ouvert avant de le supprimer." +save_before_delete_failed = "Impossible d'enregistrer l'enregistrement avant de le supprimer : {error}" + +[editor.window.camera3d_lane] +add_shot = "+ Ajouter un plan" +auto_scene = "Scène auto" +how_many = "Combien de plans ?" +prompt = "Ajoutez des plans 3D cinématiques à votre enregistrement" + +[editor.window.clip_menu] +audio = "AUDIO" +cursor = "CURSEUR" +delete = "Supprimer le clip" +delete_only_clip = "Impossible de supprimer le seul clip" +hide_cursor = "Masquer le curseur" +hide_cursor_description = "Disparaît pendant la lecture de ce clip" +keep_pitch = "Garder la hauteur" +match_speed = "Suivre la vitesse" +merge_next = "Fusionner avec le clip suivant" +merge_previous = "Fusionner avec le clip précédent" +mute = "Muet" +mute_clip = "Couper le son du clip" +mute_clip_description = "Coupe l'audio de ce clip" +rename = "Renommer le clip" +speed = "VITESSE" +split = "Couper à la tête de lecture" +split_hint = "Placez la tête de lecture dans ce clip" +volume = "Volume" + +[editor.window.frame] +add_tooltip = "Ajouter un cadre" +browser = "Navigateur" +browser_description = "Fenêtre de navigateur avec barre d'adresse" +button = "Cadre" +dark = "Sombre" +global = "Cadre global · S'applique partout où le style hérite de l'arrière-plan." +heading = "Cadre" +light = "Clair" +macbook_description = "Contour d'ordinateur portable autour de l'enregistrement" +macos_description = "Fenêtre avec boutons de contrôle colorés" +none = "Aucun" +none_description = "Afficher l'enregistrement tel quel" +style_changed = "Ce style a changé. Fermez puis rouvrez le cadre." +style_only = "Style {index} uniquement · Les paramètres globaux restent inchangés." +style_only_override = "Style {index} uniquement · La modification active son arrière-plan personnalisé." +theme = "Thème" +title = "Titre" +url = "URL" +window_title_placeholder = "Titre de la fenêtre" +windows_description = "Barre de titre avec boutons de fenêtre" + +[editor.window.header] +captions = "Sous-titres" +delete_recording = "Supprimer l'enregistrement" +export = "Exporter" +open_bundle = "Ouvrir le dossier de l'enregistrement" +presets = "Préréglages" + +[editor.window.image] +changed = "Cette image a changé pendant le choix d'un remplacement. Sélectionnez-la et réessayez." +filter = "Images" +recording_unavailable = "L'enregistrement n'est plus disponible pour cette image. Rouvrez le projet." + +[editor.window.open] +damaged = "Impossible d'ouvrir les pistes vidéo de cet enregistrement. Le dossier semble endommagé." +failed = "Impossible d'ouvrir cet enregistrement" +media_failed = "Impossible de lire les médias de cet enregistrement : {error}" +meta_failed = "Impossible de charger les métadonnées de l'enregistrement : {error}" +no_segments = "L'enregistrement n'a aucun segment. Il faut peut-être d'abord le récupérer." +not_found = "Chemin vidéo {path} introuvable !" +not_studio = "Seuls les enregistrements Studio peuvent être modifiés" + +[editor.window.presets] +apply = "Appliquer" +create = "Créer" +create_new = "Créer un préréglage" +create_title = "Créer un préréglage" +default = "Par défaut" +default_badge = "Par défaut" +delete = "Supprimer" +delete_confirm = "Voulez-vous vraiment supprimer « {name} » ?" +delete_title = "Supprimer le préréglage" +name_placeholder = "Nom du préréglage" +none_saved = "Aucun préréglage enregistré" +rename = "Renommer" +rename_title = "Renommer le préréglage" +save_settings = "Enregistrer les réglages dans le préréglage" +set_default = "Définir par défaut" + +[editor.window.preview] +full = "Pleine" +half = "Moitié" +label = "Aperçu" +quarter = "Quart" + +[editor.window.text_menu] +copy_style = "Copier le style" +copy_style_first = "Copiez d'abord un style" +delete_segment = "Supprimer le segment de texte" +delete_segments = "Supprimer les segments de texte" +paste_style = "Coller le style" +paste_style_all = "Coller le style sur toute la sélection" +segments = { one = "{count} segment de texte", other = "{count} segments de texte" } +untitled = "Texte" + +[editor.window.title] +not_saved = "Titre non enregistré" +not_saved_message = "{error}\n\nVotre titre est toujours dans l'éditeur. Essayez de l'enregistrer à nouveau." +read_failed = "Impossible de lire le titre de l'enregistrement pour {path} : {error}" +save_failed = "Impossible d'enregistrer le titre de l'enregistrement pour {path} : {error}" + +[editor.window.track] +clear_all = "Tout effacer" +delete = "Supprimer" + +[editor.window.transport] +done_splitting = "Terminer la découpe" +play_pause = "Lire/mettre en pause la vidéo" +split = "Couper" +split_tooltip = "Cliquez sur un segment pour le couper. Échap pour terminer." +zoom_in = "Zoom avant" +zoom_out = "Zoom arrière" + +[editor.window.zoom_lane] +generate = "Générer" +generating = "Génération…" +no_clicks = "Aucun clic trouvé pour zoomer. Faites glisser sur la piste pour en ajouter un." +prompt = "Ajouter des zooms automatiquement" + +[ui] +coming_soon = "Bientôt disponible" + +[ui.selection] +audio = { one = "{count} segment audio sélectionné", other = "{count} segments audio sélectionnés" } +caption = { one = "{count} segment de sous-titres sélectionné", other = "{count} segments de sous-titres sélectionnés" } +delete = "Supprimer" +done = "Terminé" +image = { one = "{count} segment d'image sélectionné", other = "{count} segments d'image sélectionnés" } +keyboard = { one = "{count} segment clavier sélectionné", other = "{count} segments clavier sélectionnés" } +mask = { one = "{count} segment de masque sélectionné", other = "{count} segments de masque sélectionnés" } +scene = { one = "{count} segment de scène sélectionné", other = "{count} segments de scène sélectionnés" } +segments = { one = "{count} segment sélectionné", other = "{count} segments sélectionnés" } +select_all = "Tout sélectionner" +style = { one = "{count} segment de style sélectionné", other = "{count} segments de style sélectionnés" } +text_segments = { one = "{count} segment de texte sélectionné", other = "{count} segments de texte sélectionnés" } +three_d = { one = "{count} segment 3D sélectionné", other = "{count} segments 3D sélectionnés" } +zoom = { one = "{count} sélectionné", other = "{count} sélectionnés" } +zoom_all = { one = "{count} sélectionné", other = "Les {count} sélectionnés" } +zoom_some = { one = "{count} sur {total} sélectionné", other = "{count} sur {total} sélectionnés" } diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index 0ee072f4c59..cdf7c6324d9 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -586,3 +586,670 @@ click_or_press = "यहाँ क्लिक करें, या दबाए get_started = "शुरू करें" space_key = "स्पेस" tagline = "सबके लिए ओपन सोर्स स्क्रीन शेयरिंग ऐप।" + +[editor.canvas] +camera = "कैमरा" +camera_follows_zoom = "कैमरे का आकार ज़ूम के साथ बदलता है; हिलाने के लिए खींचें" +edit_zoom = "ज़ूम संपादित करें" +image = "इमेज" +mask = "मास्क" +screen = "स्क्रीन" +screen_locked = "ज़ूम चालू रहने तक स्क्रीन लॉक रहती है" +text_item = "टेक्स्ट" + +[editor.clips] +append_error = "क्लिप नहीं जोड़ी जा सकी: {message}" +append_error_title = "रिकॉर्डिंग त्रुटि" +back = "एडिटर पर वापस जाएँ" +cap_filter = "Cap रिकॉर्डिंग" +clip = "क्लिप {number}" +empty_description = "कोई क्लिप रिकॉर्ड या इम्पोर्ट करें, वह यहाँ दिखेगी।" +empty_title = "अभी कोई क्लिप नहीं" +import = "इम्पोर्ट करें" +import_error = "क्लिप इम्पोर्ट नहीं हो सकी: {message}" +import_error_title = "इम्पोर्ट त्रुटि" +import_existing = "मौजूदा रिकॉर्डिंग" +import_mp4 = "MP4 वीडियो…" +importing = "इम्पोर्ट हो रहा है…" +mp4_filter = "MP4 वीडियो" +prepare_controls_failed = "रिकॉर्डिंग कंट्रोल तैयार नहीं हो सके: {error}" +record_new = "नई क्लिप रिकॉर्ड करें" +save_before_recording_failed = "रिकॉर्डिंग से पहले मौजूदा प्रोजेक्ट सेव नहीं हो सका: {error}" +split = "हिस्सा {number}" +title = "क्लिप" + +[editor.clips.error] +cancelled = "इम्पोर्ट रद्द किया गया" +cannot_decode = "वीडियो {path} डिकोड नहीं हो सका: {error}" +in_use = "यह रिकॉर्डिंग उपयोग में होने पर इम्पोर्ट नहीं हो सकता: {error}" +instant_source = "Instant रिकॉर्डिंग अभी किसी प्रोजेक्ट में इम्पोर्ट नहीं की जा सकतीं" +instant_target = "Instant मोड की रिकॉर्डिंग संपादित नहीं की जा सकतीं" +into_itself = "किसी रिकॉर्डिंग को उसी में इम्पोर्ट नहीं किया जा सकता" +missing_video = "वीडियो फ़ाइल नहीं मिली: {path}" +no_duration = "इम्पोर्ट किए गए वीडियो की कोई मान्य अवधि नहीं है" +not_a_project = "कोई Cap प्रोजेक्ट फ़ोल्डर चुनें" +source_empty = "स्रोत Cap प्रोजेक्ट में कोई रिकॉर्डिंग सेगमेंट नहीं है" +source_failed = "स्रोत Cap प्रोजेक्ट विफल रहा: {error}" +source_recording = "स्रोत Cap प्रोजेक्ट अभी भी रिकॉर्ड हो रहा है" +source_unimportable = "स्रोत Cap प्रोजेक्ट में इम्पोर्ट करने लायक कोई रिकॉर्डिंग सेगमेंट नहीं है" +unfinished_target = "क्लिप इम्पोर्ट करने से पहले इस रिकॉर्डिंग को पूरा या रिकवर करें" +unsupported_video = "असमर्थित वीडियो फ़ाइल: {path}" + +[editor.export] +back = "एडिटर पर वापस जाएँ" +existing_link_description = "दोबारा अपलोड करने पर इस लिंक का वीडियो आपके नए संपादन से बदल जाता है। लिंक वाले सभी लोग अपडेट किया गया वर्ज़न देखेंगे।" +existing_link_title = "अपना मौजूदा लिंक अपडेट करें" +failed = "एक्सपोर्ट विफल रहा" +fps = "{fps} FPS" +frames = { one = "{rendered} / {count} फ़्रेम", other = "{rendered} / {count} फ़्रेम" } +generating_preview = "प्रीव्यू बनाया जा रहा है…" +link_unavailable_cursor = "केवल कर्सर वाले एक्सपोर्ट सिर्फ़ फ़ाइल या क्लिपबोर्ड में सेव किए जा सकते हैं।" +link_unavailable_transparent = "पारदर्शी एक्सपोर्ट सिर्फ़ फ़ाइल या क्लिपबोर्ड में सेव किए जा सकते हैं।" +organization = "संगठन" +preview = "प्रीव्यू" +preview_info = "यह आपके वीडियो का रेंडर किया गया फ़्रेम है। फ़ाइनल एक्सपोर्ट की क्वालिटी देखने के लिए सेटिंग्स बदलें।" +preview_unavailable = "प्रीव्यू उपलब्ध नहीं है" +reupload = "दोबारा अपलोड करें" +reupload_tooltip = "अपना नया संपादन उसी लिंक पर अपलोड करें" +reupload_transparent = "शेयर लिंक के लिए बिना पारदर्शिता वाला बैकग्राउंड चाहिए" +sign_in_required = "रिकॉर्डिंग शेयर करने के लिए आपको साइन इन करना होगा" +task_failed = "एक्सपोर्ट कार्य विफल रहा" +title = "एक्सपोर्ट" +upgrade_message = "इस सुविधा के लिए अपग्रेड किया गया प्लान चाहिए" +upgrade_title = "अपग्रेड ज़रूरी है" +upload_failed = "रिकॉर्डिंग अपलोड नहीं हो सकी" + +[editor.export.advanced] +bits_per_pixel = "बिट्स प्रति पिक्सेल" +cursor_only = "केवल कर्सर एक्सपोर्ट करें" +cursor_only_description = "पारदर्शी बैकग्राउंड पर वही कर्सर मूवमेंट और क्लिक रखता है" +custom_bitrate = "कस्टम बिटरेट इस्तेमाल हो रहा है" +force_ffmpeg = "FFmpeg डिकोडर ज़बरदस्ती इस्तेमाल करें" +force_ffmpeg_description = "हार्डवेयर डिकोडर छोड़ें (ऑटो-फ़ॉलबैक चालू)" +title = "एडवांस्ड" +transparent_mov = "पारदर्शी MOV के रूप में एक्सपोर्ट करता है। फ़ाइलें बड़ी होती हैं और कंपोज़िटिंग या एडिटिंग के लिए सबसे अच्छी हैं।" + +[editor.export.cta] +cancel_sign_in = "साइन इन रद्द करें" +clipboard = "क्लिपबोर्ड में एक्सपोर्ट करें" +create_link = "शेयर करने लायक लिंक बनाएँ" +file = "फ़ाइल में एक्सपोर्ट करें" +reupload = "उसी लिंक पर दोबारा अपलोड करें" +sign_in = "शेयर करने के लिए साइन इन करें" + +[editor.export.destination] +clipboard = "क्लिपबोर्ड" +file = "फ़ाइल" +link = "शेयर लिंक" + +[editor.export.done] +copy_link = "लिंक कॉपी करें" +instant_hint = "अगर आप रोकते ही लिंक चाहते हैं, तो अगली रिकॉर्डिंग के लिए Instant मोड इस्तेमाल करें।" +link_copied = "लिंक कॉपी हो गया" +open_file = "फ़ाइल खोलें" +open_link = "लिंक खोलें" +retry_copy = "फिर से कॉपी करें" +reuploaded = "आपका नया संपादन उसी लिंक पर तैयार है" +uploaded = "आपका Cap सफलतापूर्वक अपलोड हो गया" + +[editor.export.estimate] +gigabytes = "~{value} GB" +gigabytes_range = "~{lower}–{upper} GB" +hours = "~{value} घंटे" +hours_range = "~{lower}–{upper} घंटे" +megabytes = "~{value} MB" +megabytes_range = "~{lower}–{upper} MB" +minutes = "~{value} मिनट" +minutes_range = "~{lower}–{upper} मिनट" +seconds = "~{value} सेकंड" +seconds_range = "~{lower}–{upper} सेकंड" +under_megabyte = "< 1 MB" +under_second = "< 1 सेकंड" + +[editor.export.optimize] +description = "बहुत छोटी फ़ाइलों के लिए सॉफ़्टवेयर से दोबारा एनकोड करता है (धीमा)" +title = "फ़ाइल साइज़ ऑप्टिमाइज़ करें" + +[editor.export.phase] +clipboard_failed = "एक्सपोर्ट पूरा हुआ; क्लिपबोर्ड में कॉपी विफल रही" +complete = "एक्सपोर्ट पूरा हुआ" +copied = "क्लिपबोर्ड में कॉपी हो गया" +copying_to_clipboard = "क्लिपबोर्ड में कॉपी हो रहा है" +rendering_cursor = "कर्सर ट्रैक रेंडर हो रहा है" +rendering_gif = "GIF रेंडर हो रहा है" +rendering_video = "वीडियो रेंडर हो रहा है" +reupload_complete = "दोबारा अपलोड पूरा हुआ" +reuploading = "आपके लिंक पर दोबारा अपलोड हो रहा है" +saving_to_file = "फ़ाइल में सेव हो रहा है" +starting = "एक्सपोर्ट की तैयारी हो रही है" +upload_complete = "अपलोड पूरा हुआ" +uploading = "शेयर करने लायक लिंक बन रहा है" + +[editor.export.quality] +larger = "बड़ी फ़ाइल" +maximum = "अधिकतम" +potato = "न्यूनतम" +smaller = "छोटी फ़ाइल" +social = "सोशल" +web = "वेब" + +[editor.export.resources] +proceed = "फिर भी जारी रखें" +title = "एक्सपोर्ट संसाधन जाँचें" + +[editor.export.section] +destination = "गंतव्य" +format = "फ़ॉर्मेट" +frame_rate = "फ़्रेम रेट" +quality = "क्वालिटी" +resolution = "रेज़ोल्यूशन" + +[editor.export.stats] +calculating = "गणना हो रही है…" +duration = "अवधि" +estimated_size = "अनुमानित साइज़" +export_time = "एक्सपोर्ट समय" +output = "आउटपुट" +output_value = "{width}×{height} · {fps} fps" +refining_size = "साइज़ सटीक किया जा रहा है…" +refining_time = "समय सटीक किया जा रहा है…" +unavailable = "उपलब्ध नहीं" + +[editor.modal] +cancel_export = "एक्सपोर्ट रद्द करें" +cancel_export_message = "क्या आप वाकई एक्सपोर्ट रद्द करना चाहते हैं?" +cancel_export_title = "एक्सपोर्ट रद्द करें?" +keep_exporting = "एक्सपोर्ट जारी रखें" +recording_retained = "रिकॉर्डिंग रखी गई" + +[editor.tabs.audio] +isolation_balanced = "संतुलित" +isolation_balanced_description = "आवाज़ के स्वाभाविक विवरण के साथ ज़्यादा साफ़ अलगाव।" +isolation_light = "हल्का" +isolation_light_description = "आपकी मूल आवाज़ और कमरे की ध्वनि ज़्यादा बनी रहती है।" +isolation_strong = "मज़बूत" +isolation_strong_description = "शोर वाली जगहों के लिए ज़्यादा अलगाव। आवाज़ का टेक्सचर बदल सकता है।" +mic_volume = "माइक्रोफ़ोन वॉल्यूम" +mute = "ऑडियो म्यूट करें" +stereo_mode = "माइक्रोफ़ोन स्टीरियो मोड" +studio_sound_description = "बैकग्राउंड शोर कम करता है और आपकी आवाज़ का स्तर संतुलित करता है।" +studio_sound_save_failed = "Studio Sound का डिफ़ॉल्ट सेव नहीं हो सका" +system_volume = "सिस्टम ऑडियो वॉल्यूम" +title = "ऑडियो कंट्रोल" +use_for_new = "नई रिकॉर्डिंग के लिए इस्तेमाल करें" + +[editor.tabs.audio.stereo] +mono_left = "मोनो L" +mono_right = "मोनो R" +stereo = "स्टीरियो" + +[editor.tabs.camera] +advanced_shadow = "एडवांस्ड शैडो सेटिंग्स" +background = "बैकग्राउंड" +corner_style = "कोने की स्टाइल" +custom_position = "कस्टम स्थिति (कैनवस पर खींची गई)" +hide = "कैमरा छिपाएँ" +keep_size_during_zoom = "ज़ूम के दौरान मूल आकार बनाए रखें" +mirror = "कैमरा मिरर करें" +position = "स्थिति" +reset = "रीसेट करें" +rounded_corners = "गोल कोने" +shadow = "शैडो" +shadow_blur = "धुंधलापन" +shadow_opacity = "अपारदर्शिता" +shape_label = "आकृति" +size_during_zoom = "ज़ूम के दौरान आकार" +size_label = "आकार" +title = "कैमरा" + +[editor.tabs.camera.blur] +heavy = "ज़्यादा ब्लर" +light = "हल्का ब्लर" +off = "बंद" +remove = "बैकग्राउंड हटाएँ" + +[editor.tabs.camera.shape] +source = "मूल" +square = "वर्ग" + +[editor.tabs.captions] +active_word_highlight = "सक्रिय शब्द हाइलाइट करें" +animation_section = "एनिमेशन" +animation_style = "एनिमेशन स्टाइल" +background_color = "बैकग्राउंड रंग" +background_opacity = "बैकग्राउंड अपारदर्शिता" +background_settings = "बैकग्राउंड सेटिंग्स" +beta = "बीटा" +delete = "हटाएँ" +deleting = "हटाया जा रहा है..." +download = "{model} मॉडल डाउनलोड करें" +download_note = "आपके सिस्टम पर एक बार डाउनलोड। सभी कैप्शन लोकल रूप से सेव रहते हैं।" +downloading = "{model} डाउनलोड हो रहा है... {percent}%" +fade_duration = "फ़ेड अवधि" +font_family = "फ़ॉन्ट" +font_settings = "फ़ॉन्ट सेटिंग्स" +font_weight = "फ़ॉन्ट वज़न" +generate = "कैप्शन बनाएँ" +generate_failed = "कैप्शन नहीं बन सके: {reason}" +generating = "बनाया जा रहा है..." +highlight_color = "हाइलाइट रंग" +highlight_note = "यह Cap में कैप्शन का पहला वर्ज़न है। कुछ स्थितियों में सक्रिय शब्द की हाइलाइटिंग गलत हो सकती है। हम इसे ठीक करने पर काम कर रहे हैं और यह आने वाले वर्ज़न में जारी होगा।" +highlight_style = "हाइलाइट स्टाइल" +horizontal_position = "क्षैतिज स्थिति" +intel_unavailable = "Parakeet कैप्शन मॉडल Intel Mac पर उपलब्ध नहीं हैं। Whisper मॉडल उपलब्ध रहेंगे।" +keep_open = "मॉडल डाउनलोड होने तक Cap खुला रखें। एडिटर दोबारा लोड होने पर अपने आप फिर से कनेक्ट हो जाएगा।" +language_label = "भाषा" +model_downloaded = "{model} मॉडल डाउनलोड हो गया" +model_label = "मॉडल" +position = "स्थिति" +regenerate = "कैप्शन दोबारा बनाएँ" +size_label = "आकार" +style = "स्टाइल" +text_color = "टेक्स्ट रंग" +title = "कैप्शन" +uppercase = "बड़े अक्षर" +vertical_position = "ऊर्ध्वाधर स्थिति" + +[editor.tabs.captions.animation] +bounce = "बाउंस" +none = "कोई नहीं" +pop = "पॉप" + +[editor.tabs.captions.highlight] +color = "रंग" +pill = "पिल" + +[editor.tabs.captions.language] +ar = "अरबी" +auto = "अपने आप पहचानें" +bn = "बांग्ला" +de = "जर्मन" +en = "अंग्रेज़ी" +es = "स्पैनिश" +fa = "फ़ारसी" +fr = "फ़्रेंच" +gu = "गुजराती" +he = "हिब्रू" +hi = "हिन्दी" +it = "इतालवी" +ja = "जापानी" +ko = "कोरियाई" +mr = "मराठी" +nl = "डच" +pa = "पंजाबी" +pl = "पोलिश" +pt = "पुर्तगाली" +ru = "रूसी" +sk = "स्लोवाक" +ta = "तमिल" +te = "तेलुगु" +tr = "तुर्की" +ur = "उर्दू" +zh = "चीनी" + +[editor.tabs.captions.model] +high_accuracy = "उच्च सटीकता" +high_accuracy_description = "बड़ा डाउनलोड, ज़्यादा सटीकता" +medium = "मध्यम" +medium_description = "धीमा, ज़्यादा सटीक" +recommended = "सुझाया गया" +recommended_description = "ज़्यादातर रिकॉर्डिंग के लिए सबसे अच्छा संतुलन" +small = "छोटा" +small_description = "सबसे छोटा डाउनलोड" + +[editor.tabs.captions.preset] +classic = "क्लासिक" +classic_description = "ठोस गोल बैकग्राउंड पर साफ़ टेक्स्ट।" +highlight = "हाइलाइट" +highlight_description = "सक्रिय शब्द के पीछे पिल के साथ बोल्ड बड़े अक्षर।" +karaoke = "कराओके" +karaoke_description = "बोलने के साथ शब्द चमकते हैं।" +minimal = "मिनिमल" +minimal_description = "बिना बैकग्राउंड के हल्का आउटलाइन टेक्स्ट।" +pop = "पॉप" +pop_description = "चटक रंग के साथ उभरते मज़ेदार बड़े अक्षर।" + +[editor.tabs.corner] +rounded = "गोल" +squircle = "स्क्विर्कल" + +[editor.tabs.cursor] +friction = "घर्षण" +hide_when_idle = "निष्क्रिय होने पर छिपाएँ" +inactivity_delay = "निष्क्रियता देरी" +mass = "द्रव्यमान" +movement_style = "कर्सर मूवमेंट स्टाइल" +show = "कर्सर दिखाएँ" +size_label = "आकार" +smooth_movement = "स्मूथ मूवमेंट" +style_label = "कर्सर स्टाइल" +svg_cursors = "उच्च क्वालिटी SVG कर्सर" +tension = "तनाव" +tilt = "झुकाव" + +[editor.tabs.cursor.style] +fast = "तेज़" +fast_description = "फुर्तीला, करीब से फ़ॉलो करता है" +mellow = "नरम" +mellow_description = "संतुलित, स्वाभाविक मूवमेंट" +slow = "धीमा" +slow_description = "लंबा, बहता हुआ मूवमेंट" +smooth = "स्मूथ" +smooth_description = "मुलायम, तैरता हुआ मूवमेंट" + +[editor.tabs.keyboard] +animation = "एनिमेशन" +background_color = "बैकग्राउंड रंग" +background_opacity = "बैकग्राउंड अपारदर्शिता" +background_settings = "बैकग्राउंड सेटिंग्स" +behavior = "व्यवहार" +beta = "बीटा" +changed_during_generation = "बनाते समय टाइमलाइन या कीबोर्ड सेटिंग्स बदल गईं। फिर से कोशिश करें।" +empty_description = "रिकॉर्ड किए गए की-प्रेस से सेगमेंट बनाने के लिए \"कीबोर्ड सेगमेंट बनाएँ\" पर क्लिक करें।" +empty_title = "अभी कोई कीबोर्ड सेगमेंट नहीं।" +fade_duration = "फ़ेड अवधि" +font_family = "फ़ॉन्ट" +font_settings = "फ़ॉन्ट सेटिंग्स" +font_weight = "फ़ॉन्ट वज़न" +generate = "कीबोर्ड सेगमेंट बनाएँ" +generating = "कीबोर्ड सेगमेंट बनाए जा रहे हैं..." +grouping_threshold = "ग्रुपिंग सीमा" +linger_duration = "दिखने की अवधि" +load_failed = "रिकॉर्डिंग डेटा लोड नहीं हो सका: {error}" +no_presses = "कोई रिकॉर्ड किया गया की-प्रेस नहीं मिला।" +position = "स्थिति" +regenerate = "कीबोर्ड सेगमेंट दोबारा बनाएँ" +show_modifiers = "मॉडिफ़ायर दिखाएँ" +show_special_keys = "विशेष कुंजियाँ दिखाएँ" +size_label = "आकार" +text_color = "टेक्स्ट रंग" +timeline_unavailable = "प्रोजेक्ट टाइमलाइन उपलब्ध नहीं है।" +timeline_unreadable = "प्रोजेक्ट टाइमलाइन पढ़ी नहीं जा सकी।" +title = "कीबोर्ड दिखाएँ" +uppercase = "बड़े अक्षर" + +[editor.tabs.position] +bottom_center = "नीचे बीच में" +bottom_left = "नीचे बाएँ" +bottom_right = "नीचे दाएँ" +manual = "मैन्युअल" +top_center = "ऊपर बीच में" +top_left = "ऊपर बाएँ" +top_right = "ऊपर दाएँ" + +[editor.tabs.sync] +auto_description = "ऑडियो को वीडियो के साथ सिंक रखने के लिए Cap ने ये ऑफ़सेट अपने आप निकाले हैं। अगर अब भी कुछ गड़बड़ लगे, तो इन्हें बदलें।" +auto_synced = "अपने आप सिंक" +camera = "कैमरा ऑफ़सेट" +clip = "क्लिप {number}" +description = "अगर ऑडियो या कैमरा स्क्रीन रिकॉर्डिंग से सिंक से बाहर हो जाए, तो स्रोत ऑफ़सेट को बारीकी से ठीक करें।" +microphone = "माइक्रोफ़ोन ऑफ़सेट" +system_audio = "सिस्टम ऑडियो ऑफ़सेट" +title = "सिंक" + +[editor.tabs.weight] +bold = "बोल्ड" +medium = "मध्यम" +normal = "सामान्य" + +[editor.timeline] +add_shot = "शॉट जोड़ें" +add_shot_with_look = "शॉट जोड़ें · {look} · {seconds} सेकंड" +paused = "रुका हुआ" + +[editor.timeline.empty] +add_audio = "ऑडियो जोड़ें" +add_scene = "सीन जोड़ें" +audio = "बैकग्राउंड म्यूज़िक जोड़ें या अपना ऑडियो इम्पोर्ट करें" +captions = "कोई कैप्शन नहीं" +keyboard = "कोई कीबोर्ड इवेंट नहीं" +scene = "अपनी स्क्रीन और कैमरे के बीच लेआउट बदलें" + +[editor.timeline.picker] +audio = "बैकग्राउंड म्यूज़िक जोड़ें या अपना ऑडियो इम्पोर्ट करें।" +caption = "अपनी रिकॉर्डिंग को अपने आप ऑन-स्क्रीन सबटाइटल में बदलें।" +clip = "आपकी रिकॉर्ड की गई स्क्रीन फ़ुटेज।" +image = "अपनी रिकॉर्डिंग में इमेज जोड़ें।" +keyboard = "टाइप करते समय स्क्रीन पर की-प्रेस दिखाएँ।" +mask = "स्क्रीन के निजी हिस्सों को धुंधला या काला करें।" +scene = "अपनी स्क्रीन और कैमरे के बीच लेआउट बदलें।" +scene_unavailable = "सीन इस्तेमाल करने के लिए कैमरे के साथ रिकॉर्ड करें।" +style = "समय के साथ बैकग्राउंड, कैमरा और कर्सर सेटिंग्स बदलें।" +text_overlay = "कैनवस पर कस्टम टेक्स्ट ओवरले और टाइटल जोड़ें।" +three_d = "सीन को 3D परिप्रेक्ष्य में झुकाएँ।" +zoom = "स्मूथ ज़ूम-इन जो एक्शन के साथ चलते हैं।" + +[editor.timeline.remove] +audio = "ऑडियो ट्रैक हटाएँ।" +captions = "कैप्शन ट्रैक हटाएँ।" +clip = "क्लिप ट्रैक हटाएँ।" +image = "इमेज ट्रैक हटाएँ।" +keyboard = "कीबोर्ड ट्रैक हटाएँ।" +mask = "मास्क ट्रैक हटाएँ।" +scene = "सीन ट्रैक हटाएँ।" +style = "स्टाइल ट्रैक हटाएँ।" +text_track = "टेक्स्ट ट्रैक हटाएँ।" +three_d = "3D ट्रैक हटाएँ।" +zoom = "ज़ूम ट्रैक हटाएँ।" + +[editor.timeline.scene] +camera_only = "केवल कैमरा" +default = "डिफ़ॉल्ट" +floating = "फ़्लोटिंग" +hide_camera = "कैमरा छिपाएँ" +split_screen = "स्प्लिट स्क्रीन" + +[editor.timeline.segment] +audio = "ऑडियो" +automatic_zoom = "ऑटोमैटिक ज़ूम" +caption = "कैप्शन" +clip = "क्लिप" +cursor_hidden = "कर्सर छिपा है" +highlight = "हाइलाइट" +label = "लेबल" +manual_zoom = "मैन्युअल ज़ूम" +muted = "म्यूट" +numbered_clip = "क्लिप {number}" +sensitive = "संवेदनशील" + +[editor.timeline.track] +audio = "ऑडियो" +captions = "कैप्शन" +clip = "क्लिप" +image = "इमेज" +keyboard = "कीबोर्ड" +mask = "मास्क" +scene = "सीन" +style = "स्टाइल" +text_track = "टेक्स्ट" +video = "वीडियो" +zoom = "ज़ूम" + +[editor.window] +audio_filter = "ऑडियो" +audio_segment_changed = "यह ऑडियो सेगमेंट बदल गया है। इसे चुनें और फिर से बदलने के लिए फ़ाइल चुनें।" +crop = "क्रॉप" +crop_tooltip = "वीडियो क्रॉप करें" +no_room_for_3d_shot = "एक और 3D शॉट के लिए जगह नहीं है" +redo = "फिर से करें" +save_failed = "{path} सेव नहीं हो सका। एडिटर खुला रखें और फिर से कोशिश करें: {error}" +undo = "पूर्ववत करें" + +[editor.window.add_track] +button = "ट्रैक जोड़ें" +hide_3d = "3D ट्रैक छिपाएँ।" +hint = "अपनी टाइमलाइन में जोड़ने के लिए एक ट्रैक चुनें।" + +[editor.window.aspect] +auto = "ऑटो" +classic = "क्लासिक" +classic_ratio = "क्लासिक ⋅4:3" +square = "वर्ग" +square_ratio = "वर्ग ⋅1:1" +tall = "लंबा" +tall_ratio = "लंबा ⋅3:4" +tooltip = "आस्पेक्ट रेशियो" +vertical = "वर्टिकल" +vertical_ratio = "वर्टिकल ⋅9:16" +wide = "वाइड" +wide_ratio = "वाइड ⋅16:9" + +[editor.window.audio_import] +copy_failed = "ऑडियो फ़ाइल कॉपी नहीं हो सकी: {error}" +directory_failed = "ऑडियो फ़ोल्डर नहीं बन सका: {error}" +no_extension = "ऑडियो फ़ाइल का कोई एक्सटेंशन नहीं है" +not_found = "ऑडियो फ़ाइल नहीं मिली: {path}" +unsupported = "असमर्थित ऑडियो फ़ॉर्मेट: .{extension}" + +[editor.window.busy] +clip_import = "इस रिकॉर्डिंग को बंद करने या हटाने से पहले क्लिप इम्पोर्ट पूरा होने दें।" +export = "इस रिकॉर्डिंग को बंद करने या हटाने से पहले एक्सपोर्ट या उसका रद्द होना पूरा होने दें।" +opening = "हटाने से पहले रिकॉर्डिंग को पूरी तरह खुलने दें।" +save_before_delete_failed = "हटाने से पहले रिकॉर्डिंग सेव नहीं हो सकी: {error}" + +[editor.window.camera3d_lane] +add_shot = "+ शॉट जोड़ें" +auto_scene = "ऑटो सीन" +how_many = "कितने शॉट?" +prompt = "अपनी रिकॉर्डिंग में सिनेमैटिक 3D शॉट जोड़ें" + +[editor.window.clip_menu] +audio = "ऑडियो" +cursor = "कर्सर" +delete = "क्लिप हटाएँ" +delete_only_clip = "इकलौती क्लिप नहीं हटाई जा सकती" +hide_cursor = "कर्सर छिपाएँ" +hide_cursor_description = "यह क्लिप चलते समय फ़ेड हो जाता है" +keep_pitch = "पिच बनाए रखें" +match_speed = "स्पीड से मिलाएँ" +merge_next = "अगली क्लिप से जोड़ें" +merge_previous = "पिछली क्लिप से जोड़ें" +mute = "म्यूट" +mute_clip = "क्लिप म्यूट करें" +mute_clip_description = "इस क्लिप का ऑडियो बंद करें" +rename = "क्लिप का नाम बदलें" +speed = "स्पीड" +split = "प्लेहेड पर विभाजित करें" +split_hint = "प्लेहेड को इस क्लिप के अंदर ले जाएँ" +volume = "वॉल्यूम" + +[editor.window.frame] +add_tooltip = "फ़्रेम जोड़ें" +browser = "ब्राउज़र" +browser_description = "एड्रेस बार के साथ ब्राउज़र फ़्रेम" +button = "फ़्रेम" +dark = "डार्क" +global = "ग्लोबल फ़्रेम · जहाँ भी स्टाइल बैकग्राउंड लेती है, वहाँ लागू होता है।" +heading = "फ़्रेम" +light = "लाइट" +macbook_description = "रिकॉर्डिंग के चारों ओर लैपटॉप बेज़ेल" +macos_description = "ट्रैफ़िक लाइट बटन के साथ विंडो फ़्रेम" +none = "कोई नहीं" +none_description = "रिकॉर्डिंग को जैसी है वैसी दिखाएँ" +style_changed = "यह स्टाइल बदल गई है। फ़्रेम बंद करके फिर से खोलें।" +style_only = "सिर्फ़ स्टाइल {index} · ग्लोबल सेटिंग्स नहीं बदलतीं।" +style_only_override = "सिर्फ़ स्टाइल {index} · बदलाव करने पर इसका अपना बैकग्राउंड चालू हो जाता है।" +theme = "थीम" +title = "टाइटल" +url = "URL" +window_title_placeholder = "विंडो टाइटल" +windows_description = "विंडो कंट्रोल के साथ टाइटल बार" + +[editor.window.header] +captions = "कैप्शन" +delete_recording = "रिकॉर्डिंग हटाएँ" +export = "एक्सपोर्ट" +open_bundle = "रिकॉर्डिंग बंडल खोलें" +presets = "प्रीसेट" + +[editor.window.image] +changed = "बदलने के लिए चुनते समय यह इमेज बदल गई। इसे चुनें और फिर से कोशिश करें।" +filter = "इमेज" +recording_unavailable = "इस इमेज के लिए रिकॉर्डिंग अब उपलब्ध नहीं है। प्रोजेक्ट फिर से खोलें।" + +[editor.window.open] +damaged = "इस रिकॉर्डिंग के वीडियो ट्रैक नहीं खुल सके। बंडल खराब लगता है।" +failed = "यह रिकॉर्डिंग नहीं खुल सकी" +media_failed = "इस रिकॉर्डिंग का मीडिया पढ़ा नहीं जा सका: {error}" +meta_failed = "रिकॉर्डिंग मेटाडेटा लोड नहीं हो सका: {error}" +no_segments = "रिकॉर्डिंग में कोई सेगमेंट नहीं है। हो सकता है पहले इसे रिकवर करना पड़े।" +not_found = "वीडियो पाथ {path} नहीं मिला!" +not_studio = "Studio के अलावा की रिकॉर्डिंग संपादित नहीं की जा सकतीं" + +[editor.window.presets] +apply = "लागू करें" +create = "बनाएँ" +create_new = "नया प्रीसेट बनाएँ" +create_title = "प्रीसेट बनाएँ" +default = "डिफ़ॉल्ट" +default_badge = "डिफ़ॉल्ट" +delete = "हटाएँ" +delete_confirm = "क्या आप वाकई \"{name}\" हटाना चाहते हैं?" +delete_title = "प्रीसेट हटाएँ" +name_placeholder = "प्रीसेट का नाम" +none_saved = "कोई सेव किया गया प्रीसेट नहीं" +rename = "नाम बदलें" +rename_title = "प्रीसेट का नाम बदलें" +save_settings = "सेटिंग्स को प्रीसेट में सेव करें" +set_default = "डिफ़ॉल्ट बनाएँ" + +[editor.window.preview] +full = "पूरा" +half = "आधा" +label = "प्रीव्यू" +quarter = "चौथाई" + +[editor.window.text_menu] +copy_style = "स्टाइल कॉपी करें" +copy_style_first = "पहले कोई स्टाइल कॉपी करें" +delete_segment = "टेक्स्ट सेगमेंट हटाएँ" +delete_segments = "टेक्स्ट सेगमेंट हटाएँ" +paste_style = "स्टाइल पेस्ट करें" +paste_style_all = "सभी चुने गए पर स्टाइल पेस्ट करें" +segments = { one = "{count} टेक्स्ट सेगमेंट", other = "{count} टेक्स्ट सेगमेंट" } +untitled = "टेक्स्ट" + +[editor.window.title] +not_saved = "टाइटल सेव नहीं हुआ" +not_saved_message = "{error}\n\nआपका टाइटल अब भी एडिटर में है। इसे फिर से सेव करने की कोशिश करें।" +read_failed = "{path} का रिकॉर्डिंग टाइटल पढ़ा नहीं जा सका: {error}" +save_failed = "{path} का रिकॉर्डिंग टाइटल सेव नहीं हो सका: {error}" + +[editor.window.track] +clear_all = "सब साफ़ करें" +delete = "हटाएँ" + +[editor.window.transport] +done_splitting = "विभाजन पूरा" +play_pause = "वीडियो चलाएँ/रोकें" +split = "विभाजित करें" +split_tooltip = "विभाजित करने के लिए किसी सेगमेंट पर क्लिक करें। खत्म करने के लिए Esc दबाएँ।" +zoom_in = "ज़ूम इन" +zoom_out = "ज़ूम आउट" + +[editor.window.zoom_lane] +generate = "बनाएँ" +generating = "बनाया जा रहा है…" +no_clicks = "ज़ूम करने के लिए कोई क्लिक नहीं मिला। जोड़ने के लिए लेन पर खींचें।" +prompt = "ज़ूम अपने आप जोड़ें" + +[ui] +coming_soon = "जल्द आ रहा है" + +[ui.selection] +audio = { one = "{count} ऑडियो सेगमेंट चुना गया", other = "{count} ऑडियो सेगमेंट चुने गए" } +caption = { one = "{count} कैप्शन सेगमेंट चुना गया", other = "{count} कैप्शन सेगमेंट चुने गए" } +delete = "हटाएँ" +done = "हो गया" +image = { one = "{count} इमेज सेगमेंट चुना गया", other = "{count} इमेज सेगमेंट चुने गए" } +keyboard = { one = "{count} कीबोर्ड सेगमेंट चुना गया", other = "{count} कीबोर्ड सेगमेंट चुने गए" } +mask = { one = "{count} मास्क सेगमेंट चुना गया", other = "{count} मास्क सेगमेंट चुने गए" } +scene = { one = "{count} सीन सेगमेंट चुना गया", other = "{count} सीन सेगमेंट चुने गए" } +segments = { one = "{count} सेगमेंट चुना गया", other = "{count} सेगमेंट चुने गए" } +select_all = "सभी चुनें" +style = { one = "{count} स्टाइल सेगमेंट चुना गया", other = "{count} स्टाइल सेगमेंट चुने गए" } +text_segments = { one = "{count} टेक्स्ट सेगमेंट चुना गया", other = "{count} टेक्स्ट सेगमेंट चुने गए" } +three_d = { one = "{count} 3D सेगमेंट चुना गया", other = "{count} 3D सेगमेंट चुने गए" } +zoom = { one = "{count} चुना गया", other = "{count} चुने गए" } +zoom_all = { one = "{count} चुना गया", other = "सभी {count} चुने गए" } +zoom_some = { one = "{total} में से {count} चुना गया", other = "{total} में से {count} चुने गए" } diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 44dd3f99308..95ef68ba2f3 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -586,3 +586,670 @@ click_or_press = "ここをクリックするか、次のキーを押す:" get_started = "はじめる" space_key = "スペース" tagline = "誰もが使えるオープンソースの画面共有アプリ。" + +[editor.canvas] +camera = "カメラ" +camera_follows_zoom = "カメラのサイズはズームに合わせて変わります。ドラッグで移動できます" +edit_zoom = "ズームを編集" +image = "画像" +mask = "マスク" +screen = "画面" +screen_locked = "ズームが有効な間は画面が固定されます" +text_item = "テキスト" + +[editor.clips] +append_error = "クリップを追加できませんでした:{message}" +append_error_title = "録画エラー" +back = "エディタに戻る" +cap_filter = "Cap録画" +clip = "クリップ{number}" +empty_description = "クリップを録画するか読み込むと、ここに表示されます。" +empty_title = "クリップはまだありません" +import = "読み込む" +import_error = "クリップを読み込めませんでした:{message}" +import_error_title = "読み込みエラー" +import_existing = "既存の録画" +import_mp4 = "MP4ビデオ…" +importing = "読み込み中…" +mp4_filter = "MP4ビデオ" +prepare_controls_failed = "録画コントロールを準備できませんでした:{error}" +record_new = "新しいクリップを録画" +save_before_recording_failed = "録画前に現在のプロジェクトを保存できませんでした:{error}" +split = "分割{number}" +title = "クリップ" + +[editor.clips.error] +cancelled = "読み込みをキャンセルしました" +cannot_decode = "ビデオ{path}をデコードできません:{error}" +in_use = "この録画の使用中は読み込めません:{error}" +instant_source = "Instant録画はまだプロジェクトに読み込めません" +instant_target = "Instantモードの録画は編集できません" +into_itself = "録画をそれ自体に読み込むことはできません" +missing_video = "ビデオファイルがありません:{path}" +no_duration = "読み込んだビデオに有効な長さがありません" +not_a_project = "Capのプロジェクトフォルダを選択してください" +source_empty = "読み込み元のCapプロジェクトに録画セグメントがありません" +source_failed = "読み込み元のCapプロジェクトでエラーが発生しました:{error}" +source_recording = "読み込み元のCapプロジェクトはまだ録画中です" +source_unimportable = "読み込み元のCapプロジェクトに読み込める録画セグメントがありません" +unfinished_target = "クリップを読み込む前に、この録画を完了するか復元してください" +unsupported_video = "対応していないビデオファイルです:{path}" + +[editor.export] +back = "エディタに戻る" +existing_link_description = "再アップロードすると、このリンクのビデオが最新の編集内容に置き換わります。リンクを知っている全員に更新版が表示されます。" +existing_link_title = "既存のリンクを更新" +failed = "書き出しに失敗しました" +fps = "{fps} FPS" +frames = { other = "{rendered} / {count}フレーム" } +generating_preview = "プレビューを生成中…" +link_unavailable_cursor = "カーソルのみの書き出しは、ファイルかクリップボードにのみ保存できます。" +link_unavailable_transparent = "透過の書き出しは、ファイルかクリップボードにのみ保存できます。" +organization = "組織" +preview = "プレビュー" +preview_info = "ビデオからレンダリングしたフレームです。設定を調整すると、最終的な書き出しの品質を確認できます。" +preview_unavailable = "プレビューを表示できません" +reupload = "再アップロード" +reupload_tooltip = "最新の編集内容を同じリンクにアップロード" +reupload_transparent = "共有リンクには透過のない背景が必要です" +sign_in_required = "録画を共有するにはサインインが必要です" +task_failed = "書き出し処理に失敗しました" +title = "書き出し" +upgrade_message = "この機能を使うにはプランのアップグレードが必要です" +upgrade_title = "アップグレードが必要です" +upload_failed = "録画をアップロードできませんでした" + +[editor.export.advanced] +bits_per_pixel = "ピクセルあたりのビット数" +cursor_only = "カーソルのみを書き出す" +cursor_only_description = "同じカーソルの動きとクリックを透過背景の上に保持します" +custom_bitrate = "カスタムビットレートを使用中" +force_ffmpeg = "FFmpegデコーダを強制" +force_ffmpeg_description = "ハードウェアデコーダを使わない(自動フォールバック有効)" +title = "詳細設定" +transparent_mov = "透過MOVとして書き出します。ファイルが大きいため、合成や編集に適しています。" + +[editor.export.cta] +cancel_sign_in = "サインインをキャンセル" +clipboard = "クリップボードに書き出す" +create_link = "共有リンクを作成" +file = "ファイルに書き出す" +reupload = "同じリンクに再アップロード" +sign_in = "サインインして共有" + +[editor.export.destination] +clipboard = "クリップボード" +file = "ファイル" +link = "共有リンク" + +[editor.export.done] +copy_link = "リンクをコピー" +instant_hint = "停止した瞬間にリンクが欲しい場合は、次の録画でInstantモードを使ってください。" +link_copied = "リンクをコピーしました" +open_file = "ファイルを開く" +open_link = "リンクを開く" +retry_copy = "コピーを再試行" +reuploaded = "最新の編集内容が同じリンクで利用できます" +uploaded = "Capのアップロードが完了しました" + +[editor.export.estimate] +gigabytes = "約{value} GB" +gigabytes_range = "約{lower}~{upper} GB" +hours = "約{value}時間" +hours_range = "約{lower}~{upper}時間" +megabytes = "約{value} MB" +megabytes_range = "約{lower}~{upper} MB" +minutes = "約{value}分" +minutes_range = "約{lower}~{upper}分" +seconds = "約{value}秒" +seconds_range = "約{lower}~{upper}秒" +under_megabyte = "1 MB未満" +under_second = "1秒未満" + +[editor.export.optimize] +description = "ソフトウェアで再エンコードし、ファイルを大幅に小さくします(低速)" +title = "ファイルサイズを最適化" + +[editor.export.phase] +clipboard_failed = "書き出しは完了しましたが、クリップボードへのコピーに失敗しました" +complete = "書き出しが完了しました" +copied = "クリップボードにコピーしました" +copying_to_clipboard = "クリップボードにコピー中" +rendering_cursor = "カーソルトラックをレンダリング中" +rendering_gif = "GIFをレンダリング中" +rendering_video = "ビデオをレンダリング中" +reupload_complete = "再アップロードが完了しました" +reuploading = "リンクに再アップロード中" +saving_to_file = "ファイルに保存中" +starting = "書き出しを準備中" +upload_complete = "アップロードが完了しました" +uploading = "共有リンクを作成中" + +[editor.export.quality] +larger = "大きいファイル" +maximum = "最高" +potato = "最低" +smaller = "小さいファイル" +social = "SNS" +web = "Web" + +[editor.export.resources] +proceed = "このまま続行" +title = "書き出しのリソースを確認" + +[editor.export.section] +destination = "書き出し先" +format = "形式" +frame_rate = "フレームレート" +quality = "品質" +resolution = "解像度" + +[editor.export.stats] +calculating = "計算中…" +duration = "長さ" +estimated_size = "推定サイズ" +export_time = "書き出し時間" +output = "出力" +output_value = "{width}×{height} · {fps} fps" +refining_size = "サイズを精査中…" +refining_time = "時間を精査中…" +unavailable = "利用不可" + +[editor.modal] +cancel_export = "書き出しをキャンセル" +cancel_export_message = "書き出しをキャンセルしてもよろしいですか?" +cancel_export_title = "書き出しをキャンセルしますか?" +keep_exporting = "書き出しを続ける" +recording_retained = "録画を保持しました" + +[editor.tabs.audio] +isolation_balanced = "バランス" +isolation_balanced_description = "自然な声の質感を保ちながら、よりはっきり分離します。" +isolation_light = "弱" +isolation_light_description = "元の声や部屋の響きをより多く残します。" +isolation_strong = "強" +isolation_strong_description = "騒がしい場所向けに強く分離します。声の質感が変わることがあります。" +mic_volume = "マイクの音量" +mute = "音声をミュート" +stereo_mode = "マイクのステレオモード" +studio_sound_description = "背景ノイズを抑え、声の音量を整えます。" +studio_sound_save_failed = "Studio Soundのデフォルトを保存できませんでした" +system_volume = "システム音声の音量" +title = "音声コントロール" +use_for_new = "新しい録画で使用" + +[editor.tabs.audio.stereo] +mono_left = "モノラル L" +mono_right = "モノラル R" +stereo = "ステレオ" + +[editor.tabs.camera] +advanced_shadow = "影の詳細設定" +background = "背景" +corner_style = "角のスタイル" +custom_position = "カスタム位置(キャンバス上でドラッグ)" +hide = "カメラを非表示" +keep_size_during_zoom = "ズーム中も元のサイズを保つ" +mirror = "カメラを反転" +position = "位置" +reset = "リセット" +rounded_corners = "角の丸み" +shadow = "影" +shadow_blur = "ぼかし" +shadow_opacity = "不透明度" +shape_label = "形" +size_during_zoom = "ズーム中のサイズ" +size_label = "サイズ" +title = "カメラ" + +[editor.tabs.camera.blur] +heavy = "強いぼかし" +light = "弱いぼかし" +off = "オフ" +remove = "背景を削除" + +[editor.tabs.camera.shape] +source = "元の形" +square = "正方形" + +[editor.tabs.captions] +active_word_highlight = "話している単語をハイライト" +animation_section = "アニメーション" +animation_style = "アニメーションのスタイル" +background_color = "背景色" +background_opacity = "背景の不透明度" +background_settings = "背景の設定" +beta = "ベータ" +delete = "削除" +deleting = "削除中..." +download = "{model}モデルをダウンロード" +download_note = "システムへのダウンロードは1回だけです。字幕はすべてローカルに保存されます。" +downloading = "{model}をダウンロード中... {percent}%" +fade_duration = "フェード時間" +font_family = "フォント" +font_settings = "フォントの設定" +font_weight = "フォントの太さ" +generate = "字幕を生成" +generate_failed = "字幕を生成できませんでした:{reason}" +generating = "生成中..." +highlight_color = "ハイライトの色" +highlight_note = "これはCapの字幕機能の最初のバージョンです。話している単語のハイライトは、状況によって不正確になる場合があります。修正に取り組んでおり、今後のバージョンで公開する予定です。" +highlight_style = "ハイライトのスタイル" +horizontal_position = "水平位置" +intel_unavailable = "IntelのMacではParakeetの字幕モデルを利用できません。Whisperモデルは引き続き利用できます。" +keep_open = "モデルのダウンロード中はCapを開いたままにしてください。エディタを再読み込みしても自動的に再接続されます。" +language_label = "言語" +model_downloaded = "{model}モデルをダウンロード済み" +model_label = "モデル" +position = "位置" +regenerate = "字幕を再生成" +size_label = "サイズ" +style = "スタイル" +text_color = "テキストの色" +title = "字幕" +uppercase = "大文字" +vertical_position = "垂直位置" + +[editor.tabs.captions.animation] +bounce = "バウンス" +none = "なし" +pop = "ポップ" + +[editor.tabs.captions.highlight] +color = "色" +pill = "ピル" + +[editor.tabs.captions.language] +ar = "アラビア語" +auto = "自動検出" +bn = "ベンガル語" +de = "ドイツ語" +en = "英語" +es = "スペイン語" +fa = "ペルシア語" +fr = "フランス語" +gu = "グジャラート語" +he = "ヘブライ語" +hi = "ヒンディー語" +it = "イタリア語" +ja = "日本語" +ko = "韓国語" +mr = "マラーティー語" +nl = "オランダ語" +pa = "パンジャブ語" +pl = "ポーランド語" +pt = "ポルトガル語" +ru = "ロシア語" +sk = "スロバキア語" +ta = "タミル語" +te = "テルグ語" +tr = "トルコ語" +ur = "ウルドゥー語" +zh = "中国語" + +[editor.tabs.captions.model] +high_accuracy = "高精度" +high_accuracy_description = "ダウンロードは大きめ、精度は高め" +medium = "中" +medium_description = "低速だがより正確" +recommended = "推奨" +recommended_description = "ほとんどの録画に最適なバランス" +small = "小" +small_description = "最小のダウンロード" + +[editor.tabs.captions.preset] +classic = "クラシック" +classic_description = "角丸の単色背景にすっきりしたテキスト。" +highlight = "ハイライト" +highlight_description = "太字の大文字で、話している単語の後ろにピルを表示。" +karaoke = "カラオケ" +karaoke_description = "発話に合わせて単語が光ります。" +minimal = "ミニマル" +minimal_description = "背景なしの控えめな縁取りテキスト。" +pop = "ポップ" +pop_description = "鮮やかなアクセントで飛び出す遊び心のある大文字。" + +[editor.tabs.corner] +rounded = "角丸" +squircle = "スクワークル" + +[editor.tabs.cursor] +friction = "摩擦" +hide_when_idle = "操作がないときは非表示" +inactivity_delay = "非表示までの時間" +mass = "質量" +movement_style = "カーソルの動きのスタイル" +show = "カーソルを表示" +size_label = "サイズ" +smooth_movement = "なめらかな動き" +style_label = "カーソルのスタイル" +svg_cursors = "高品質なSVGカーソル" +tension = "張力" +tilt = "傾き" + +[editor.tabs.cursor.style] +fast = "速い" +fast_description = "きびきびと、ぴったり追従" +mellow = "穏やか" +mellow_description = "バランスの取れた自然な動き" +slow = "遅い" +slow_description = "長く漂うような動き" +smooth = "なめらか" +smooth_description = "柔らかく浮遊するような動き" + +[editor.tabs.keyboard] +animation = "アニメーション" +background_color = "背景色" +background_opacity = "背景の不透明度" +background_settings = "背景の設定" +behavior = "動作" +beta = "ベータ" +changed_during_generation = "生成中にタイムラインまたはキーボードの設定が変更されました。もう一度お試しください。" +empty_description = "「キーボードセグメントを生成」をクリックすると、記録されたキー入力からセグメントを作成します。" +empty_title = "キーボードセグメントはまだありません。" +fade_duration = "フェード時間" +font_family = "フォント" +font_settings = "フォントの設定" +font_weight = "フォントの太さ" +generate = "キーボードセグメントを生成" +generating = "キーボードセグメントを生成中..." +grouping_threshold = "グループ化のしきい値" +linger_duration = "表示を残す時間" +load_failed = "録画データを読み込めませんでした:{error}" +no_presses = "記録されたキー入力が見つかりませんでした。" +position = "位置" +regenerate = "キーボードセグメントを再生成" +show_modifiers = "修飾キーを表示" +show_special_keys = "特殊キーを表示" +size_label = "サイズ" +text_color = "テキストの色" +timeline_unavailable = "プロジェクトのタイムラインを利用できません。" +timeline_unreadable = "プロジェクトのタイムラインを読み込めませんでした。" +title = "キーボードを表示" +uppercase = "大文字" + +[editor.tabs.position] +bottom_center = "下中央" +bottom_left = "左下" +bottom_right = "右下" +manual = "手動" +top_center = "上中央" +top_left = "左上" +top_right = "右上" + +[editor.tabs.sync] +auto_description = "Capは音声をビデオと同期させるため、これらのオフセットを自動で計算しました。まだずれて聞こえる場合は調整してください。" +auto_synced = "自動同期済み" +camera = "カメラのオフセット" +clip = "クリップ{number}" +description = "音声やカメラが画面録画とずれる場合は、ソースのオフセットを微調整してください。" +microphone = "マイクのオフセット" +system_audio = "システム音声のオフセット" +title = "同期" + +[editor.tabs.weight] +bold = "太字" +medium = "中" +normal = "標準" + +[editor.timeline] +add_shot = "ショットを追加" +add_shot_with_look = "ショットを追加 · {look} · {seconds}秒" +paused = "一時停止中" + +[editor.timeline.empty] +add_audio = "音声を追加" +add_scene = "シーンを追加" +audio = "BGMを追加するか、自分の音声を読み込みます" +captions = "字幕なし" +keyboard = "キーボードイベントなし" +scene = "画面とカメラのレイアウトを切り替えます" + +[editor.timeline.picker] +audio = "BGMを追加するか、自分の音声を読み込みます。" +caption = "録画を自動で文字起こしし、画面上の字幕にします。" +clip = "録画した画面の映像です。" +image = "録画に画像を追加します。" +keyboard = "入力中のキー操作を画面に表示します。" +mask = "画面のプライベートな部分をぼかすか黒く塗りつぶします。" +scene = "画面とカメラのレイアウトを切り替えます。" +scene_unavailable = "シーンを使うにはカメラ付きで録画してください。" +style = "背景、カメラ、カーソルの設定を時間に合わせて変更します。" +text_overlay = "キャンバスにカスタムのテキストやタイトルを重ねます。" +three_d = "シーンを3Dの遠近感で傾けます。" +zoom = "動きに合わせたなめらかなズームイン。" + +[editor.timeline.remove] +audio = "音声トラックを削除します。" +captions = "字幕トラックを削除します。" +clip = "クリップトラックを削除します。" +image = "画像トラックを削除します。" +keyboard = "キーボードトラックを削除します。" +mask = "マスクトラックを削除します。" +scene = "シーントラックを削除します。" +style = "スタイルトラックを削除します。" +text_track = "テキストトラックを削除します。" +three_d = "3Dトラックを削除します。" +zoom = "ズームトラックを削除します。" + +[editor.timeline.scene] +camera_only = "カメラのみ" +default = "デフォルト" +floating = "フローティング" +hide_camera = "カメラを非表示" +split_screen = "分割画面" + +[editor.timeline.segment] +audio = "音声" +automatic_zoom = "自動ズーム" +caption = "字幕" +clip = "クリップ" +cursor_hidden = "カーソル非表示" +highlight = "強調" +label = "ラベル" +manual_zoom = "手動ズーム" +muted = "ミュート" +numbered_clip = "クリップ{number}" +sensitive = "機密" + +[editor.timeline.track] +audio = "音声" +captions = "字幕" +clip = "クリップ" +image = "画像" +keyboard = "キーボード" +mask = "マスク" +scene = "シーン" +style = "スタイル" +text_track = "テキスト" +video = "ビデオ" +zoom = "ズーム" + +[editor.window] +audio_filter = "音声" +audio_segment_changed = "この音声セグメントは変更されました。選択して、もう一度置き換えるファイルを選んでください。" +crop = "切り抜き" +crop_tooltip = "ビデオを切り抜く" +no_room_for_3d_shot = "別の3Dショットを入れる余地がありません" +redo = "やり直す" +save_failed = "{path}を保存できませんでした。エディタを開いたまま、もう一度お試しください:{error}" +undo = "取り消す" + +[editor.window.add_track] +button = "トラックを追加" +hide_3d = "3Dトラックを非表示にします。" +hint = "タイムラインに追加するトラックを選んでください。" + +[editor.window.aspect] +auto = "自動" +classic = "クラシック" +classic_ratio = "クラシック ⋅4:3" +square = "正方形" +square_ratio = "正方形 ⋅1:1" +tall = "縦長" +tall_ratio = "縦長 ⋅3:4" +tooltip = "アスペクト比" +vertical = "縦" +vertical_ratio = "縦 ⋅9:16" +wide = "ワイド" +wide_ratio = "ワイド ⋅16:9" + +[editor.window.audio_import] +copy_failed = "音声ファイルをコピーできませんでした:{error}" +directory_failed = "音声フォルダを作成できませんでした:{error}" +no_extension = "音声ファイルに拡張子がありません" +not_found = "音声ファイルが見つかりません:{path}" +unsupported = "対応していない音声形式です:.{extension}" + +[editor.window.busy] +clip_import = "この録画を閉じるか削除する前に、クリップの読み込みが終わるまでお待ちください。" +export = "この録画を閉じるか削除する前に、書き出しまたはそのキャンセルが終わるまでお待ちください。" +opening = "削除する前に、録画が開き終わるまでお待ちください。" +save_before_delete_failed = "削除前に録画を保存できませんでした:{error}" + +[editor.window.camera3d_lane] +add_shot = "+ ショットを追加" +auto_scene = "自動シーン" +how_many = "ショットはいくつ?" +prompt = "録画に映画のような3Dショットを追加" + +[editor.window.clip_menu] +audio = "音声" +cursor = "カーソル" +delete = "クリップを削除" +delete_only_clip = "唯一のクリップは削除できません" +hide_cursor = "カーソルを非表示" +hide_cursor_description = "このクリップの再生中はフェードアウトします" +keep_pitch = "ピッチを保持" +match_speed = "速度に合わせる" +merge_next = "次のクリップと結合" +merge_previous = "前のクリップと結合" +mute = "ミュート" +mute_clip = "クリップをミュート" +mute_clip_description = "このクリップの音声を消します" +rename = "クリップ名を変更" +speed = "速度" +split = "再生ヘッドで分割" +split_hint = "再生ヘッドをこのクリップ内に移動してください" +volume = "音量" + +[editor.window.frame] +add_tooltip = "フレームを追加" +browser = "ブラウザ" +browser_description = "アドレスバー付きのブラウザ枠" +button = "フレーム" +dark = "ダーク" +global = "グローバルフレーム · スタイルが背景を継承する箇所すべてに適用されます。" +heading = "フレーム" +light = "ライト" +macbook_description = "録画の周りにノートパソコンのベゼル" +macos_description = "信号ボタン付きのウィンドウ枠" +none = "なし" +none_description = "録画をそのまま表示" +style_changed = "このスタイルは変更されました。フレームを閉じて開き直してください。" +style_only = "スタイル{index}のみ · グローバル設定は変わりません。" +style_only_override = "スタイル{index}のみ · 編集すると独自の背景設定が有効になります。" +theme = "テーマ" +title = "タイトル" +url = "URL" +window_title_placeholder = "ウィンドウタイトル" +windows_description = "ウィンドウボタン付きのタイトルバー" + +[editor.window.header] +captions = "字幕" +delete_recording = "録画を削除" +export = "書き出し" +open_bundle = "録画バンドルを開く" +presets = "プリセット" + +[editor.window.image] +changed = "置き換えを選んでいる間にこの画像が変更されました。選択してもう一度お試しください。" +filter = "画像" +recording_unavailable = "この画像の録画は利用できなくなりました。プロジェクトを開き直してください。" + +[editor.window.open] +damaged = "この録画のビデオトラックを開けませんでした。バンドルが破損しているようです。" +failed = "この録画を開けませんでした" +media_failed = "この録画のメディアを読み込めませんでした:{error}" +meta_failed = "録画のメタデータを読み込めませんでした:{error}" +no_segments = "録画にセグメントがありません。先に復元が必要な可能性があります。" +not_found = "ビデオのパス{path}が見つかりません!" +not_studio = "Studio以外の録画は編集できません" + +[editor.window.presets] +apply = "適用" +create = "作成" +create_new = "新しいプリセットを作成" +create_title = "プリセットを作成" +default = "デフォルト" +default_badge = "デフォルト" +delete = "削除" +delete_confirm = "「{name}」を削除してもよろしいですか?" +delete_title = "プリセットを削除" +name_placeholder = "プリセット名" +none_saved = "保存済みのプリセットはありません" +rename = "名前を変更" +rename_title = "プリセット名を変更" +save_settings = "設定をプリセットに保存" +set_default = "デフォルトに設定" + +[editor.window.preview] +full = "フル" +half = "1/2" +label = "プレビュー" +quarter = "1/4" + +[editor.window.text_menu] +copy_style = "スタイルをコピー" +copy_style_first = "先にスタイルをコピーしてください" +delete_segment = "テキストセグメントを削除" +delete_segments = "テキストセグメントを削除" +paste_style = "スタイルをペースト" +paste_style_all = "選択したすべてにスタイルをペースト" +segments = { other = "{count}個のテキストセグメント" } +untitled = "テキスト" + +[editor.window.title] +not_saved = "タイトルを保存できませんでした" +not_saved_message = "{error}\n\nタイトルはエディタに残っています。もう一度保存してください。" +read_failed = "{path}の録画タイトルを読み込めませんでした:{error}" +save_failed = "{path}の録画タイトルを保存できませんでした:{error}" + +[editor.window.track] +clear_all = "すべて消去" +delete = "削除" + +[editor.window.transport] +done_splitting = "分割を終了" +play_pause = "ビデオを再生/一時停止" +split = "分割" +split_tooltip = "セグメントをクリックして分割します。Escで終了します。" +zoom_in = "ズームイン" +zoom_out = "ズームアウト" + +[editor.window.zoom_lane] +generate = "生成" +generating = "生成中…" +no_clicks = "ズームするクリックが見つかりません。レーン上をドラッグして追加してください。" +prompt = "ズームを自動で追加" + +[ui] +coming_soon = "近日公開" + +[ui.selection] +audio = { other = "{count}個の音声セグメントを選択中" } +caption = { other = "{count}個の字幕セグメントを選択中" } +delete = "削除" +done = "完了" +image = { other = "{count}個の画像セグメントを選択中" } +keyboard = { other = "{count}個のキーボードセグメントを選択中" } +mask = { other = "{count}個のマスクセグメントを選択中" } +scene = { other = "{count}個のシーンセグメントを選択中" } +segments = { other = "{count}個のセグメントを選択中" } +select_all = "すべて選択" +style = { other = "{count}個のスタイルセグメントを選択中" } +text_segments = { other = "{count}個のテキストセグメントを選択中" } +three_d = { other = "{count}個の3Dセグメントを選択中" } +zoom = { other = "{count}個を選択中" } +zoom_all = { other = "{count}個すべてを選択中" } +zoom_some = { other = "{total}個中{count}個を選択中" } diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index faaf888950e..8aebd8306d1 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -586,3 +586,670 @@ click_or_press = "点击此处,或按下" get_started = "开始使用" space_key = "空格" tagline = "人人可用的开源屏幕分享应用。" + +[editor.canvas] +camera = "摄像头" +camera_follows_zoom = "摄像头大小随缩放变化;拖动即可移动" +edit_zoom = "编辑缩放" +image = "图片" +mask = "遮罩" +screen = "屏幕" +screen_locked = "缩放生效期间屏幕已锁定" +text_item = "文字" + +[editor.clips] +append_error = "无法添加片段:{message}" +append_error_title = "录制错误" +back = "返回编辑器" +cap_filter = "Cap 录制" +clip = "片段 {number}" +empty_description = "录制或导入一个片段后,它会显示在这里。" +empty_title = "还没有片段" +import = "导入" +import_error = "无法导入片段:{message}" +import_error_title = "导入错误" +import_existing = "已有的录制" +import_mp4 = "MP4 视频…" +importing = "正在导入…" +mp4_filter = "MP4 视频" +prepare_controls_failed = "无法准备录制控件:{error}" +record_new = "录制新片段" +save_before_recording_failed = "录制前无法保存当前项目:{error}" +split = "拆分 {number}" +title = "片段" + +[editor.clips.error] +cancelled = "已取消导入" +cannot_decode = "无法解码视频 {path}:{error}" +in_use = "此录制正在使用中,无法导入:{error}" +instant_source = "暂时无法将 Instant 录制导入项目" +instant_target = "Instant 模式的录制无法编辑" +into_itself = "无法将录制导入到它自身" +missing_video = "缺少视频文件:{path}" +no_duration = "导入的视频没有有效时长" +not_a_project = "请选择一个 Cap 项目文件夹" +source_empty = "源 Cap 项目没有录制分段" +source_failed = "源 Cap 项目出错:{error}" +source_recording = "源 Cap 项目仍在录制" +source_unimportable = "源 Cap 项目没有可导入的录制分段" +unfinished_target = "导入片段前,请先完成或恢复此录制" +unsupported_video = "不支持的视频文件:{path}" + +[editor.export] +back = "返回编辑器" +existing_link_description = "重新上传会用你最新的编辑替换此链接中的视频。拥有链接的所有人都会看到更新后的版本。" +existing_link_title = "更新现有链接" +failed = "导出失败" +fps = "{fps} FPS" +frames = { other = "{rendered} / {count} 帧" } +generating_preview = "正在生成预览…" +link_unavailable_cursor = "仅光标的导出只能保存到文件或剪贴板。" +link_unavailable_transparent = "透明导出只能保存到文件或剪贴板。" +organization = "组织" +preview = "预览" +preview_info = "这是从你的视频渲染出的一帧。调整设置即可查看最终导出的画质。" +preview_unavailable = "预览不可用" +reupload = "重新上传" +reupload_tooltip = "将你最新的编辑上传到同一链接" +reupload_transparent = "分享链接需要不透明的背景" +sign_in_required = "你需要登录才能分享录制" +task_failed = "导出任务失败" +title = "导出" +upgrade_message = "此功能需要升级套餐" +upgrade_title = "需要升级" +upload_failed = "无法上传录制" + +[editor.export.advanced] +bits_per_pixel = "每像素比特数" +cursor_only = "仅导出光标" +cursor_only_description = "在透明背景上保留相同的光标移动和点击" +custom_bitrate = "正在使用自定义码率" +force_ffmpeg = "强制使用 FFmpeg 解码器" +force_ffmpeg_description = "跳过硬件解码器(已启用自动回退)" +title = "高级" +transparent_mov = "导出为透明 MOV。文件较大,最适合合成或剪辑。" + +[editor.export.cta] +cancel_sign_in = "取消登录" +clipboard = "导出到剪贴板" +create_link = "创建分享链接" +file = "导出为文件" +reupload = "重新上传到同一链接" +sign_in = "登录以分享" + +[editor.export.destination] +clipboard = "剪贴板" +file = "文件" +link = "分享链接" + +[editor.export.done] +copy_link = "复制链接" +instant_hint = "如果想在停止录制时立即获得链接,下次录制请使用 Instant 模式。" +link_copied = "链接已复制" +open_file = "打开文件" +open_link = "打开链接" +retry_copy = "重新复制" +reuploaded = "你最新的编辑已在同一链接上就绪" +uploaded = "你的 Cap 已成功上传" + +[editor.export.estimate] +gigabytes = "约 {value} GB" +gigabytes_range = "约 {lower}–{upper} GB" +hours = "约 {value} 小时" +hours_range = "约 {lower}–{upper} 小时" +megabytes = "约 {value} MB" +megabytes_range = "约 {lower}–{upper} MB" +minutes = "约 {value} 分钟" +minutes_range = "约 {lower}–{upper} 分钟" +seconds = "约 {value} 秒" +seconds_range = "约 {lower}–{upper} 秒" +under_megabyte = "< 1 MB" +under_second = "< 1 秒" + +[editor.export.optimize] +description = "用软件重新编码,文件小得多(较慢)" +title = "优化文件大小" + +[editor.export.phase] +clipboard_failed = "导出完成;复制到剪贴板失败" +complete = "导出完成" +copied = "已复制到剪贴板" +copying_to_clipboard = "正在复制到剪贴板" +rendering_cursor = "正在渲染光标轨道" +rendering_gif = "正在渲染 GIF" +rendering_video = "正在渲染视频" +reupload_complete = "重新上传完成" +reuploading = "正在重新上传到你的链接" +saving_to_file = "正在保存到文件" +starting = "正在准备导出" +upload_complete = "上传完成" +uploading = "正在创建分享链接" + +[editor.export.quality] +larger = "文件更大" +maximum = "最高" +potato = "最低" +smaller = "文件更小" +social = "社交" +web = "网页" + +[editor.export.resources] +proceed = "仍然继续" +title = "检查导出资源" + +[editor.export.section] +destination = "目标" +format = "格式" +frame_rate = "帧率" +quality = "画质" +resolution = "分辨率" + +[editor.export.stats] +calculating = "正在计算…" +duration = "时长" +estimated_size = "预计大小" +export_time = "导出用时" +output = "输出" +output_value = "{width}×{height} · {fps} fps" +refining_size = "正在细化大小…" +refining_time = "正在细化用时…" +unavailable = "不可用" + +[editor.modal] +cancel_export = "取消导出" +cancel_export_message = "确定要取消导出吗?" +cancel_export_title = "取消导出?" +keep_exporting = "继续导出" +recording_retained = "录制已保留" + +[editor.tabs.audio] +isolation_balanced = "均衡" +isolation_balanced_description = "更清晰的人声分离,同时保留自然的声音细节。" +isolation_light = "轻度" +isolation_light_description = "保留更多原始人声和房间声音。" +isolation_strong = "强力" +isolation_strong_description = "为嘈杂环境提供更强分离。可能改变声音质感。" +mic_volume = "麦克风音量" +mute = "静音" +stereo_mode = "麦克风立体声模式" +studio_sound_description = "降低背景噪音并平衡你的音量。" +studio_sound_save_failed = "无法保存 Studio Sound 默认设置" +system_volume = "系统音频音量" +title = "音频控制" +use_for_new = "用于新的录制" + +[editor.tabs.audio.stereo] +mono_left = "单声道 L" +mono_right = "单声道 R" +stereo = "立体声" + +[editor.tabs.camera] +advanced_shadow = "高级阴影设置" +background = "背景" +corner_style = "圆角样式" +custom_position = "自定义位置(在画布上拖动)" +hide = "隐藏摄像头" +keep_size_during_zoom = "缩放时保持原始大小" +mirror = "镜像摄像头" +position = "位置" +reset = "重置" +rounded_corners = "圆角" +shadow = "阴影" +shadow_blur = "模糊" +shadow_opacity = "不透明度" +shape_label = "形状" +size_during_zoom = "缩放时的大小" +size_label = "大小" +title = "摄像头" + +[editor.tabs.camera.blur] +heavy = "强模糊" +light = "轻模糊" +off = "关闭" +remove = "移除背景" + +[editor.tabs.camera.shape] +source = "原始" +square = "方形" + +[editor.tabs.captions] +active_word_highlight = "高亮当前单词" +animation_section = "动画" +animation_style = "动画样式" +background_color = "背景颜色" +background_opacity = "背景不透明度" +background_settings = "背景设置" +beta = "Beta" +delete = "删除" +deleting = "正在删除..." +download = "下载{model}模型" +download_note = "只需下载到你的系统一次。所有字幕都保存在本地。" +downloading = "正在下载{model}... {percent}%" +fade_duration = "淡入淡出时长" +font_family = "字体" +font_settings = "字体设置" +font_weight = "字重" +generate = "生成字幕" +generate_failed = "无法生成字幕:{reason}" +generating = "正在生成..." +highlight_color = "高亮颜色" +highlight_note = "这是 Cap 字幕的第一个版本。在某些情况下,当前单词高亮可能不准确。我们正在修复,修复会在后续版本中发布。" +highlight_style = "高亮样式" +horizontal_position = "水平位置" +intel_unavailable = "Parakeet 字幕模型在 Intel Mac 上不可用。Whisper 模型仍可使用。" +keep_open = "下载模型时请保持 Cap 打开。编辑器重新加载后会自动重新连接。" +language_label = "语言" +model_downloaded = "已下载{model}模型" +model_label = "模型" +position = "位置" +regenerate = "重新生成字幕" +size_label = "大小" +style = "样式" +text_color = "文字颜色" +title = "字幕" +uppercase = "大写" +vertical_position = "垂直位置" + +[editor.tabs.captions.animation] +bounce = "弹跳" +none = "无" +pop = "弹出" + +[editor.tabs.captions.highlight] +color = "颜色" +pill = "胶囊" + +[editor.tabs.captions.language] +ar = "阿拉伯语" +auto = "自动检测" +bn = "孟加拉语" +de = "德语" +en = "英语" +es = "西班牙语" +fa = "波斯语" +fr = "法语" +gu = "古吉拉特语" +he = "希伯来语" +hi = "印地语" +it = "意大利语" +ja = "日语" +ko = "韩语" +mr = "马拉地语" +nl = "荷兰语" +pa = "旁遮普语" +pl = "波兰语" +pt = "葡萄牙语" +ru = "俄语" +sk = "斯洛伐克语" +ta = "泰米尔语" +te = "泰卢固语" +tr = "土耳其语" +ur = "乌尔都语" +zh = "中文" + +[editor.tabs.captions.model] +high_accuracy = "高精度" +high_accuracy_description = "下载更大,精度更高" +medium = "中" +medium_description = "较慢,更准确" +recommended = "推荐" +recommended_description = "适合大多数录制的最佳平衡" +small = "小" +small_description = "下载最小" + +[editor.tabs.captions.preset] +classic = "经典" +classic_description = "圆角纯色背景上的清晰文字。" +highlight = "高亮" +highlight_description = "粗体大写,当前单词后有胶囊背景。" +karaoke = "卡拉 OK" +karaoke_description = "单词随语音同步亮起。" +minimal = "极简" +minimal_description = "无背景的低调描边文字。" +pop = "跳跃" +pop_description = "活泼的大写文字,以鲜艳的强调色弹出。" + +[editor.tabs.corner] +rounded = "圆角" +squircle = "超椭圆" + +[editor.tabs.cursor] +friction = "摩擦" +hide_when_idle = "空闲时隐藏" +inactivity_delay = "空闲延迟" +mass = "质量" +movement_style = "光标移动样式" +show = "显示光标" +size_label = "大小" +smooth_movement = "平滑移动" +style_label = "光标样式" +svg_cursors = "高质量 SVG 光标" +tension = "张力" +tilt = "倾斜" + +[editor.tabs.cursor.style] +fast = "快速" +fast_description = "灵敏,紧密跟随" +mellow = "柔和" +mellow_description = "均衡、自然的移动" +slow = "缓慢" +slow_description = "悠长、漂移的移动" +smooth = "顺滑" +smooth_description = "柔软、轻盈的移动" + +[editor.tabs.keyboard] +animation = "动画" +background_color = "背景颜色" +background_opacity = "背景不透明度" +background_settings = "背景设置" +behavior = "行为" +beta = "Beta" +changed_during_generation = "生成过程中时间线或键盘设置发生了变化。请重试。" +empty_description = "点击“生成键盘分段”,根据录制的按键创建分段。" +empty_title = "还没有键盘分段。" +fade_duration = "淡入淡出时长" +font_family = "字体" +font_settings = "字体设置" +font_weight = "字重" +generate = "生成键盘分段" +generating = "正在生成键盘分段..." +grouping_threshold = "分组阈值" +linger_duration = "停留时长" +load_failed = "无法加载录制数据:{error}" +no_presses = "未找到录制的按键。" +position = "位置" +regenerate = "重新生成键盘分段" +show_modifiers = "显示修饰键" +show_special_keys = "显示特殊键" +size_label = "大小" +text_color = "文字颜色" +timeline_unavailable = "项目时间线不可用。" +timeline_unreadable = "无法读取项目时间线。" +title = "显示键盘" +uppercase = "大写" + +[editor.tabs.position] +bottom_center = "底部居中" +bottom_left = "左下" +bottom_right = "右下" +manual = "手动" +top_center = "顶部居中" +top_left = "左上" +top_right = "右上" + +[editor.tabs.sync] +auto_description = "Cap 已自动计算这些偏移,让音频与视频保持同步。如果听起来仍不对,请调整它们。" +auto_synced = "已自动同步" +camera = "摄像头偏移" +clip = "片段 {number}" +description = "如果音频或摄像头与屏幕录制不同步,可以微调各来源的偏移。" +microphone = "麦克风偏移" +system_audio = "系统音频偏移" +title = "同步" + +[editor.tabs.weight] +bold = "粗体" +medium = "中等" +normal = "常规" + +[editor.timeline] +add_shot = "添加镜头" +add_shot_with_look = "添加镜头 · {look} · {seconds} 秒" +paused = "已暂停" + +[editor.timeline.empty] +add_audio = "添加音频" +add_scene = "添加场景" +audio = "添加背景音乐或导入你自己的音频" +captions = "没有字幕" +keyboard = "没有键盘事件" +scene = "在屏幕和摄像头之间切换布局" + +[editor.timeline.picker] +audio = "添加背景音乐或导入你自己的音频。" +caption = "自动转写你的录制,生成屏幕字幕。" +clip = "你录制的屏幕画面。" +image = "为你的录制添加图片。" +keyboard = "在你输入时在屏幕上显示按键。" +mask = "模糊或涂黑屏幕上的隐私区域。" +scene = "在屏幕和摄像头之间切换布局。" +scene_unavailable = "使用摄像头录制后才能使用场景。" +style = "随时间改变背景、摄像头和光标设置。" +text_overlay = "在画布上添加自定义文字叠加和标题。" +three_d = "以 3D 透视倾斜场景。" +zoom = "跟随动作的平滑放大。" + +[editor.timeline.remove] +audio = "移除音频轨道。" +captions = "移除字幕轨道。" +clip = "移除片段轨道。" +image = "移除图片轨道。" +keyboard = "移除键盘轨道。" +mask = "移除遮罩轨道。" +scene = "移除场景轨道。" +style = "移除样式轨道。" +text_track = "移除文字轨道。" +three_d = "移除 3D 轨道。" +zoom = "移除缩放轨道。" + +[editor.timeline.scene] +camera_only = "仅摄像头" +default = "默认" +floating = "悬浮" +hide_camera = "隐藏摄像头" +split_screen = "分屏" + +[editor.timeline.segment] +audio = "音频" +automatic_zoom = "自动缩放" +caption = "字幕" +clip = "片段" +cursor_hidden = "已隐藏光标" +highlight = "突出显示" +label = "标签" +manual_zoom = "手动缩放" +muted = "已静音" +numbered_clip = "片段 {number}" +sensitive = "敏感" + +[editor.timeline.track] +audio = "音频" +captions = "字幕" +clip = "片段" +image = "图片" +keyboard = "键盘" +mask = "遮罩" +scene = "场景" +style = "样式" +text_track = "文字" +video = "视频" +zoom = "缩放" + +[editor.window] +audio_filter = "音频" +audio_segment_changed = "此音频分段已更改。请选中它并重新选择替换文件。" +crop = "裁剪" +crop_tooltip = "裁剪视频" +no_room_for_3d_shot = "没有空间再添加 3D 镜头" +redo = "重做" +save_failed = "无法保存 {path}。请保持编辑器打开并重试:{error}" +undo = "撤销" + +[editor.window.add_track] +button = "添加轨道" +hide_3d = "隐藏 3D 轨道。" +hint = "选择要添加到时间线的轨道。" + +[editor.window.aspect] +auto = "自动" +classic = "经典" +classic_ratio = "经典 ⋅4:3" +square = "方形" +square_ratio = "方形 ⋅1:1" +tall = "竖长" +tall_ratio = "竖长 ⋅3:4" +tooltip = "宽高比" +vertical = "竖屏" +vertical_ratio = "竖屏 ⋅9:16" +wide = "宽屏" +wide_ratio = "宽屏 ⋅16:9" + +[editor.window.audio_import] +copy_failed = "无法复制音频文件:{error}" +directory_failed = "无法创建音频文件夹:{error}" +no_extension = "音频文件没有扩展名" +not_found = "找不到音频文件:{path}" +unsupported = "不支持的音频格式:.{extension}" + +[editor.window.busy] +clip_import = "请等待片段导入完成后再关闭或删除此录制。" +export = "请等待导出或取消导出完成后再关闭或删除此录制。" +opening = "请等待录制打开完成后再删除。" +save_before_delete_failed = "删除前无法保存录制:{error}" + +[editor.window.camera3d_lane] +add_shot = "+ 添加镜头" +auto_scene = "自动场景" +how_many = "要几个镜头?" +prompt = "为你的录制添加电影感 3D 镜头" + +[editor.window.clip_menu] +audio = "音频" +cursor = "光标" +delete = "删除片段" +delete_only_clip = "无法删除唯一的片段" +hide_cursor = "隐藏光标" +hide_cursor_description = "播放此片段时淡出" +keep_pitch = "保持音调" +match_speed = "匹配速度" +merge_next = "与下一个片段合并" +merge_previous = "与上一个片段合并" +mute = "静音" +mute_clip = "静音片段" +mute_clip_description = "关闭此片段的声音" +rename = "重命名片段" +speed = "速度" +split = "在播放头处拆分" +split_hint = "将播放头移到此片段内" +volume = "音量" + +[editor.window.frame] +add_tooltip = "添加边框" +browser = "浏览器" +browser_description = "带地址栏的浏览器边框" +button = "边框" +dark = "深色" +global = "全局边框 · 应用于所有继承背景的样式。" +heading = "边框" +light = "浅色" +macbook_description = "录制画面外的笔记本边框" +macos_description = "带红绿灯按钮的窗口边框" +none = "无" +none_description = "按原样显示录制" +style_changed = "此样式已更改。请关闭并重新打开边框。" +style_only = "仅样式 {index} · 全局设置保持不变。" +style_only_override = "仅样式 {index} · 编辑会启用它自己的背景设置。" +theme = "主题" +title = "标题" +url = "URL" +window_title_placeholder = "窗口标题" +windows_description = "带窗口控制按钮的标题栏" + +[editor.window.header] +captions = "字幕" +delete_recording = "删除录制" +export = "导出" +open_bundle = "打开录制包" +presets = "预设" + +[editor.window.image] +changed = "选择替换图片时此图片已更改。请选中它后重试。" +filter = "图片" +recording_unavailable = "此图片对应的录制已不可用。请重新打开项目。" + +[editor.window.open] +damaged = "无法打开此录制的视频轨道。录制包似乎已损坏。" +failed = "无法打开此录制" +media_failed = "无法读取此录制的媒体:{error}" +meta_failed = "无法加载录制元数据:{error}" +no_segments = "录制没有分段。可能需要先恢复。" +not_found = "找不到视频路径 {path}!" +not_studio = "无法编辑非 Studio 录制" + +[editor.window.presets] +apply = "应用" +create = "创建" +create_new = "创建新预设" +create_title = "创建预设" +default = "默认" +default_badge = "默认" +delete = "删除" +delete_confirm = "确定要删除“{name}”吗?" +delete_title = "删除预设" +name_placeholder = "预设名称" +none_saved = "没有已保存的预设" +rename = "重命名" +rename_title = "重命名预设" +save_settings = "将设置保存到预设" +set_default = "设为默认" + +[editor.window.preview] +full = "全尺寸" +half = "1/2" +label = "预览" +quarter = "1/4" + +[editor.window.text_menu] +copy_style = "复制样式" +copy_style_first = "请先复制一个样式" +delete_segment = "删除文字分段" +delete_segments = "删除文字分段" +paste_style = "粘贴样式" +paste_style_all = "将样式粘贴到所有选中项" +segments = { other = "{count} 个文字分段" } +untitled = "文字" + +[editor.window.title] +not_saved = "标题未保存" +not_saved_message = "{error}\n\n你的标题仍在编辑器中。请再次尝试保存。" +read_failed = "无法读取 {path} 的录制标题:{error}" +save_failed = "无法保存 {path} 的录制标题:{error}" + +[editor.window.track] +clear_all = "全部清除" +delete = "删除" + +[editor.window.transport] +done_splitting = "完成拆分" +play_pause = "播放/暂停视频" +split = "拆分" +split_tooltip = "点击一个分段进行拆分。按 Esc 结束。" +zoom_in = "放大" +zoom_out = "缩小" + +[editor.window.zoom_lane] +generate = "生成" +generating = "正在生成…" +no_clicks = "未找到可缩放的点击。在轨道上拖动即可添加。" +prompt = "自动添加缩放" + +[ui] +coming_soon = "即将推出" + +[ui.selection] +audio = { other = "已选择 {count} 个音频分段" } +caption = { other = "已选择 {count} 个字幕分段" } +delete = "删除" +done = "完成" +image = { other = "已选择 {count} 个图片分段" } +keyboard = { other = "已选择 {count} 个键盘分段" } +mask = { other = "已选择 {count} 个遮罩分段" } +scene = { other = "已选择 {count} 个场景分段" } +segments = { other = "已选择 {count} 个分段" } +select_all = "全选" +style = { other = "已选择 {count} 个样式分段" } +text_segments = { other = "已选择 {count} 个文字分段" } +three_d = { other = "已选择 {count} 个 3D 分段" } +zoom = { other = "已选择 {count} 个" } +zoom_all = { other = "已选择全部 {count} 个" } +zoom_some = { other = "已选择 {count}/{total} 个" } diff --git a/apps/desktop-gpui/src/editor_canvas.rs b/apps/desktop-gpui/src/editor_canvas.rs index e39582dffa6..a89bade62fd 100644 --- a/apps/desktop-gpui/src/editor_canvas.rs +++ b/apps/desktop-gpui/src/editor_canvas.rs @@ -43,6 +43,7 @@ use std::cell::Cell; use std::rc::Rc; +use crate::i18n::tr; use crate::{editor_timeline::TrackKind, editor_window::EditorWindow}; use cap_project::{CameraXPosition, CameraYPosition, OverlayTrackKind, SceneMode, XY}; use cap_rendering::FrameLayout; @@ -451,11 +452,11 @@ pub enum CanvasSelection { impl CanvasSelection { fn label(self) -> SharedString { match self { - Self::Display => "Screen".into(), - Self::Camera => "Camera".into(), - Self::Mask(_) => "Mask".into(), - Self::Text(_) => "Text".into(), - Self::Image(_) => "Image".into(), + Self::Display => tr::editor::canvas::screen().into(), + Self::Camera => tr::editor::canvas::camera().into(), + Self::Mask(_) => tr::editor::canvas::mask().into(), + Self::Text(_) => tr::editor::canvas::text_item().into(), + Self::Image(_) => tr::editor::canvas::image().into(), } } @@ -1463,12 +1464,12 @@ impl EditorWindow { CanvasSelection::Display => ( self.display_draggable(), self.display_draggable(), - (!self.display_draggable()).then_some("Screen is locked while a zoom is active"), + (!self.display_draggable()).then(tr::editor::canvas::screen_locked), ), CanvasSelection::Camera => ( true, self.camera_resizable(), - (!self.camera_resizable()).then_some("Camera size follows the zoom — drag to move"), + (!self.camera_resizable()).then(tr::editor::canvas::camera_follows_zoom), ), CanvasSelection::Mask(_) | CanvasSelection::Text(_) | CanvasSelection::Image(_) => { (true, true, None) @@ -1569,7 +1570,7 @@ impl EditorWindow { .rounded(px(6.)) .bg(gpui::hsla(0., 0., 1., 0.15)) .font_weight(FontWeight::SEMIBOLD) - .child("Edit zoom") + .child(tr::editor::canvas::edit_zoom()) .on_mouse_down( MouseButton::Left, cx.listener(|this, event, _, cx| { diff --git a/apps/desktop-gpui/src/editor_clips.rs b/apps/desktop-gpui/src/editor_clips.rs index 51ea12b7551..bce9e30a274 100644 --- a/apps/desktop-gpui/src/editor_clips.rs +++ b/apps/desktop-gpui/src/editor_clips.rs @@ -44,6 +44,7 @@ use crate::{ editor_edits::{self as edits, TrackSegmentOps}, editor_timeline::{clip_timeline_offsets, format_time}, editor_window::EditorWindow, + i18n::tr, session::{Phase, RecordingSession}, theme::Theme, ui, @@ -64,7 +65,7 @@ pub(crate) fn format_clip_duration(seconds: f64) -> String { /// `clipLabel` (`:563`). pub(crate) fn clip_label(clip_index: usize) -> String { - format!("Clip {}", clip_index + 1) + tr::editor::clips::clip(clip_index + 1) } /// `segmentClipIndex` (`:565-567`). The `?? Math.min(index, ...)` fallback is @@ -91,7 +92,7 @@ pub(crate) fn segment_label(segments: &[TimelineSegment], index: usize) -> Strin if split_number == 1 { clip_label(segment_clip_index(&segments[index])) } else { - format!("Split {}", split_number - 1) + tr::editor::clips::split(split_number - 1) } } @@ -453,8 +454,8 @@ impl EditorWindow { ) -> impl IntoElement { ui::EditorButton::plain(&self.theme, "clips-pill") .left_icon("icons/clapperboard.svg") - .when(!compact, |button| button.label("Clips")) - .tooltip(&self.theme, "Clips") + .when(!compact, |button| button.label(tr::editor::clips::title())) + .tooltip(&self.theme, tr::editor::clips::title()) .pressed(self.clips.open) .disabled(!self.project_ready()) .on_click(cx.listener(|this, _, window, cx| this.toggle_clips(window, cx))) @@ -795,7 +796,7 @@ impl EditorWindow { .flex_shrink_0() .text_color(Hsla::from(theme.editor.text_2)), ) - .child("Back to editor") + .child(tr::editor::clips::back()) .on_click(cx.listener(|this, _, window, cx| this.close_clips(window, cx))) } @@ -830,7 +831,7 @@ impl EditorWindow { ui::ButtonSize::Md, ) .icon("icons/video.svg") - .label("Record a new clip") + .label(tr::editor::clips::record_new()) .height(px(40.)) .gap(px(8.)) .font_weight(FontWeight::MEDIUM) @@ -858,9 +859,9 @@ impl EditorWindow { ) .icon("icons/circle-plus.svg") .label(if self.clips.importing { - "Importing…" + tr::editor::clips::importing() } else { - "Import" + tr::editor::clips::import() }) .height(px(40.)) .gap(px(8.)) @@ -891,7 +892,7 @@ impl EditorWindow { .text_size(px(14.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.gray_12)) - .child("Clips"), + .child(tr::editor::clips::title()), ) .children((recorded_clip_count > 0).then(|| { div() @@ -959,14 +960,14 @@ impl EditorWindow { .text_size(px(14.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.gray_12)) - .child("No clips yet"), + .child(tr::editor::clips::empty_title()), ) .child( div() .max_w(px(200.)) .text_size(px(12.)) .text_color(Hsla::from(theme.gray_10)) - .child("Record or import a clip and it will show up here."), + .child(tr::editor::clips::empty_description()), ) } @@ -1287,14 +1288,14 @@ impl EditorWindow { self.pending_save().borrow_mut().flush(); if let Err(error) = self.project.write(&self.project_path) { tracing::error!("failed to persist the project config before recording: {error}"); - let message = format!("Could not save the current project before recording: {error}"); + let message = tr::editor::clips::save_before_recording_failed(error); cx.spawn(async move |_, _| show_append_error(&message)) .detach(); return false; } let main = cx.global::().main; if let Err(error) = main.update(cx, |view, _, _| view.cancel_deep_link_start()) { - let message = format!("Could not prepare the recording controls: {error}"); + let message = tr::editor::clips::prepare_controls_failed(error); cx.spawn(async move |_, _| show_append_error(&message)) .detach(); return false; @@ -1460,16 +1461,21 @@ impl EditorWindow { .text_size(px(12.)) .text_color(Hsla::from(theme.gray_12)) .child( - row("clips-import-existing", "Existing recording") - .hover(move |style| style.bg(Hsla::from(theme.gray_3))) - .on_click(cx.listener(|this, _, window, cx| { + row( + "clips-import-existing", + tr::editor::clips::import_existing(), + ) + .hover(move |style| style.bg(Hsla::from(theme.gray_3))) + .on_click(cx.listener( + |this, _, window, cx| { this.clips.import_menu = None; cx.notify(); this.pick_existing_recording(window, cx); - })), + }, + )), ) .child( - row("clips-import-mp4", "MP4 Video…") + row("clips-import-mp4", tr::editor::clips::import_mp4()) .hover(move |style| style.bg(Hsla::from(theme.gray_3))) .on_click(cx.listener(|this, _, window, cx| { this.clips.import_menu = None; @@ -1564,11 +1570,15 @@ impl EditorWindow { #[cfg(target_os = "macos")] let source = crate::platform::open_image_panel(&["mp4"]); #[cfg(target_os = "linux")] - let source = - crate::platform::open_file_panel_async(&[("MP4 Video", &["mp4"])], None, cx).await; + let source = crate::platform::open_file_panel_async( + &[(tr::editor::clips::mp4_filter(), &["mp4"])], + None, + cx, + ) + .await; #[cfg(not(any(target_os = "macos", target_os = "linux")))] let source = rfd::FileDialog::new() - .add_filter("MP4 Video", &["mp4"]) + .add_filter(tr::editor::clips::mp4_filter(), &["mp4"]) .pick_file(); if let Some(source) = source { this.update_in(cx, |this, window, cx| { @@ -1887,20 +1897,20 @@ impl PreparedMp4Import { in_flight: crate::import::InFlightImport, ) -> Result { if cancelled.load(Ordering::Acquire) { - return Err("Import cancelled".to_string()); + return Err(tr::editor::clips::error::cancelled().to_string()); } let target = target.canonicalize().map_err(|error| error.to_string())?; let upload_lock = cap_recording::upload_resume::UploadLock::acquire(&target) - .map_err(|error| format!("Cannot import while this recording is in use: {error}"))?; + .map_err(tr::editor::clips::error::in_use)?; let original_meta = std::fs::read(target.join("recording-meta.json")).map_err(|error| error.to_string())?; let mut meta = RecordingMeta::load_for_project(&target) .map_err(|error| format!("Failed to load target project metadata: {error}"))?; let RecordingMetaInner::Studio(studio) = &meta.inner else { - return Err("Instant mode recordings cannot be edited".to_string()); + return Err(tr::editor::clips::error::instant_target().to_string()); }; if !matches!(studio.status(), StudioRecordingStatus::Complete) { - return Err("Finish or recover this recording before importing a clip".to_string()); + return Err(tr::editor::clips::error::unfinished_target().to_string()); } let segments = ensure_multiple_segments(&mut meta)?.segments.clone(); let fallback_timeline = if config.timeline.is_none() { @@ -1910,7 +1920,7 @@ impl PreparedMp4Import { None }; if cancelled.load(Ordering::Acquire) { - return Err("Import cancelled".to_string()); + return Err(tr::editor::clips::error::cancelled().to_string()); } let index = u32::try_from(segments.len()).map_err(|error| error.to_string())?; let (directory, relative) = unique_segment_dir(&target, index)?; @@ -1929,7 +1939,7 @@ impl PreparedMp4Import { )?; let duration = get_video_duration_secs(&display_path)?; if !duration.is_finite() || duration <= 0.0 { - return Err("Imported video has no valid duration".to_string()); + return Err(tr::editor::clips::error::no_duration().to_string()); } let system_audio = sample_rate.map(|_| AudioMeta { path: format!("{relative}/audio.ogg").into(), @@ -1975,7 +1985,7 @@ impl PreparedMp4Import { write_config: impl FnOnce(&ProjectConfiguration, &Path) -> std::io::Result<()>, ) -> Result { if self.cancelled.load(Ordering::Acquire) { - return Err("Import cancelled".to_string()); + return Err(tr::editor::clips::error::cancelled().to_string()); } let meta_path = self.target.join("recording-meta.json"); if std::fs::read(&meta_path).map_err(|error| error.to_string())? != self.original_meta { @@ -2066,7 +2076,7 @@ async fn pick_existing_recording_path(_cx: &mut gpui::AsyncWindowContext) -> Opt #[cfg(target_os = "linux")] { crate::platform::open_file_panel_async( - &[("Cap Recording", &["cap"])], + &[(tr::editor::clips::cap_filter(), &["cap"])], Some(crate::recording::recordings_dir()), _cx, ) @@ -2076,7 +2086,7 @@ async fn pick_existing_recording_path(_cx: &mut gpui::AsyncWindowContext) -> Opt { rfd::FileDialog::new() .set_directory(crate::recording::recordings_dir()) - .add_filter("Cap Recording", &["cap"]) + .add_filter(tr::editor::clips::cap_filter(), &["cap"]) .pick_file() } } @@ -2085,8 +2095,8 @@ async fn pick_existing_recording_path(_cx: &mut gpui::AsyncWindowContext) -> Opt /// dialog this app uses where the webview has toasts. fn show_import_error(message: &str) { let _ = rfd::MessageDialog::new() - .set_title("Import Error") - .set_description(format!("Failed to import clip: {message}")) + .set_title(tr::editor::clips::import_error_title()) + .set_description(tr::editor::clips::import_error(message)) .set_level(rfd::MessageLevel::Error) .set_buttons(rfd::MessageButtons::Ok) .show(); @@ -2096,8 +2106,8 @@ fn show_import_error(message: &str) { /// stand-in. fn show_append_error(message: &str) { let _ = rfd::MessageDialog::new() - .set_title("Recording Error") - .set_description(format!("Failed to add clip: {message}")) + .set_title(tr::editor::clips::append_error_title()) + .set_description(tr::editor::clips::append_error(message)) .set_level(rfd::MessageLevel::Error) .set_buttons(rfd::MessageButtons::Ok) .show(); @@ -2422,7 +2432,7 @@ fn source_asset_path( /// single-segment studio bundle into the multi-segment shape imports extend. fn ensure_multiple_segments(meta: &mut RecordingMeta) -> Result<&mut MultipleSegments, String> { let RecordingMetaInner::Studio(studio_meta) = &mut meta.inner else { - return Err("Instant mode recordings cannot be edited".to_string()); + return Err(tr::editor::clips::error::instant_target().to_string()); }; if let StudioRecordingMeta::SingleSegment { segment } = studio_meta.as_ref() { @@ -2759,19 +2769,20 @@ fn copy_video_meta( )? else { if required { - return Err(format!( - "Missing video file: {}", - source_project_path.join(source.path.as_str()).display() + return Err(tr::editor::clips::error::missing_video( + source_project_path.join(source.path.as_str()).display(), )); } return Ok(None); }; let can_decode = probe_video_can_decode(&source_path) - .map_err(|e| format!("Cannot decode video {}: {e}", source_path.display()))?; + .map_err(|e| tr::editor::clips::error::cannot_decode(source_path.display(), e))?; if !can_decode { if required { - return Err(format!("Unsupported video file: {}", source_path.display())); + return Err(tr::editor::clips::error::unsupported_video( + source_path.display(), + )); } return Ok(None); } @@ -3099,9 +3110,8 @@ fn get_source_video_duration_secs( VIDEO_IMPORT_EXTENSIONS, )? .ok_or_else(|| { - format!( - "Missing video file: {}", - source_meta.project_path.join(video.path.as_str()).display() + tr::editor::clips::error::missing_video( + source_meta.project_path.join(video.path.as_str()).display(), ) })?; get_video_duration_secs(&source_path) @@ -3294,31 +3304,31 @@ pub(crate) fn append_cap_project_to_editor( source_path: &Path, ) -> Result { if same_project_path(target_project_path, source_path) { - return Err("Cannot import a recording into itself".to_string()); + return Err(tr::editor::clips::error::into_itself().to_string()); } if !is_cap_project_path(source_path) { - return Err("Select a Cap project folder".to_string()); + return Err(tr::editor::clips::error::not_a_project().to_string()); } let source_meta = RecordingMeta::load_for_project(source_path) .map_err(|e| format!("Failed to load source project metadata: {e}"))?; let RecordingMetaInner::Studio(source_studio_meta) = &source_meta.inner else { - return Err("Instant recordings can't be imported into a project yet".to_string()); + return Err(tr::editor::clips::error::instant_source().to_string()); }; match source_studio_meta.status() { StudioRecordingStatus::InProgress => { - return Err("Source Cap project is still recording".to_string()); + return Err(tr::editor::clips::error::source_recording().to_string()); } StudioRecordingStatus::Failed { error } => { - return Err(format!("Source Cap project failed: {error}")); + return Err(tr::editor::clips::error::source_failed(error)); } StudioRecordingStatus::Complete | StudioRecordingStatus::NeedsRemux => {} } let source_segments = studio_segments_for_import(source_studio_meta); if source_segments.is_empty() { - return Err("Source Cap project has no recording segments".to_string()); + return Err(tr::editor::clips::error::source_empty().to_string()); } let source_timeline = source_timeline_segments_for_import(&source_meta, &source_segments)?; @@ -3384,7 +3394,7 @@ pub(crate) fn append_cap_project_to_editor( }; if copied_segments.is_empty() { - return Err("Source Cap project has no importable recording segments".to_string()); + return Err(tr::editor::clips::error::source_unimportable().to_string()); } { diff --git a/apps/desktop-gpui/src/editor_export.rs b/apps/desktop-gpui/src/editor_export.rs index e3c53ce48ab..f83852968c9 100644 --- a/apps/desktop-gpui/src/editor_export.rs +++ b/apps/desktop-gpui/src/editor_export.rs @@ -25,6 +25,7 @@ use gpui::{ }; use crate::editor_window::EditorWindow; +use crate::i18n::tr; use crate::sign_in::{SignIn, SignInEvent, SignInOrigin, SignInOutcome}; use crate::store::{self, ExportPrefs}; use crate::ui; @@ -38,11 +39,20 @@ const STAGE_PADDING_X: f32 = 24.; const STAGE_VERTICAL_CHROME: f32 = 16. + 22. + 14. + 18. + STATS_HEIGHT + 20.; const STATS_HEIGHT: f32 = 44.; const SEGMENT_HEIGHT: f32 = 26.; -const COMPRESSION_PRESETS: [(ExportCompression, &str); 4] = [ - (ExportCompression::Potato, "Potato"), - (ExportCompression::Web, "Web"), - (ExportCompression::Social, "Social"), - (ExportCompression::Maximum, "Maximum"), +const COMPRESSION_PRESETS: [(ExportCompression, fn() -> &'static str); 4] = [ + ( + ExportCompression::Potato, + tr::editor::export::quality::potato, + ), + (ExportCompression::Web, tr::editor::export::quality::web), + ( + ExportCompression::Social, + tr::editor::export::quality::social, + ), + ( + ExportCompression::Maximum, + tr::editor::export::quality::maximum, + ), ]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -57,9 +67,9 @@ impl ExportDestination { fn label(self) -> &'static str { match self { - Self::File => "File", - Self::Clipboard => "Clipboard", - Self::Link => "Shareable Link", + Self::File => tr::editor::export::destination::file(), + Self::Clipboard => tr::editor::export::destination::clipboard(), + Self::Link => tr::editor::export::destination::link(), } } @@ -453,29 +463,56 @@ fn export_file_name(pretty_name: &str, extension: &str) -> String { } fn format_estimate_range(range: [f64; 2], time: bool) -> String { + type Single = fn(f64) -> String; + type Range = fn(f64, f64) -> String; if range[1] < 1.0 { - return if time { "< 1s" } else { "< 1 MB" }.into(); + return if time { + tr::editor::export::estimate::under_second() + } else { + tr::editor::export::estimate::under_megabyte() + } + .into(); } - let (scale, unit) = if time { + let (scale, single, range_of): (f64, Single, Range) = if time { if range[1] >= 3600.0 { - (3600.0, "hr") + ( + 3600.0, + tr::editor::export::estimate::hours, + tr::editor::export::estimate::hours_range, + ) } else if range[1] >= 60.0 { - (60.0, "min") + ( + 60.0, + tr::editor::export::estimate::minutes, + tr::editor::export::estimate::minutes_range, + ) } else { - (1.0, "s") + ( + 1.0, + tr::editor::export::estimate::seconds, + tr::editor::export::estimate::seconds_range, + ) } } else if range[1] >= 1024.0 { - (1024.0, "GB") + ( + 1024.0, + tr::editor::export::estimate::gigabytes, + tr::editor::export::estimate::gigabytes_range, + ) } else { - (1.0, "MB") + ( + 1.0, + tr::editor::export::estimate::megabytes, + tr::editor::export::estimate::megabytes_range, + ) }; let precision = if scale == 1.0 { 1.0 } else { 10.0 }; let lower = ((range[0] / scale * precision).round() / precision).max(1.0 / precision); let upper = ((range[1] / scale * precision).round() / precision).max(lower); if lower == upper { - format!("~{lower} {unit}") + single(lower) } else { - format!("~{lower}–{upper} {unit}") + range_of(lower, upper) } } @@ -496,13 +533,13 @@ impl EditorWindow { .child( ui::EditorButton::plain(&self.theme, "editor-reupload") .left_icon("icons/cloud-upload.svg") - .label("Reupload") + .label(tr::editor::export::reupload()) .tooltip( &self.theme, if has_transparent_background(&self.project) { - "Share links require a background without transparency" + tr::editor::export::reupload_transparent() } else { - "Upload your latest edit to the same link" + tr::editor::export::reupload_tooltip() }, ) .disabled(!self.project_ready() || has_transparent_background(&self.project)) @@ -723,7 +760,7 @@ impl EditorWindow { ui.preview_error = Some(error.to_string()); } None => { - ui.preview_error = Some("Preview unavailable".into()); + ui.preview_error = Some(tr::editor::export::preview_unavailable().into()); } }); if !updated { @@ -1012,7 +1049,7 @@ impl EditorWindow { let _ = this.update(cx, |this, cx| { if let Some(ui) = this.export.as_mut() { ui.phase = ExportPhase::Failed; - ui.error = Some("Export task failed".into()); + ui.error = Some(tr::editor::export::task_failed().into()); } cx.notify(); }); @@ -1287,7 +1324,7 @@ impl EditorWindow { show_upload_failure( &this, cx, - "You need to sign in to share recordings", + tr::editor::export::sign_in_required(), true, ); } @@ -1302,8 +1339,8 @@ impl EditorWindow { cx.open_url(crate::auth::PRICING_URL); }); platform::alert_dialog( - "Upgrade required", - "This feature requires an upgraded plan", + tr::editor::export::upgrade_title(), + tr::editor::export::upgrade_message(), ); } Ok(Err(error)) => { @@ -1320,7 +1357,12 @@ impl EditorWindow { } Err(error) => { tracing::error!(%error, "share upload task ended without a result"); - show_upload_failure(&this, cx, "Failed to upload recording", true); + show_upload_failure( + &this, + cx, + tr::editor::export::upload_failed(), + true, + ); } } } @@ -1344,12 +1386,12 @@ impl EditorWindow { cx.notify(); }); if !cancelled { - platform::alert_dialog("Export failed", &error); + platform::alert_dialog(tr::editor::export::failed(), &error); } } Err(error) => { tracing::error!(%error, "share export task ended without a result"); - show_upload_failure(&this, cx, "Export task failed", false); + show_upload_failure(&this, cx, tr::editor::export::task_failed(), false); } } let _ = this.update_in(cx, |this, window, cx| { @@ -1421,7 +1463,7 @@ impl EditorWindow { .size(px(14.)) .text_color(Hsla::from(editor.text_2)), ) - .child("Back to editor") + .child(tr::editor::export::back()) .on_click(cx.listener(|this, _, window, cx| { this.close_export(window, cx); })), @@ -1433,7 +1475,7 @@ impl EditorWindow { .text_size(px(13.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(editor.text_1)) - .child("Export"), + .child(tr::editor::export::title()), ) .child(div().flex_1()); #[cfg(target_os = "windows")] @@ -1539,7 +1581,7 @@ impl EditorWindow { .child( ui.preview_error .clone() - .unwrap_or_else(|| "Generating preview…".into()), + .unwrap_or_else(|| tr::editor::export::generating_preview().into()), ) .into_any_element(), }; @@ -1565,7 +1607,7 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(editor.text_2)) - .child("Preview"), + .child(tr::editor::export::preview()), ) .child( div() @@ -1573,12 +1615,8 @@ impl EditorWindow { .flex() .items_center() .tooltip(move |_window, cx| { - crate::ui::Tooltip::new( - &theme, - "This is a rendered frame from your video. Adjust the \ - settings to see the quality of the final export.", - ) - .view(cx) + crate::ui::Tooltip::new(&theme, tr::editor::export::preview_info()) + .view(cx) }) .child( svg() @@ -1611,24 +1649,28 @@ impl EditorWindow { .shadow(editor.card_shadow()) .child(export_stat( &theme, - "Duration", + tr::editor::export::stats::duration(), stats.map(|_| format_duration(duration)), false, )) .child(export_stat( &theme, - "Output", + tr::editor::export::stats::output(), stats.map(|stats| { - format!("{}×{} · {} fps", stats.width, stats.height, ui.fps) + tr::editor::export::stats::output_value( + stats.width, + stats.height, + ui.fps, + ) }), true, )) .child(export_stat( &theme, if ui.estimate_loading && ui.estimate.is_some() { - "Refining size…" + tr::editor::export::stats::refining_size() } else { - "Estimated size" + tr::editor::export::stats::estimated_size() }, Some( ui.estimate @@ -1638,9 +1680,9 @@ impl EditorWindow { }) .unwrap_or_else(|| { if ui.estimate_loading { - "Calculating…".into() + tr::editor::export::stats::calculating().into() } else { - "Unavailable".into() + tr::editor::export::stats::unavailable().into() } }), ), @@ -1649,9 +1691,9 @@ impl EditorWindow { .child(export_stat( &theme, if ui.estimate_loading && ui.estimate.is_some() { - "Refining time…" + tr::editor::export::stats::refining_time() } else { - "Export time" + tr::editor::export::stats::export_time() }, Some( ui.estimate @@ -1661,9 +1703,9 @@ impl EditorWindow { }) .unwrap_or_else(|| { if ui.estimate_loading { - "Calculating…".into() + tr::editor::export::stats::calculating().into() } else { - "Unavailable".into() + tr::editor::export::stats::unavailable().into() } }), ), @@ -1729,17 +1771,29 @@ impl EditorWindow { let signed_in = !ui.auth_rejected && store::auth_snapshot().signed_in(); let sign_in_pending = SignIn::global(cx).read(cx).is_pending(); let (label, icon, primary) = match ui.destination { - ExportDestination::File => ("Export to File", Some("icons/folder.svg"), true), - ExportDestination::Clipboard => ("Export to Clipboard", Some("icons/copy.svg"), true), - ExportDestination::Link if sign_in_pending => ("Cancel Sign In", None, false), - ExportDestination::Link if !signed_in => { - ("Sign in to share", Some("icons/link.svg"), true) + ExportDestination::File => ( + tr::editor::export::cta::file(), + Some("icons/folder.svg"), + true, + ), + ExportDestination::Clipboard => ( + tr::editor::export::cta::clipboard(), + Some("icons/copy.svg"), + true, + ), + ExportDestination::Link if sign_in_pending => { + (tr::editor::export::cta::cancel_sign_in(), None, false) } + ExportDestination::Link if !signed_in => ( + tr::editor::export::cta::sign_in(), + Some("icons/link.svg"), + true, + ), ExportDestination::Link => ( if self.sharing.is_some() { - "Reupload to same link" + tr::editor::export::cta::reupload() } else { - "Create shareable link" + tr::editor::export::cta::create_link() }, Some("icons/link.svg"), true, @@ -1809,178 +1863,180 @@ impl EditorWindow { ) -> impl IntoElement { let theme = self.theme; let editor = theme.editor; - self.export_section("Destination", "icons/upload-arrow.svg") - .child( - ui::SegmentedControl::editor( - &theme, - "export-destination", - ExportDestination::ALL - .iter() - .map(|dest| { - let label = if *dest == ExportDestination::Link - && self.sharing.is_some() - { - "Reupload" - } else { - dest.label() - }; - let mut option = - ui::SegmentOption::new(label, ui.destination == *dest) - .disabled(*dest == ExportDestination::Link && link_disabled); - option.icon = Some(dest.icon().into()); - option - }) - .collect(), - ) - .stretch() - .item_height(px(30.)) - .on_select(cx.listener(|this, index: &usize, window, cx| { - let Some(dest) = ui::option_at(ExportDestination::ALL, *index) else { + self.export_section( + tr::editor::export::section::destination(), + "icons/upload-arrow.svg", + ) + .child( + ui::SegmentedControl::editor( + &theme, + "export-destination", + ExportDestination::ALL + .iter() + .map(|dest| { + let label = if *dest == ExportDestination::Link && self.sharing.is_some() { + tr::editor::export::reupload() + } else { + dest.label() + }; + let mut option = ui::SegmentOption::new(label, ui.destination == *dest) + .disabled(*dest == ExportDestination::Link && link_disabled); + option.icon = Some(dest.icon().into()); + option + }) + .collect(), + ) + .stretch() + .item_height(px(30.)) + .on_select(cx.listener(|this, index: &usize, window, cx| { + let Some(dest) = ui::option_at(ExportDestination::ALL, *index) else { + return; + }; + if let Some(ui) = this.export.as_mut() { + if dest == ExportDestination::Link + && (has_transparent_background(&this.project) || ui.cursor_only) + { return; - }; - if let Some(ui) = this.export.as_mut() { - if dest == ExportDestination::Link - && (has_transparent_background(&this.project) || ui.cursor_only) - { - return; - } - ui.destination = dest; - if dest == ExportDestination::Link && ui.format == ExportFormatKind::Gif { - ui.format = ExportFormatKind::Mp4; - } - ui.persist(); } - this.refresh_export_preview(window, cx); - cx.notify(); - })), + ui.destination = dest; + if dest == ExportDestination::Link && ui.format == ExportFormatKind::Gif { + ui.format = ExportFormatKind::Mp4; + } + ui.persist(); + } + this.refresh_export_preview(window, cx); + cx.notify(); + })), + ) + .when(link_disabled, |field| { + field.child( + div() + .text_size(px(11.)) + .text_color(Hsla::from(editor.text_3)) + .child(if ui.cursor_only { + tr::editor::export::link_unavailable_cursor() + } else { + tr::editor::export::link_unavailable_transparent() + }), ) - .when(link_disabled, |field| { + }) + .when( + ui.destination == ExportDestination::Link && self.sharing.is_some(), + |field| { + let link = self + .sharing + .as_ref() + .map(|sharing| sharing.link.clone()) + .unwrap_or_default(); field.child( div() - .text_size(px(11.)) - .text_color(Hsla::from(editor.text_3)) - .child(if ui.cursor_only { - "Cursor-only exports can only be saved to a file or clipboard." - } else { - "Transparent exports can only be saved to a file or clipboard." - }), + .p(px(12.)) + .rounded(px(10.)) + .bg(Hsla::from(editor.card_2)) + .flex() + .flex_col() + .gap(px(4.)) + .child( + div() + .text_size(px(12.5)) + .font_weight(FontWeight::MEDIUM) + .text_color(Hsla::from(editor.text_1)) + .child(tr::editor::export::existing_link_title()), + ) + .child( + div() + .text_size(px(11.5)) + .line_height(px(15.)) + .text_color(Hsla::from(editor.text_3)) + .child(tr::editor::export::existing_link_description()), + ) + .child( + div() + .id("reupload-existing-link") + .text_size(px(11.5)) + .text_color(Hsla::from(editor.accent)) + .truncate() + .cursor_pointer() + .child(link.clone()) + .on_click(move |_, _, cx| cx.open_url(&link)), + ), ) - }) - .when( - ui.destination == ExportDestination::Link && self.sharing.is_some(), - |field| { - let link = self.sharing.as_ref() - .map(|sharing| sharing.link.clone()) - .unwrap_or_default(); - field.child( - div() - .p(px(12.)) - .rounded(px(10.)) - .bg(Hsla::from(editor.card_2)) - .flex() - .flex_col() - .gap(px(4.)) - .child( - div() - .text_size(px(12.5)) - .font_weight(FontWeight::MEDIUM) - .text_color(Hsla::from(editor.text_1)) - .child("Update your existing link"), - ) - .child( - div() - .text_size(px(11.5)) - .line_height(px(15.)) - .text_color(Hsla::from(editor.text_3)) - .child("Reupload replaces the video at this link with your latest edit. Everyone with the link will see the updated version."), - ) - .child( - div() - .id("reupload-existing-link") - .text_size(px(11.5)) - .text_color(Hsla::from(editor.accent)) - .truncate() - .cursor_pointer() - .child(link.clone()) - .on_click(move |_, _, cx| cx.open_url(&link)), - ), - ) - }, - ) - .when( - ui.destination == ExportDestination::Link - && self.sharing.is_none() - && store::auth_snapshot().organizations.len() > 1, - |this| { - let orgs = store::auth_snapshot().organizations; - let selected = ui - .organization_id - .clone() - .or_else(|| orgs.first().map(|org| org.id.clone())); - let label = orgs - .iter() - .find(|org| Some(org.id.as_str()) == selected.as_deref()) - .or(orgs.first()) - .map(|org| org.name.clone()) - .unwrap_or_else(|| "Organization".into()); - this.child( - div() - .id("export-organization") - .flex() - .flex_row() - .items_center() - .justify_between() - .h(px(30.)) - .px(px(10.)) - .rounded(px(7.)) - .bg(Hsla::from(editor.ctl)) - .cursor_pointer() - .hover(move |style| style.bg(Hsla::from(editor.ctl_hover))) - .on_click(cx.listener(move |this, _, _, cx| { - let orgs = store::auth_snapshot().organizations; - if orgs.is_empty() { - return; - } - if let Some(ui) = this.export.as_mut() { - let current = ui - .organization_id - .as_deref() - .or(orgs.first().map(|org| org.id.as_str())); - let index = orgs - .iter() - .position(|org| Some(org.id.as_str()) == current) - .unwrap_or(0); - let next = orgs[(index + 1) % orgs.len()].id.clone(); - ui.organization_id = Some(next); - ui.persist(); - } - cx.notify(); - })) - .child( - div() - .text_size(px(12.)) - .text_color(Hsla::from(editor.text_2)) - .child("Organization"), - ) - .child( - div() - .flex() - .flex_row() - .items_center() - .gap(px(4.)) - .text_size(px(12.)) - .text_color(Hsla::from(editor.text_1)) - .child(label) - .child( - svg() - .path("icons/caret-down.svg") - .size(px(14.)) - .text_color(Hsla::from(editor.text_3)), - ), - ), - ) - }, - ) + }, + ) + .when( + ui.destination == ExportDestination::Link + && self.sharing.is_none() + && store::auth_snapshot().organizations.len() > 1, + |this| { + let orgs = store::auth_snapshot().organizations; + let selected = ui + .organization_id + .clone() + .or_else(|| orgs.first().map(|org| org.id.clone())); + let label = orgs + .iter() + .find(|org| Some(org.id.as_str()) == selected.as_deref()) + .or(orgs.first()) + .map(|org| org.name.clone()) + .unwrap_or_else(|| tr::editor::export::organization().into()); + this.child( + div() + .id("export-organization") + .flex() + .flex_row() + .items_center() + .justify_between() + .h(px(30.)) + .px(px(10.)) + .rounded(px(7.)) + .bg(Hsla::from(editor.ctl)) + .cursor_pointer() + .hover(move |style| style.bg(Hsla::from(editor.ctl_hover))) + .on_click(cx.listener(move |this, _, _, cx| { + let orgs = store::auth_snapshot().organizations; + if orgs.is_empty() { + return; + } + if let Some(ui) = this.export.as_mut() { + let current = ui + .organization_id + .as_deref() + .or(orgs.first().map(|org| org.id.as_str())); + let index = orgs + .iter() + .position(|org| Some(org.id.as_str()) == current) + .unwrap_or(0); + let next = orgs[(index + 1) % orgs.len()].id.clone(); + ui.organization_id = Some(next); + ui.persist(); + } + cx.notify(); + })) + .child( + div() + .text_size(px(12.)) + .text_color(Hsla::from(editor.text_2)) + .child(tr::editor::export::organization()), + ) + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .text_size(px(12.)) + .text_color(Hsla::from(editor.text_1)) + .child(label) + .child( + svg() + .path("icons/caret-down.svg") + .size(px(14.)) + .text_color(Hsla::from(editor.text_3)), + ), + ), + ) + }, + ) } fn export_format_field( @@ -1991,7 +2047,7 @@ impl EditorWindow { ) -> impl IntoElement { let theme = self.theme; let options = [ExportFormatKind::Mp4, ExportFormatKind::Gif]; - self.export_section("Format", "icons/video.svg") + self.export_section(tr::editor::export::section::format(), "icons/video.svg") .disabled(locked) .child( ui::SegmentedControl::editor( @@ -2043,24 +2099,63 @@ impl EditorWindow { ) -> impl IntoElement { let theme = self.theme; let resolutions = resolutions.to_vec(); - self.export_section("Resolution", "icons/monitor-outline.svg") + self.export_section( + tr::editor::export::section::resolution(), + "icons/monitor-outline.svg", + ) + .child( + ui::SegmentedControl::editor( + &theme, + "export-resolution", + resolutions + .iter() + .map(|res| ui::SegmentOption::new(res.label(), ui.resolution == *res)) + .collect(), + ) + .stretch() + .item_height(px(SEGMENT_HEIGHT)) + .on_select(cx.listener(move |this, index: &usize, window, cx| { + let Some(resolution) = resolutions.get(*index).copied() else { + return; + }; + if let Some(ui) = this.export.as_mut() { + ui.resolution = resolution; + ui.persist(); + } + this.refresh_export_preview(window, cx); + cx.notify(); + })), + ) + } + + fn export_fps_field( + &self, + ui: &ExportUi, + options: &[u32], + cx: &mut Context, + ) -> impl IntoElement { + let theme = self.theme; + let options = options.to_vec(); + self.export_section(tr::editor::export::section::frame_rate(), "icons/gauge.svg") .child( ui::SegmentedControl::editor( &theme, - "export-resolution", - resolutions + "export-fps", + options .iter() - .map(|res| ui::SegmentOption::new(res.label(), ui.resolution == *res)) + .map(|fps| { + ui::SegmentOption::new(tr::editor::export::fps(fps), ui.fps == *fps) + }) .collect(), ) .stretch() .item_height(px(SEGMENT_HEIGHT)) .on_select(cx.listener(move |this, index: &usize, window, cx| { - let Some(resolution) = resolutions.get(*index).copied() else { + let Some(fps) = options.get(*index).copied() else { return; }; if let Some(ui) = this.export.as_mut() { - ui.resolution = resolution; + ui.fps = fps; ui.persist(); } this.refresh_export_preview(window, cx); @@ -2069,43 +2164,10 @@ impl EditorWindow { ) } - fn export_fps_field( - &self, - ui: &ExportUi, - options: &[u32], - cx: &mut Context, - ) -> impl IntoElement { - let theme = self.theme; - let options = options.to_vec(); - self.export_section("Frame rate", "icons/gauge.svg").child( - ui::SegmentedControl::editor( - &theme, - "export-fps", - options - .iter() - .map(|fps| ui::SegmentOption::new(format!("{fps} FPS"), ui.fps == *fps)) - .collect(), - ) - .stretch() - .item_height(px(SEGMENT_HEIGHT)) - .on_select(cx.listener(move |this, index: &usize, window, cx| { - let Some(fps) = options.get(*index).copied() else { - return; - }; - if let Some(ui) = this.export.as_mut() { - ui.fps = fps; - ui.persist(); - } - this.refresh_export_preview(window, cx); - cx.notify(); - })), - ) - } - fn export_quality_field(&self, ui: &ExportUi, cx: &mut Context) -> impl IntoElement { let theme = self.theme; let editor = theme.editor; - self.export_section("Quality", "icons/gem.svg") + self.export_section(tr::editor::export::section::quality(), "icons/gem.svg") .child( ui::SegmentedControl::editor( &theme, @@ -2114,7 +2176,7 @@ impl EditorWindow { .iter() .map(|(value, label)| { ui::SegmentOption::new( - *label, + label(), !ui.is_custom_bpp() && matches_compression(ui.compression, *value), ) }) @@ -2143,8 +2205,8 @@ impl EditorWindow { .px(px(2.)) .text_size(px(10.5)) .text_color(Hsla::from(editor.text_3)) - .child("Smaller file") - .child("Larger file"), + .child(tr::editor::export::quality::smaller()) + .child(tr::editor::export::quality::larger()), ) .child(self.export_optimize_row(ui, cx)) } @@ -2192,8 +2254,8 @@ impl EditorWindow { fn export_optimize_row(&self, ui: &ExportUi, cx: &mut Context) -> impl IntoElement { self.export_toggle_row( "export-optimize", - "Optimize file size", - "Re-encodes with software for much smaller files (slower)", + tr::editor::export::optimize::title(), + tr::editor::export::optimize::description(), ui.optimize_filesize, cx.listener(|this, _, window, cx| { if let Some(ui) = this.export.as_mut() { @@ -2247,7 +2309,7 @@ impl EditorWindow { .size(px(14.)) .text_color(Hsla::from(editor.text_2)), ) - .child("Advanced") + .child(tr::editor::export::advanced::title()) .child( svg() .path(if ui.advanced_open { @@ -2263,8 +2325,8 @@ impl EditorWindow { .when(ui.advanced_open, |this| { this.child(self.export_toggle_row( "export-cursor-only", - "Export cursor only", - "Keeps the same cursor motion and clicks on a transparent background", + tr::editor::export::advanced::cursor_only(), + tr::editor::export::advanced::cursor_only_description(), ui.cursor_only, cx.listener(|this, _, window, cx| { if let Some(ui) = this.export.as_mut() { @@ -2302,9 +2364,7 @@ impl EditorWindow { .text_size(px(11.5)) .line_height(px(15.)) .text_color(Hsla::from(editor.text_2)) - .child( - "Exports as a transparent MOV. Files are large and best for compositing or editing.", - ), + .child(tr::editor::export::advanced::transparent_mov()), ), ) }) @@ -2323,7 +2383,7 @@ impl EditorWindow { .min_w(px(96.)) .text_size(px(13.)) .text_color(Hsla::from(editor.text_1)) - .child("Bits per pixel"), + .child(tr::editor::export::advanced::bits_per_pixel()), ) .child( div() @@ -2351,11 +2411,13 @@ impl EditorWindow { Some(gpui::hsla(0., 0., 0., 0.12)), ) .thumb_shadow() - .on_drag_start(cx.listener( - |this, event: &MouseDownEvent, _window, cx| { - this.export_bpp_mouse_down(event, cx); - }, - )), + .on_drag_start( + cx.listener( + |this, event: &MouseDownEvent, _window, cx| { + this.export_bpp_mouse_down(event, cx); + }, + ), + ), ), ) .child( @@ -2373,15 +2435,15 @@ impl EditorWindow { div() .text_size(px(11.)) .text_color(Hsla::from(editor.text_3)) - .child("Using a custom bitrate"), + .child(tr::editor::export::advanced::custom_bitrate()), ) }) }) .when(cfg!(target_os = "macos") && show_bpp, |this| { this.child(self.export_toggle_row( "export-force-ffmpeg", - "Force FFmpeg decoder", - "Skip hardware decoder (auto-fallback enabled)", + tr::editor::export::advanced::force_ffmpeg(), + tr::editor::export::advanced::force_ffmpeg_description(), ui.force_ffmpeg, cx.listener(|this, _, window, cx| { if let Some(ui) = this.export.as_mut() { @@ -2457,28 +2519,34 @@ impl EditorWindow { Some(ui.rendered as f32 / ui.total_frames as f32) }; let heading = match ui.phase { - ExportPhase::Starting => "Preparing export", - ExportPhase::Rendering if ui.cursor_only => "Rendering cursor track", - ExportPhase::Rendering if ui.format == ExportFormatKind::Gif => "Rendering GIF", - ExportPhase::Rendering => "Rendering video", + ExportPhase::Starting => tr::editor::export::phase::starting(), + ExportPhase::Rendering if ui.cursor_only => { + tr::editor::export::phase::rendering_cursor() + } + ExportPhase::Rendering if ui.format == ExportFormatKind::Gif => { + tr::editor::export::phase::rendering_gif() + } + ExportPhase::Rendering => tr::editor::export::phase::rendering_video(), ExportPhase::Copying if ui.destination == ExportDestination::Clipboard => { - "Copying to clipboard" + tr::editor::export::phase::copying_to_clipboard() } - ExportPhase::Copying => "Saving to file", - ExportPhase::Uploading if ui.reuploading => "Reuploading to your link", - ExportPhase::Uploading => "Creating shareable link", + ExportPhase::Copying => tr::editor::export::phase::saving_to_file(), + ExportPhase::Uploading if ui.reuploading => tr::editor::export::phase::reuploading(), + ExportPhase::Uploading => tr::editor::export::phase::uploading(), ExportPhase::Done if ui.destination == ExportDestination::Clipboard => { - "Copied to clipboard" + tr::editor::export::phase::copied() } ExportPhase::Done if ui.destination == ExportDestination::Link && ui.reuploading => { - "Reupload complete" + tr::editor::export::phase::reupload_complete() + } + ExportPhase::Done if ui.destination == ExportDestination::Link => { + tr::editor::export::phase::upload_complete() } - ExportPhase::Done if ui.destination == ExportDestination::Link => "Upload complete", - ExportPhase::Done => "Export complete", + ExportPhase::Done => tr::editor::export::phase::complete(), ExportPhase::Failed if ui.clipboard_retry_path().is_some() => { - "Export complete; clipboard copy failed" + tr::editor::export::phase::clipboard_failed() } - ExportPhase::Failed => "Export failed", + ExportPhase::Failed => tr::editor::export::failed(), ExportPhase::Idle | ExportPhase::ChoosingFile => "", }; @@ -2523,30 +2591,35 @@ impl EditorWindow { .child(heading), ) .when( - ui.phase == ExportPhase::Done - && ui.destination == ExportDestination::Link, + ui.phase == ExportPhase::Done && ui.destination == ExportDestination::Link, |this| { this.child( div() .text_size(px(12.)) .text_color(Hsla::from(editor.text_2)) .child(if ui.reuploading { - "Your latest edit is ready at the same link" + tr::editor::export::done::reuploaded() } else { - "Your Cap has been uploaded successfully" + tr::editor::export::done::uploaded() }), ) }, ), ) - .when(ui.phase == ExportPhase::Rendering && ui.total_frames > 0, |this| { - this.child( - div() - .text_size(px(12.)) - .text_color(Hsla::from(editor.text_2)) - .child(format!("{} / {} frames", ui.rendered, ui.total_frames)), - ) - }) + .when( + ui.phase == ExportPhase::Rendering && ui.total_frames > 0, + |this| { + this.child( + div() + .text_size(px(12.)) + .text_color(Hsla::from(editor.text_2)) + .child(tr::editor::export::frames( + ui.total_frames as usize, + ui.rendered, + )), + ) + }, + ) .when_some(ui.error.clone(), |this, error| { this.child( div() @@ -2565,7 +2638,7 @@ impl EditorWindow { ui::ButtonVariant::Gray, ui::ButtonSize::Md, ) - .label("Cancel") + .label(tr::common::cancel()) .on_click(cx.listener(|this, _, window, cx| { let Some(ui) = this.export.as_mut() else { return; @@ -2593,10 +2666,10 @@ impl EditorWindow { #[cfg(not(target_os = "linux"))] cx.spawn_in(window, async move |this, cx| { let confirmed = platform::confirm_dialog( - "Cancel export?", - "Are you sure you want to cancel the export?", - "Cancel export", - "Keep exporting", + tr::editor::modal::cancel_export_title(), + tr::editor::modal::cancel_export_message(), + tr::editor::modal::cancel_export(), + tr::editor::modal::keep_exporting(), true, ); let _ = this.update(cx, |this, cx| { @@ -2615,98 +2688,108 @@ impl EditorWindow { .text_size(px(12.)) .text_color(Hsla::from(editor.text_2)) .text_center() - .child( - "Use Instant Mode for your next recording if you want a link the moment you stop.", - ), + .child(tr::editor::export::done::instant_hint()), ) }) - .when(ui.phase == ExportPhase::Done || ui.phase == ExportPhase::Failed, |this| { - this.when(ui.clipboard_retry_path().is_some(), |this| { - this.child( - ui::Button::plain( - &theme, - "export-retry-copy", - ui::ButtonVariant::Gray, - ui::ButtonSize::Md, - ) - .icon("icons/copy.svg") - .label("Retry Copy") - .on_click(cx.listener(|this, _, window, cx| { - this.retry_clipboard_copy(window, cx); - })), - ) - }) - .when( - (ui.phase == ExportPhase::Done && ui.destination == ExportDestination::File) - || ui.clipboard_retry_path().is_some(), - |this| { - let path = ui.output_path.clone(); + .when( + ui.phase == ExportPhase::Done || ui.phase == ExportPhase::Failed, + |this| { + this.when(ui.clipboard_retry_path().is_some(), |this| { this.child( ui::Button::plain( &theme, - "export-open-file", + "export-retry-copy", ui::ButtonVariant::Gray, ui::ButtonSize::Md, ) - .label("Open File") - .on_click(move |_, _, cx| { - if let Some(path) = &path { - library::open_with_system(path, cx); - } - }), + .icon("icons/copy.svg") + .label(tr::editor::export::done::retry_copy()) + .on_click(cx.listener( + |this, _, window, cx| { + this.retry_clipboard_copy(window, cx); + }, + )), ) - }, - ) - .when( - ui.phase == ExportPhase::Done - && ui.destination == ExportDestination::Link - && ui.share_link.is_some(), - |this| { - let copied = ui.copy_link_pressed; - let open = ui.share_link.clone().unwrap_or_default(); - this.child( - ui::Button::plain( - &theme, - "export-copy-link", - ui::ButtonVariant::Gray, - ui::ButtonSize::Md, + }) + .when( + (ui.phase == ExportPhase::Done + && ui.destination == ExportDestination::File) + || ui.clipboard_retry_path().is_some(), + |this| { + let path = ui.output_path.clone(); + this.child( + ui::Button::plain( + &theme, + "export-open-file", + ui::ButtonVariant::Gray, + ui::ButtonSize::Md, + ) + .label(tr::editor::export::done::open_file()) + .on_click(move |_, _, cx| { + if let Some(path) = &path { + library::open_with_system(path, cx); + } + }), ) - .icon(if copied { - "icons/check.svg" - } else { - "icons/copy.svg" - }) - .label(if copied { "Link copied" } else { "Copy link" }) - .on_click(cx.listener(|this, _, window, cx| { - this.copy_share_link(window, cx); - })), - ) - .child( - ui::Button::plain( - &theme, - "export-open-link", - ui::ButtonVariant::Primary, - ui::ButtonSize::Md, + }, + ) + .when( + ui.phase == ExportPhase::Done + && ui.destination == ExportDestination::Link + && ui.share_link.is_some(), + |this| { + let copied = ui.copy_link_pressed; + let open = ui.share_link.clone().unwrap_or_default(); + this.child( + ui::Button::plain( + &theme, + "export-copy-link", + ui::ButtonVariant::Gray, + ui::ButtonSize::Md, + ) + .icon(if copied { + "icons/check.svg" + } else { + "icons/copy.svg" + }) + .label(if copied { + tr::editor::export::done::link_copied() + } else { + tr::editor::export::done::copy_link() + }) + .on_click(cx.listener( + |this, _, window, cx| { + this.copy_share_link(window, cx); + }, + )), ) - .icon("icons/link.svg") - .label("Open link") - .on_click(move |_, _, cx| { - cx.open_url(&open); - }), + .child( + ui::Button::plain( + &theme, + "export-open-link", + ui::ButtonVariant::Primary, + ui::ButtonSize::Md, + ) + .icon("icons/link.svg") + .label(tr::editor::export::done::open_link()) + .on_click(move |_, _, cx| { + cx.open_url(&open); + }), + ) + }, + ) + .child( + ui::Button::plain( + &theme, + "export-back-done", + ui::ButtonVariant::Gray, + ui::ButtonSize::Md, ) - }, - ) - .child( - ui::Button::plain( - &theme, - "export-back-done", - ui::ButtonVariant::Gray, - ui::ButtonSize::Md, + .label(tr::editor::export::back()) + .on_click(cx.listener(|this, _, window, cx| this.close_export(window, cx))), ) - .label("Back to editor") - .on_click(cx.listener(|this, _, window, cx| this.close_export(window, cx))), - ) - }) + }, + ) } } @@ -2748,10 +2831,10 @@ async fn confirm_export_resources( }); let response = (current && !window.has_active_prompt()).then(|| { crate::editor_modal::confirm_action( - "Check export resources", + tr::editor::export::resources::title(), &message, - "Proceed anyway", - "Go back", + tr::editor::export::resources::proceed(), + tr::common::go_back(), window, cx, ) @@ -2785,10 +2868,10 @@ async fn confirm_export_resources( return Ok(false); } platform::confirm_dialog( - "Check export resources", + tr::editor::export::resources::title(), &message, - "Proceed anyway", - "Go back", + tr::editor::export::resources::proceed(), + tr::common::go_back(), false, ) }; @@ -2850,7 +2933,7 @@ fn show_upload_failure( cx.notify(); }); if dialog { - platform::alert_dialog("Failed to upload recording", &message); + platform::alert_dialog(tr::editor::export::upload_failed(), &message); } } @@ -2864,6 +2947,7 @@ fn export_stat( div() .flex() .flex_1() + .min_w_0() .flex_col() .justify_center() .px(px(16.)) @@ -2876,6 +2960,7 @@ fn export_stat( .text_size(px(10.5)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(editor.text_3)) + .truncate() .child(label), ) .child(match value { @@ -3001,6 +3086,20 @@ async fn run_export( #[cfg(test)] mod tests { + #[test] + fn estimate_ranges_keep_their_english_wording() { + use super::format_estimate_range; + + assert_eq!(format_estimate_range([0.2, 0.5], false), "< 1 MB"); + assert_eq!(format_estimate_range([0.2, 0.5], true), "< 1s"); + assert_eq!(format_estimate_range([12.0, 12.0], false), "~12 MB"); + assert_eq!(format_estimate_range([12.3, 40.7], false), "~12–41 MB"); + assert_eq!(format_estimate_range([2048.0, 3500.0], false), "~2–3.4 GB"); + assert_eq!(format_estimate_range([30.0, 45.0], true), "~30–45 s"); + assert_eq!(format_estimate_range([90.0, 150.0], true), "~1.5–2.5 min"); + assert_eq!(format_estimate_range([7200.0, 7200.0], true), "~2 hr"); + } + use super::{ ExportDestination, ExportPhase, ExportUi, cancel_matching_export, export_file_name, }; diff --git a/apps/desktop-gpui/src/editor_modal.rs b/apps/desktop-gpui/src/editor_modal.rs index 245bbef6254..2068338b8af 100644 --- a/apps/desktop-gpui/src/editor_modal.rs +++ b/apps/desktop-gpui/src/editor_modal.rs @@ -7,7 +7,7 @@ use gpui::{ div, prelude::FluentBuilder, px, }; -use crate::{theme::Theme, ui}; +use crate::{i18n::tr, theme::Theme, ui}; #[derive(Clone, Copy)] enum MessageKind { @@ -20,7 +20,8 @@ pub(crate) fn retained_alert( window: &mut Window, cx: &mut App, ) -> impl Future + use<> { - let response = informational_alert("Recording retained", message, window, cx); + let response = + informational_alert(tr::editor::modal::recording_retained(), message, window, cx); async move { let _ = response.await; } @@ -36,7 +37,7 @@ pub(crate) fn informational_alert( MessageKind::Retained, title, message, - vec![PromptButton::ok("OK")], + vec![PromptButton::ok(tr::common::ok())], window, cx, ); @@ -48,10 +49,10 @@ pub(crate) fn confirm_cancel_export( cx: &mut App, ) -> impl Future + use<> { confirm_action( - "Cancel export?", - "Are you sure you want to cancel the export?", - "Cancel export", - "Keep exporting", + tr::editor::modal::cancel_export_title(), + tr::editor::modal::cancel_export_message(), + tr::editor::modal::cancel_export(), + tr::editor::modal::keep_exporting(), window, cx, ) diff --git a/apps/desktop-gpui/src/editor_tabs.rs b/apps/desktop-gpui/src/editor_tabs.rs index 7801a8d0f4a..97b0ef8bf69 100644 --- a/apps/desktop-gpui/src/editor_tabs.rs +++ b/apps/desktop-gpui/src/editor_tabs.rs @@ -38,6 +38,7 @@ use crate::{ editor_color::GradeTarget, editor_sidebar::{SliderKey, collapsible, dashed_divider}, editor_window::EditorWindow, + i18n::tr, store, theme::Theme, transcription, ui, @@ -47,128 +48,160 @@ use crate::{ // Catalogues // --------------------------------------------------------------------------- +/// A catalogue label, read in the current language each time it is drawn. +pub type Label = fn() -> &'static str; + /// `CAMERA_SHAPES` (`ConfigSidebar.tsx:381-391`). -pub const CAMERA_SHAPES: [(CameraShape, &str); 2] = [ - (CameraShape::Square, "Square"), - (CameraShape::Source, "Source"), +pub const CAMERA_SHAPES: [(CameraShape, Label); 2] = [ + (CameraShape::Square, tr::editor::tabs::camera::shape::square), + (CameraShape::Source, tr::editor::tabs::camera::shape::source), ]; -pub const CAMERA_BLUR_MODES: &[(BackgroundBlurMode, &str)] = &[ - (BackgroundBlurMode::Off, "Off"), - (BackgroundBlurMode::Light, "Light Blur"), - (BackgroundBlurMode::Heavy, "Heavy Blur"), +pub const CAMERA_BLUR_MODES: &[(BackgroundBlurMode, Label)] = &[ + (BackgroundBlurMode::Off, tr::editor::tabs::camera::blur::off), + ( + BackgroundBlurMode::Light, + tr::editor::tabs::camera::blur::light, + ), + ( + BackgroundBlurMode::Heavy, + tr::editor::tabs::camera::blur::heavy, + ), #[cfg(target_os = "macos")] - (BackgroundBlurMode::Remove, "Remove Background"), + ( + BackgroundBlurMode::Remove, + tr::editor::tabs::camera::blur::remove, + ), ]; /// `CORNER_STYLE_OPTIONS` (`:399-402`). -pub const CORNER_STYLES: [(CornerStyle, &str); 2] = [ - (CornerStyle::Squircle, "Squircle"), - (CornerStyle::Rounded, "Rounded"), +pub const CORNER_STYLES: [(CornerStyle, Label); 2] = [ + (CornerStyle::Squircle, tr::editor::tabs::corner::squircle), + (CornerStyle::Rounded, tr::editor::tabs::corner::rounded), ]; /// `STEREO_MODES` (`:375-379`). -pub const STEREO_MODES: [(StereoMode, &str); 3] = [ - (StereoMode::Stereo, "Stereo"), - (StereoMode::MonoL, "Mono L"), - (StereoMode::MonoR, "Mono R"), +pub const STEREO_MODES: [(StereoMode, Label); 3] = [ + (StereoMode::Stereo, tr::editor::tabs::audio::stereo::stereo), + ( + StereoMode::MonoL, + tr::editor::tabs::audio::stereo::mono_left, + ), + ( + StereoMode::MonoR, + tr::editor::tabs::audio::stereo::mono_right, + ), ]; /// `CURSOR_ANIMATION_STYLE_OPTIONS` (`:434-476`) -- the four presets plus the /// `custom` value the physics sliders drop the picker into. -pub const CURSOR_STYLES: [(CursorAnimationStyle, &str, &str); 4] = [ +pub const CURSOR_STYLES: [(CursorAnimationStyle, Label, Label); 4] = [ ( CursorAnimationStyle::Slow, - "Slow", - "Long, drifting movement", + tr::editor::tabs::cursor::style::slow, + tr::editor::tabs::cursor::style::slow_description, ), ( CursorAnimationStyle::Smooth, - "Smooth", - "Soft, floaty movement", + tr::editor::tabs::cursor::style::smooth, + tr::editor::tabs::cursor::style::smooth_description, ), ( CursorAnimationStyle::Mellow, - "Mellow", - "Balanced, natural movement", + tr::editor::tabs::cursor::style::mellow, + tr::editor::tabs::cursor::style::mellow_description, + ), + ( + CursorAnimationStyle::Fast, + tr::editor::tabs::cursor::style::fast, + tr::editor::tabs::cursor::style::fast_description, ), - (CursorAnimationStyle::Fast, "Fast", "Snappy, close tracking"), ]; /// `FONT_OPTIONS` (`text-style.tsx:12-16`). pub const FONT_OPTIONS: [&str; 3] = ["System Sans-Serif", "System Serif", "System Monospace"]; /// `TEXT_WEIGHT_OPTIONS` (`text-style.tsx:36-40`). -pub const TEXT_WEIGHTS: [(u32, &str); 3] = [(400, "Normal"), (500, "Medium"), (700, "Bold")]; +pub const TEXT_WEIGHTS: [(u32, Label); 3] = [ + (400, tr::editor::tabs::weight::normal), + (500, tr::editor::tabs::weight::medium), + (700, tr::editor::tabs::weight::bold), +]; /// `KEYBOARD_POSITION_OPTIONS` (`text-style.tsx:28-35`). -pub const KEYBOARD_POSITIONS: [(&str, &str); 6] = [ - ("top-left", "Top Left"), - ("top-center", "Top Center"), - ("top-right", "Top Right"), - ("bottom-left", "Bottom Left"), - ("bottom-center", "Bottom Center"), - ("bottom-right", "Bottom Right"), +pub const KEYBOARD_POSITIONS: [(&str, Label); 6] = [ + ("top-left", tr::editor::tabs::position::top_left), + ("top-center", tr::editor::tabs::position::top_center), + ("top-right", tr::editor::tabs::position::top_right), + ("bottom-left", tr::editor::tabs::position::bottom_left), + ("bottom-center", tr::editor::tabs::position::bottom_center), + ("bottom-right", tr::editor::tabs::position::bottom_right), ]; /// `CAPTION_POSITION_OPTIONS` (`text-style.tsx:18-26`) -- the keyboard's six /// with `manual` in front. -pub const CAPTION_POSITIONS: [(&str, &str); 7] = [ - ("manual", "Manual"), - ("top-left", "Top Left"), - ("top-center", "Top Center"), - ("top-right", "Top Right"), - ("bottom-left", "Bottom Left"), - ("bottom-center", "Bottom Center"), - ("bottom-right", "Bottom Right"), +pub const CAPTION_POSITIONS: [(&str, Label); 7] = [ + ("manual", tr::editor::tabs::position::manual), + ("top-left", tr::editor::tabs::position::top_left), + ("top-center", tr::editor::tabs::position::top_center), + ("top-right", tr::editor::tabs::position::top_right), + ("bottom-left", tr::editor::tabs::position::bottom_left), + ("bottom-center", tr::editor::tabs::position::bottom_center), + ("bottom-right", tr::editor::tabs::position::bottom_right), ]; /// `CAPTION_ANIMATION_OPTIONS` (`text-style.tsx:64-68`). -pub const CAPTION_ANIMATIONS: [(&str, &str); 3] = - [("none", "None"), ("bounce", "Bounce"), ("pop", "Pop")]; +pub const CAPTION_ANIMATIONS: [(&str, Label); 3] = [ + ("none", tr::editor::tabs::captions::animation::none), + ("bounce", tr::editor::tabs::captions::animation::bounce), + ("pop", tr::editor::tabs::captions::animation::pop), +]; /// `CAPTION_HIGHLIGHT_STYLE_OPTIONS` (`text-style.tsx:70-73`). -pub const CAPTION_HIGHLIGHT_STYLES: [(&str, &str); 2] = [("color", "Color"), ("pill", "Pill")]; +pub const CAPTION_HIGHLIGHT_STYLES: [(&str, Label); 2] = [ + ("color", tr::editor::tabs::captions::highlight::color), + ("pill", tr::editor::tabs::captions::highlight::pill), +]; /// One row of `MODEL_OPTIONS` (`CaptionsTab.tsx:72-78`). pub struct CaptionModel { pub name: &'static str, - pub label: &'static str, + pub label: Label, /// The engine identity the source shows in an info tooltip. pub model_name: &'static str, pub size: &'static str, - pub description: &'static str, + pub description: Label, } /// `MODEL_OPTIONS` (`CaptionsTab.tsx:87-116`). pub static CAPTION_MODELS: &[CaptionModel] = &[ CaptionModel { name: "best", - label: "Recommended", + label: tr::editor::tabs::captions::model::recommended, model_name: "parakeet-tdt-0.6b-v3 int8", size: "~640MB", - description: "Best balance for most recordings", + description: tr::editor::tabs::captions::model::recommended_description, }, CaptionModel { name: "best-max", - label: "High Accuracy", + label: tr::editor::tabs::captions::model::high_accuracy, model_name: "parakeet-tdt-0.6b-v3", size: "~2.4GB", - description: "Larger download, higher accuracy", + description: tr::editor::tabs::captions::model::high_accuracy_description, }, CaptionModel { name: "small", - label: "Small", + label: tr::editor::tabs::captions::model::small, model_name: "whisper.cpp small", size: "466MB", - description: "Smallest download", + description: tr::editor::tabs::captions::model::small_description, }, CaptionModel { name: "medium", - label: "Medium", + label: tr::editor::tabs::captions::model::medium, model_name: "whisper.cpp medium", size: "1.5GB", - description: "Slower, more accurate", + description: tr::editor::tabs::captions::model::medium_description, }, ]; @@ -185,40 +218,40 @@ pub fn available_caption_models() -> &'static [CaptionModel] { /// `LANGUAGE_OPTIONS` (`CaptionsTab.tsx:118-148`), de-duplicated: the array /// repeats `ar`/`hi`/`bn`/`ta` at the end, and a Kobalte listbox keyed on the /// code renders each once. -pub const CAPTION_LANGUAGES: [(&str, &str); 26] = [ - ("auto", "Auto Detect"), - ("en", "English"), - ("es", "Spanish"), - ("fr", "French"), - ("de", "German"), - ("it", "Italian"), - ("pt", "Portuguese"), - ("nl", "Dutch"), - ("pl", "Polish"), - ("ru", "Russian"), - ("sk", "Slovak"), - ("tr", "Turkish"), - ("ja", "Japanese"), - ("ko", "Korean"), - ("zh", "Chinese"), - ("ar", "Arabic"), - ("hi", "Hindi"), - ("bn", "Bengali"), - ("ta", "Tamil"), - ("te", "Telugu"), - ("mr", "Marathi"), - ("gu", "Gujarati"), - ("pa", "Punjabi"), - ("ur", "Urdu"), - ("fa", "Persian"), - ("he", "Hebrew"), +pub const CAPTION_LANGUAGES: [(&str, Label); 26] = [ + ("auto", tr::editor::tabs::captions::language::auto), + ("en", tr::editor::tabs::captions::language::en), + ("es", tr::editor::tabs::captions::language::es), + ("fr", tr::editor::tabs::captions::language::fr), + ("de", tr::editor::tabs::captions::language::de), + ("it", tr::editor::tabs::captions::language::it), + ("pt", tr::editor::tabs::captions::language::pt), + ("nl", tr::editor::tabs::captions::language::nl), + ("pl", tr::editor::tabs::captions::language::pl), + ("ru", tr::editor::tabs::captions::language::ru), + ("sk", tr::editor::tabs::captions::language::sk), + ("tr", tr::editor::tabs::captions::language::tr), + ("ja", tr::editor::tabs::captions::language::ja), + ("ko", tr::editor::tabs::captions::language::ko), + ("zh", tr::editor::tabs::captions::language::zh), + ("ar", tr::editor::tabs::captions::language::ar), + ("hi", tr::editor::tabs::captions::language::hi), + ("bn", tr::editor::tabs::captions::language::bn), + ("ta", tr::editor::tabs::captions::language::ta), + ("te", tr::editor::tabs::captions::language::te), + ("mr", tr::editor::tabs::captions::language::mr), + ("gu", tr::editor::tabs::captions::language::gu), + ("pa", tr::editor::tabs::captions::language::pa), + ("ur", tr::editor::tabs::captions::language::ur), + ("fa", tr::editor::tabs::captions::language::fa), + ("he", tr::editor::tabs::captions::language::he), ]; /// `CAPTION_STYLE_PRESETS` (`store/captions.ts:57-160`). pub struct CaptionPreset { pub id: &'static str, - pub label: &'static str, - pub description: &'static str, + pub label: Label, + pub description: Label, pub font_weight: u32, pub size: u32, pub color: &'static str, @@ -237,8 +270,8 @@ pub struct CaptionPreset { pub static CAPTION_PRESETS: &[CaptionPreset] = &[ CaptionPreset { id: "classic", - label: "Classic", - description: "Clean text on a solid rounded background.", + label: tr::editor::tabs::captions::preset::classic, + description: tr::editor::tabs::captions::preset::classic_description, font_weight: 700, size: 50, color: "#FFFFFF", @@ -255,8 +288,8 @@ pub static CAPTION_PRESETS: &[CaptionPreset] = &[ }, CaptionPreset { id: "karaoke", - label: "Karaoke", - description: "Words light up in sync with speech.", + label: tr::editor::tabs::captions::preset::karaoke, + description: tr::editor::tabs::captions::preset::karaoke_description, font_weight: 700, size: 52, color: "#FFFFFF", @@ -273,8 +306,8 @@ pub static CAPTION_PRESETS: &[CaptionPreset] = &[ }, CaptionPreset { id: "highlight", - label: "Highlight", - description: "Bold caps with a pill behind the active word.", + label: tr::editor::tabs::captions::preset::highlight, + description: tr::editor::tabs::captions::preset::highlight_description, font_weight: 700, size: 54, color: "#FFFFFF", @@ -291,8 +324,8 @@ pub static CAPTION_PRESETS: &[CaptionPreset] = &[ }, CaptionPreset { id: "pop", - label: "Pop", - description: "Playful caps that pop in with a vibrant accent.", + label: tr::editor::tabs::captions::preset::pop, + description: tr::editor::tabs::captions::preset::pop_description, font_weight: 700, size: 56, color: "#FFFFFF", @@ -309,8 +342,8 @@ pub static CAPTION_PRESETS: &[CaptionPreset] = &[ }, CaptionPreset { id: "minimal", - label: "Minimal", - description: "Subtle outlined text with no background.", + label: tr::editor::tabs::captions::preset::minimal, + description: tr::editor::tabs::captions::preset::minimal_description, font_weight: 600, size: 46, color: "#FFFFFF", @@ -739,20 +772,20 @@ impl EditorWindow { SidebarMenu::BackgroundCornerStyle => CORNER_STYLES .iter() .map(|(style, label)| { - ui::MenuItem::new(*label, *style == project.background.rounding_type) + ui::MenuItem::new(label(), *style == project.background.rounding_type) }) .collect(), SidebarMenu::CameraBlur => CAMERA_BLUR_MODES .iter() .map(|(mode, label)| { - ui::MenuItem::new(*label, *mode == project.camera.background_blur.mode) + ui::MenuItem::new(label(), *mode == project.camera.background_blur.mode) }) .collect(), SidebarMenu::CameraShape => CAMERA_SHAPES .iter() .map(|(shape, label)| { ui::MenuItem::new( - *label, + label(), std::mem::discriminant(shape) == std::mem::discriminant(&project.camera.shape), ) @@ -761,13 +794,13 @@ impl EditorWindow { SidebarMenu::CameraCornerStyle => CORNER_STYLES .iter() .map(|(style, label)| { - ui::MenuItem::new(*label, *style == project.camera.rounding_type) + ui::MenuItem::new(label(), *style == project.camera.rounding_type) }) .collect(), SidebarMenu::AudioStereo => STEREO_MODES .iter() .map(|(mode, label)| { - ui::MenuItem::new(*label, *mode == project.audio.mic_stereo_mode) + ui::MenuItem::new(label(), *mode == project.audio.mic_stereo_mode) }) .collect(), SidebarMenu::CaptionModel => { @@ -778,7 +811,9 @@ impl EditorWindow { ui::MenuItem::new( SharedString::from(format!( "{} · {} · {}", - model.label, model.size, model.description + (model.label)(), + model.size, + (model.description)() )), model.name == selected, ) @@ -788,7 +823,7 @@ impl EditorWindow { SidebarMenu::CaptionLanguage => CAPTION_LANGUAGES .iter() .map(|(code, label)| { - ui::MenuItem::new(*label, *code == self.sidebar.caption_language) + ui::MenuItem::new(label(), *code == self.sidebar.caption_language) }) .collect(), SidebarMenu::CaptionFont => FONT_OPTIONS @@ -797,19 +832,21 @@ impl EditorWindow { .collect(), SidebarMenu::CaptionHighlightStyle => CAPTION_HIGHLIGHT_STYLES .iter() - .map(|(value, label)| ui::MenuItem::new(*label, *value == captions.highlight_style)) + .map(|(value, label)| { + ui::MenuItem::new(label(), *value == captions.highlight_style) + }) .collect(), SidebarMenu::CaptionPosition => CAPTION_POSITIONS .iter() - .map(|(value, label)| ui::MenuItem::new(*label, *value == captions.position)) + .map(|(value, label)| ui::MenuItem::new(label(), *value == captions.position)) .collect(), SidebarMenu::CaptionAnimation => CAPTION_ANIMATIONS .iter() - .map(|(value, label)| ui::MenuItem::new(*label, *value == captions.animation)) + .map(|(value, label)| ui::MenuItem::new(label(), *value == captions.animation)) .collect(), SidebarMenu::CaptionWeight => TEXT_WEIGHTS .iter() - .map(|(weight, label)| ui::MenuItem::new(*label, *weight == captions.font_weight)) + .map(|(weight, label)| ui::MenuItem::new(label(), *weight == captions.font_weight)) .collect(), SidebarMenu::KeyboardFont => FONT_OPTIONS .iter() @@ -817,11 +854,11 @@ impl EditorWindow { .collect(), SidebarMenu::KeyboardPosition => KEYBOARD_POSITIONS .iter() - .map(|(value, label)| ui::MenuItem::new(*label, *value == keyboard.position)) + .map(|(value, label)| ui::MenuItem::new(label(), *value == keyboard.position)) .collect(), SidebarMenu::KeyboardWeight => TEXT_WEIGHTS .iter() - .map(|(weight, label)| ui::MenuItem::new(*label, *weight == keyboard.font_weight)) + .map(|(weight, label)| ui::MenuItem::new(label(), *weight == keyboard.font_weight)) .collect(), SidebarMenu::TextFontFamily(index) | SidebarMenu::TextWeight(index) @@ -1401,79 +1438,97 @@ impl EditorWindow { .find(|(shape, _)| { std::mem::discriminant(shape) == std::mem::discriminant(&camera.shape) }) - .map_or("Square", |(_, label)| *label); + .map_or(tr::editor::tabs::camera::shape::square(), |(_, label)| { + label() + }); let blur_label = CAMERA_BLUR_MODES .iter() .find(|(mode, _)| *mode == camera.background_blur.mode) - .map_or("Off", |(_, label)| *label); + .map_or(tr::editor::tabs::camera::blur::off(), |(_, label)| label()); let corner_label = CORNER_STYLES .iter() .find(|(style, _)| *style == camera.rounding_type) - .map_or("Squircle", |(_, label)| *label); + .map_or(tr::editor::tabs::corner::squircle(), |(_, label)| label()); div() .flex() .flex_col() .gap(px(14.)) .child( - ui::Field::section(&theme, "Camera").child( + ui::Field::section(&theme, tr::editor::tabs::camera::title()).child( div() .flex() .flex_col() .gap(px(14.)) .child(self.render_camera_position(cx)) - .child(ui::Subfield::plain(&theme, "Hide Camera").child( - ui::Toggle::plain(&theme, "camera-hide", camera.hide).on_click( - cx.listener(|this, _, window, cx| { - let next = !this.style_control_project().camera.hide; - this.edit_project("camera-hide", window, cx, move |p| { - p.camera.hide = next; - true - }); - }), + .child( + ui::Subfield::plain(&theme, tr::editor::tabs::camera::hide()).child( + ui::Toggle::plain(&theme, "camera-hide", camera.hide).on_click( + cx.listener(|this, _, window, cx| { + let next = !this.style_control_project().camera.hide; + this.edit_project("camera-hide", window, cx, move |p| { + p.camera.hide = next; + true + }); + }), + ), ), - )) - .child(ui::Subfield::plain(&theme, "Mirror Camera").child( - ui::Toggle::plain(&theme, "camera-mirror", camera.mirror).on_click( - cx.listener(|this, _, window, cx| { - let next = !this.style_control_project().camera.mirror; - this.edit_project("camera-mirror", window, cx, move |p| { - p.camera.mirror = next; - true - }); - }), + ) + .child( + ui::Subfield::plain(&theme, tr::editor::tabs::camera::mirror()).child( + ui::Toggle::plain(&theme, "camera-mirror", camera.mirror).on_click( + cx.listener(|this, _, window, cx| { + let next = !this.style_control_project().camera.mirror; + this.edit_project("camera-mirror", window, cx, move |p| { + p.camera.mirror = next; + true + }); + }), + ), ), - )) - .child(ui::Subfield::plain(&theme, "Background").child( - div().w(px(160.)).child(self.menu_select( - SidebarMenu::CameraBlur, - "camera-blur", - blur_label, - cx, - )), - )) - .child(ui::Subfield::plain(&theme, "Shape").child( - div().w(px(160.)).child(self.menu_select( - SidebarMenu::CameraShape, - "camera-shape", - shape_label, - cx, - )), - )), + ) + .child( + ui::Subfield::plain(&theme, tr::editor::tabs::camera::background()) + .child(div().w(px(160.)).child(self.menu_select( + SidebarMenu::CameraBlur, + "camera-blur", + blur_label, + cx, + ))), + ) + .child( + ui::Subfield::plain(&theme, tr::editor::tabs::camera::shape_label()) + .child(div().w(px(160.)).child(self.menu_select( + SidebarMenu::CameraShape, + "camera-shape", + shape_label, + cx, + ))), + ), ), ) // `
` .child(dashed_divider(Hsla::from(theme.editor.line))) - .child(self.slider_field("Size", SliderKey::Camera(CameraSlider::Size), "%", cx)) .child(self.slider_field( - "Size During Zoom", + tr::editor::tabs::camera::size_label(), + SliderKey::Camera(CameraSlider::Size), + "%", + cx, + )) + .child(self.slider_field( + tr::editor::tabs::camera::size_during_zoom(), SliderKey::Camera(CameraSlider::ZoomSize), "%", cx, )) .child( - ui::Subfield::plain(&theme, "Keep original size during zoom").child( - ui::Toggle::plain(&theme, "camera-keep-size", camera.scale_during_zoom >= 1.) + ui::Subfield::plain(&theme, tr::editor::tabs::camera::keep_size_during_zoom()) + .child( + ui::Toggle::plain( + &theme, + "camera-keep-size", + camera.scale_during_zoom >= 1., + ) .on_click(cx.listener(|this, _, window, cx| { // `keep ? 1 : DEFAULT_CAMERA_SCALE_DURING_ZOOM`. let keep = this.style_control_project().camera.scale_during_zoom >= 1.; @@ -1487,14 +1542,14 @@ impl EditorWindow { true }); })), - ), + ), ) .child( div() .flex() .flex_col() .child(self.slider_field( - "Rounded Corners", + tr::editor::tabs::camera::rounded_corners(), SliderKey::Camera(CameraSlider::Rounding), "%", cx, @@ -1511,7 +1566,7 @@ impl EditorWindow { .flex() .flex_col() .child(self.slider_field( - "Shadow", + tr::editor::tabs::camera::shadow(), SliderKey::Camera(CameraSlider::Shadow), "%", cx, @@ -1537,11 +1592,11 @@ impl EditorWindow { div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_11)) - .child("Custom position (dragged on canvas)"), + .child(tr::editor::tabs::camera::custom_position()), ) .child( ui::EditorButton::plain(&theme, "camera-position-reset") - .label("Reset") + .label(tr::editor::tabs::camera::reset()) .on_click(cx.listener(|this, _, window, cx| { this.edit_project("camera-manual-position", window, cx, |p| { p.camera.manual_position = None; @@ -1562,7 +1617,7 @@ impl EditorWindow { label: &'static str, cx: &mut Context, ) -> AnyElement { - ui::Field::inline(&self.theme, "Corner Style") + ui::Field::inline(&self.theme, tr::editor::tabs::camera::corner_style()) .child( div() .w(px(150.)) @@ -1660,7 +1715,10 @@ impl EditorWindow { div() .flex() .flex_col() - .child(ui::Subfield::plain(&theme, "Position")) + .child(ui::Subfield::plain( + &theme, + tr::editor::tabs::camera::position(), + )) .child(grid) .into_any_element() } @@ -1677,7 +1735,7 @@ impl EditorWindow { .child(crate::editor_sidebar::disclosure_row( &theme, "camera-shadow-advanced", - "Advanced shadow settings", + tr::editor::tabs::camera::advanced_shadow(), open, cx.listener(|this, _, window, cx| { this.sidebar.camera_shadow_open.toggle(); @@ -1691,19 +1749,19 @@ impl EditorWindow { .flex_col() .pt(px(4.)) .child(self.slider_field( - "Size", + tr::editor::tabs::camera::size_label(), SliderKey::Camera(CameraSlider::ShadowSize), "", cx, )) .child(self.slider_field( - "Opacity", + tr::editor::tabs::camera::shadow_opacity(), SliderKey::Camera(CameraSlider::ShadowOpacity), "", cx, )) .child(self.slider_field( - "Blur", + tr::editor::tabs::camera::shadow_blur(), SliderKey::Camera(CameraSlider::ShadowBlur), "", cx, @@ -1727,33 +1785,37 @@ impl EditorWindow { let stereo_label = STEREO_MODES .iter() .find(|(mode, _)| *mode == audio.mic_stereo_mode) - .map_or("Stereo", |(_, label)| *label); + .map_or(tr::editor::tabs::audio::stereo::stereo(), |(_, label)| { + label() + }); div() .flex() .flex_col() .gap(px(14.)) .child( - ui::Field::section(&theme, "Audio Controls").child( + ui::Field::section(&theme, tr::editor::tabs::audio::title()).child( div() .flex() .flex_col() .gap(px(16.)) - .child(ui::Subfield::plain(&theme, "Mute Audio").child( - ui::Toggle::plain(&theme, "audio-mute", audio.mute).on_click( - cx.listener(|this, _, window, cx| { - let next = !this.project.audio.mute; - this.edit_project("audio-mute", window, cx, move |p| { - p.audio.mute = next; - true - }); - }), + .child( + ui::Subfield::plain(&theme, tr::editor::tabs::audio::mute()).child( + ui::Toggle::plain(&theme, "audio-mute", audio.mute).on_click( + cx.listener(|this, _, window, cx| { + let next = !this.project.audio.mute; + this.edit_project("audio-mute", window, cx, move |p| { + p.audio.mute = next; + true + }); + }), + ), ), - )) + ) // Only a two-channel microphone gets the stereo row // (`:709-711`). .children(stereo_mic.then(|| { - ui::Subfield::plain(&theme, "Microphone Stereo Mode") + ui::Subfield::plain(&theme, tr::editor::tabs::audio::stereo_mode()) .child(div().w(px(160.)).child(self.menu_select( SidebarMenu::AudioStereo, "audio-stereo", @@ -1767,7 +1829,7 @@ impl EditorWindow { .children(has_microphone.then(|| self.render_studio_sound_card(cx))) .children(has_microphone.then(|| { self.slider_field_disabled( - "Microphone Volume", + tr::editor::tabs::audio::mic_volume(), SliderKey::Audio(AudioSlider::MicVolume), "db", muted, @@ -1777,7 +1839,7 @@ impl EditorWindow { })) .children(has_system_audio.then(|| { self.slider_field_disabled( - "System Audio Volume", + tr::editor::tabs::audio::system_volume(), SliderKey::Audio(AudioSlider::SystemVolume), "db", muted, @@ -1797,18 +1859,18 @@ impl EditorWindow { let isolation_options = [ ( cap_project::VoiceIsolation::Light, - "Light", - "Keep more of your original voice and room sound.", + tr::editor::tabs::audio::isolation_light(), + tr::editor::tabs::audio::isolation_light_description(), ), ( cap_project::VoiceIsolation::Balanced, - "Balanced", - "Clearer isolation with natural voice detail.", + tr::editor::tabs::audio::isolation_balanced(), + tr::editor::tabs::audio::isolation_balanced_description(), ), ( cap_project::VoiceIsolation::Strong, - "Strong", - "More isolation for noisy spaces. May change voice texture.", + tr::editor::tabs::audio::isolation_strong(), + tr::editor::tabs::audio::isolation_strong_description(), ), ]; let enabled = self.project.audio.improve; @@ -1868,9 +1930,7 @@ impl EditorWindow { .text_size(px(12.)) .line_height(px(16.)) .text_color(Hsla::from(theme.editor.text_3)) - .child( - "Reduces background noise and balances your voice level.", - ), + .child(tr::editor::tabs::audio::studio_sound_description()), ), ) .child( @@ -1922,7 +1982,10 @@ impl EditorWindow { if store::set_studio_sound_isolation(isolation) { None } else { - Some("Could not save the Studio Sound default".into()) + Some( + tr::editor::tabs::audio::studio_sound_save_failed() + .into(), + ) }; cx.notify(); } @@ -1958,7 +2021,7 @@ impl EditorWindow { div() .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_2)) - .child("Use for new recordings"), + .child(tr::editor::tabs::audio::use_for_new()), ) .child( ui::Toggle::plain(&theme, "audio-improve-default", enabled_by_default) @@ -1971,8 +2034,9 @@ impl EditorWindow { this.sidebar.audio_enhancement_default = next; this.sidebar.audio_enhancement_error = None; } else { - this.sidebar.audio_enhancement_error = - Some("Could not save the Studio Sound default".into()); + this.sidebar.audio_enhancement_error = Some( + tr::editor::tabs::audio::studio_sound_save_failed().into(), + ); } cx.notify(); })), @@ -1998,7 +2062,7 @@ impl EditorWindow { let hidden = cursor.hide; let mut body = div().flex().flex_col().gap(px(14.)).child( - ui::Field::inline(&theme, "Show cursor").value( + ui::Field::inline(&theme, tr::editor::tabs::cursor::show()).value( ui::Toggle::plain(&theme, "cursor-show", !hidden) .on_click(cx.listener(move |this, _, window, cx| { this.edit_project("cursor-hide", window, cx, move |p| { @@ -2022,14 +2086,24 @@ impl EditorWindow { body = body .child( - ui::Field::stacked(&theme, "Cursor Style") + ui::Field::stacked(&theme, tr::editor::tabs::cursor::style_label()) .child(self.render_cursor_style_picker(cx)), ) - .child(self.slider_field("Size", SliderKey::Cursor(CursorSlider::Size), "", cx)) - .child(self.slider_field("Tilt", SliderKey::Cursor(CursorSlider::Tilt), "x100%", cx)) + .child(self.slider_field( + tr::editor::tabs::cursor::size_label(), + SliderKey::Cursor(CursorSlider::Size), + "", + cx, + )) + .child(self.slider_field( + tr::editor::tabs::cursor::tilt(), + SliderKey::Cursor(CursorSlider::Tilt), + "x100%", + cx, + )) .child(self.render_cursor_ripple(cx)) .child( - ui::Field::inline(&theme, "Hide When Idle").value( + ui::Field::inline(&theme, tr::editor::tabs::cursor::hide_when_idle()).value( ui::Toggle::plain(&theme, "cursor-idle", idle) .on_click(cx.listener(move |this, _, window, cx| { this.edit_project("cursor-idle", window, cx, move |p| { @@ -2042,11 +2116,11 @@ impl EditorWindow { ); if idle { - // `` + // `` // with a `w-12 text-right` readout beside the slider (`:945-967`). let readout = format!("{:.1}s", cursor.hide_when_idle_delay); body = body.child( - ui::Subfield::plain(&theme, "Inactivity Delay") + ui::Subfield::plain(&theme, tr::editor::tabs::cursor::inactivity_delay()) .gap(px(16.)) .child( div() @@ -2074,14 +2148,14 @@ impl EditorWindow { let smooth = !cursor.raw; let mut body = body .child( - ui::Field::section(&theme, "Cursor Movement Style").child( + ui::Field::section(&theme, tr::editor::tabs::cursor::movement_style()).child( ui::RadioCards::plain( &theme, "cursor-style", CURSOR_STYLES .iter() .map(|(_, label, description)| { - ui::RadioCard::new(*label, Some(description)) + ui::RadioCard::new(label(), Some(description())) }) .collect(), style_index, @@ -2112,18 +2186,19 @@ impl EditorWindow { .flex() .flex_col() .child( - ui::Field::inline(&theme, "Smooth Movement").value( - ui::Toggle::plain(&theme, "cursor-smooth", smooth) - .on_click(cx.listener(move |this, _, window, cx| { - this.sidebar.cursor_physics_open.set_open(!smooth); - this.animate_collapsibles(window, cx); - this.edit_project("cursor-raw", window, cx, move |p| { - p.cursor.raw = smooth; - true - }); - })) - .into_any_element(), - ), + ui::Field::inline(&theme, tr::editor::tabs::cursor::smooth_movement()) + .value( + ui::Toggle::plain(&theme, "cursor-smooth", smooth) + .on_click(cx.listener(move |this, _, window, cx| { + this.sidebar.cursor_physics_open.set_open(!smooth); + this.animate_collapsibles(window, cx); + this.edit_project("cursor-raw", window, cx, move |p| { + p.cursor.raw = smooth; + true + }); + })) + .into_any_element(), + ), ) .child(collapsible( &self.sidebar.cursor_physics_open, @@ -2134,19 +2209,19 @@ impl EditorWindow { .pt(px(4.)) .pb(px(8.)) .child(self.slider_field( - "Tension", + tr::editor::tabs::cursor::tension(), SliderKey::Cursor(CursorSlider::Tension), "", cx, )) .child(self.slider_field( - "Friction", + tr::editor::tabs::cursor::friction(), SliderKey::Cursor(CursorSlider::Friction), "", cx, )) .child(self.slider_field( - "Mass", + tr::editor::tabs::cursor::mass(), SliderKey::Cursor(CursorSlider::Mass), "", cx, @@ -2159,7 +2234,7 @@ impl EditorWindow { // showing a setting the renderer is already overriding. if cursor.cursor_type().family().is_none() { body = body.child( - ui::Field::inline(&theme, "High Quality SVG Cursors").value( + ui::Field::inline(&theme, tr::editor::tabs::cursor::svg_cursors()).value( ui::Toggle::plain(&theme, "cursor-svg", cursor.use_svg) .on_click(cx.listener(|this, _, window, cx| { let next = !this.style_control_project().cursor.use_svg; @@ -2183,13 +2258,14 @@ impl EditorWindow { return; } let Some(timeline) = self.project.timeline.clone() else { - self.keyboard_generation_message = Some("The project timeline is unavailable.".into()); + self.keyboard_generation_message = + Some(tr::editor::tabs::keyboard::timeline_unavailable().into()); cx.notify(); return; }; let Ok(timeline_fingerprint) = serde_json::to_vec(&timeline) else { self.keyboard_generation_message = - Some("The project timeline could not be read.".into()); + Some(tr::editor::tabs::keyboard::timeline_unreadable().into()); cx.notify(); return; }; @@ -2206,7 +2282,7 @@ impl EditorWindow { .background_executor() .spawn(async move { let meta = RecordingMeta::load_for_project(&path) - .map_err(|error| format!("Failed to load recording data: {error}"))?; + .map_err(tr::editor::tabs::keyboard::load_failed)?; cap_project::generate_project_keyboard_segments(&meta, &timeline, &settings) }) .await; @@ -2233,10 +2309,8 @@ impl EditorWindow { keyboard_generation_settings_fingerprint(&keyboard_settings(&this.project)) == settings_fingerprint; if !timeline_unchanged || !settings_unchanged { - this.keyboard_generation_message = Some( - "The timeline or keyboard settings changed during generation. Try again." - .into(), - ); + this.keyboard_generation_message = + Some(tr::editor::tabs::keyboard::changed_during_generation().into()); cx.notify(); window.refresh(); return; @@ -2249,7 +2323,7 @@ impl EditorWindow { .is_none_or(|timeline| timeline.keyboard_segments.is_empty()); if !has_segments && already_empty { this.keyboard_generation_message = - Some("No recorded keyboard presses were found.".into()); + Some(tr::editor::tabs::keyboard::no_presses().into()); cx.notify(); window.refresh(); return; @@ -2278,7 +2352,7 @@ impl EditorWindow { this.set_selection(None, cx); } this.keyboard_generation_message = - Some("No recorded keyboard presses were found.".into()); + Some(tr::editor::tabs::keyboard::no_presses().into()); cx.notify(); window.refresh(); } @@ -2302,11 +2376,13 @@ impl EditorWindow { let position_label = KEYBOARD_POSITIONS .iter() .find(|(value, _)| *value == settings.position) - .map_or("Bottom Center", |(_, label)| *label); + .map_or(tr::editor::tabs::position::bottom_center(), |(_, label)| { + label() + }); let weight_label = TEXT_WEIGHTS .iter() .find(|(weight, _)| *weight == settings.font_weight) - .map_or("Normal", |(_, label)| *label); + .map_or(tr::editor::tabs::weight::normal(), |(_, label)| label()); // `class={cx("space-y-4", !enabled && "opacity-50 pointer-events-none")}` let body = div() @@ -2315,13 +2391,13 @@ impl EditorWindow { .gap(px(16.)) .when(!enabled, |this| this.opacity(0.5)) .child( - ui::Field::section(&theme, "Font Settings").child( + ui::Field::section(&theme, tr::editor::tabs::keyboard::font_settings()).child( div() .flex() .flex_col() .gap(px(12.)) .child(self.labelled_inline( - "Font Family", + tr::editor::tabs::keyboard::font_family(), self.menu_select( SidebarMenu::KeyboardFont, "keyboard-font", @@ -2331,7 +2407,7 @@ impl EditorWindow { )) .child( self.slider_field( - "Size", + tr::editor::tabs::keyboard::size_label(), SliderKey::Keyboard(KeyboardSlider::Size), "", cx, @@ -2339,7 +2415,7 @@ impl EditorWindow { .into_any_element(), ) .child(self.labelled( - "Text Color", + tr::editor::tabs::keyboard::text_color(), self.render_hex_field( crate::editor_panels::FieldKey::KeyboardColor, &settings.color, @@ -2349,66 +2425,71 @@ impl EditorWindow { ), ) .child( - ui::Field::section(&theme, "Background Settings").child( - div() - .flex() - .flex_col() - .gap(px(12.)) - .child(self.labelled( - "Background Color", - self.render_hex_field( - crate::editor_panels::FieldKey::KeyboardBackground, - &settings.background_color, - cx, + ui::Field::section(&theme, tr::editor::tabs::keyboard::background_settings()) + .child( + div() + .flex() + .flex_col() + .gap(px(12.)) + .child(self.labelled( + tr::editor::tabs::keyboard::background_color(), + self.render_hex_field( + crate::editor_panels::FieldKey::KeyboardBackground, + &settings.background_color, + cx, + ), + )) + .child( + self.slider_field( + tr::editor::tabs::keyboard::background_opacity(), + SliderKey::Keyboard(KeyboardSlider::BackgroundOpacity), + "", + cx, + ) + .into_any_element(), ), - )) - .child( - self.slider_field( - "Background Opacity", - SliderKey::Keyboard(KeyboardSlider::BackgroundOpacity), - "", - cx, - ) - .into_any_element(), - ), - ), + ), ) .child( - ui::Field::inline(&theme, "Position").child(self.menu_select( - SidebarMenu::KeyboardPosition, - "keyboard-position", - position_label, - cx, - )), + ui::Field::inline(&theme, tr::editor::tabs::keyboard::position()).child( + self.menu_select( + SidebarMenu::KeyboardPosition, + "keyboard-position", + position_label, + cx, + ), + ), ) .child( - ui::Field::inline(&theme, "Font Weight").child(self.menu_select( - SidebarMenu::KeyboardWeight, - "keyboard-weight", - weight_label, - cx, - )), + ui::Field::inline(&theme, tr::editor::tabs::keyboard::font_weight()).child( + self.menu_select( + SidebarMenu::KeyboardWeight, + "keyboard-weight", + weight_label, + cx, + ), + ), ) .child( - ui::Field::section(&theme, "Animation").child( + ui::Field::section(&theme, tr::editor::tabs::keyboard::animation()).child( div() .flex() .flex_col() .gap(px(12.)) .child(self.labelled_readout( - "Fade Duration", + tr::editor::tabs::keyboard::fade_duration(), SliderKey::Keyboard(KeyboardSlider::FadeDuration), format!("{:.0}ms", settings.fade_duration * 1000.), cx, )) .child(self.labelled_readout( - "Linger Duration", + tr::editor::tabs::keyboard::linger_duration(), SliderKey::Keyboard(KeyboardSlider::LingerDuration), format!("{:.1}s", settings.linger_duration), cx, )) .child(self.labelled_readout( - "Grouping Threshold", + tr::editor::tabs::keyboard::grouping_threshold(), SliderKey::Keyboard(KeyboardSlider::GroupingThreshold), format!("{:.0}ms", settings.grouping_threshold_ms), cx, @@ -2416,13 +2497,17 @@ impl EditorWindow { ), ) .child( - ui::Field::section(&theme, "Behavior").child( + ui::Field::section(&theme, tr::editor::tabs::keyboard::behavior()).child( div() .flex() .flex_col() .gap(px(12.)) .child( - ui::Subfield::plain(&theme, "Show Modifiers").child( + ui::Subfield::plain( + &theme, + tr::editor::tabs::keyboard::show_modifiers(), + ) + .child( ui::Toggle::plain( &theme, "keyboard-modifiers", @@ -2442,7 +2527,11 @@ impl EditorWindow { ), ) .child( - ui::Subfield::plain(&theme, "Show Special Keys").child( + ui::Subfield::plain( + &theme, + tr::editor::tabs::keyboard::show_special_keys(), + ) + .child( ui::Toggle::plain( &theme, "keyboard-special", @@ -2463,18 +2552,25 @@ impl EditorWindow { ), ) .child( - ui::Subfield::plain(&theme, "Uppercase").child( - ui::Toggle::plain(&theme, "keyboard-uppercase", settings.uppercase) - .on_click(cx.listener(|this, _, window, cx| { - let next = !keyboard_settings(&this.project).uppercase; - this.set_keyboard_setting( - "keyboard-uppercase", - window, - cx, - move |settings| settings.uppercase = next, - ); - })), - ), + ui::Subfield::plain(&theme, tr::editor::tabs::keyboard::uppercase()) + .child( + ui::Toggle::plain( + &theme, + "keyboard-uppercase", + settings.uppercase, + ) + .on_click(cx.listener( + |this, _, window, cx| { + let next = !keyboard_settings(&this.project).uppercase; + this.set_keyboard_setting( + "keyboard-uppercase", + window, + cx, + move |settings| settings.uppercase = next, + ); + }, + )), + ), ), ), ) @@ -2487,11 +2583,11 @@ impl EditorWindow { ui::ButtonSize::Md, ) .label(if self.generating_keyboard { - "Generating Keyboard Segments..." + tr::editor::tabs::keyboard::generating() } else if has_segments { - "Regenerate Keyboard Segments" + tr::editor::tabs::keyboard::regenerate() } else { - "Generate Keyboard Segments" + tr::editor::tabs::keyboard::generate() }) .full_width() .disabled(self.generating_keyboard || self.project.timeline.is_none()) @@ -2518,24 +2614,21 @@ impl EditorWindow { div() .text_size(px(14.)) .text_color(Hsla::from(theme.gray_11)) - .child("No keyboard segments yet."), + .child(tr::editor::tabs::keyboard::empty_title()), ) .child( div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_10)) - .child( - "Click \"Generate Keyboard Segments\" to create segments from \ - recorded keyboard presses.", - ), + .child(tr::editor::tabs::keyboard::empty_description()), ) .into_any_element() })); // The whole tab is one `Field` with the master toggle in its header and // a `Beta` badge (`KeyboardTab.tsx:128-135`). - ui::Field::section(&theme, "Show keyboard") - .badge("Beta") + ui::Field::section(&theme, tr::editor::tabs::keyboard::title()) + .badge(tr::editor::tabs::keyboard::beta()) .value( ui::Toggle::plain(&theme, "keyboard-enabled", enabled) .on_click(cx.listener(move |this, _, window, cx| { @@ -2601,24 +2694,35 @@ impl EditorWindow { let language_label = CAPTION_LANGUAGES .iter() .find(|(code, _)| *code == self.sidebar.caption_language) - .map_or("Auto Detect", |(_, label)| *label); + .map_or( + tr::editor::tabs::captions::language::auto(), + |(_, label)| label(), + ); let font_label = settings.font.clone(); let position_label = CAPTION_POSITIONS .iter() .find(|(value, _)| *value == settings.position) - .map_or("Bottom Center", |(_, label)| *label); + .map_or(tr::editor::tabs::position::bottom_center(), |(_, label)| { + label() + }); let animation_label = CAPTION_ANIMATIONS .iter() .find(|(value, _)| *value == settings.animation) - .map_or("Bounce", |(_, label)| *label); + .map_or( + tr::editor::tabs::captions::animation::bounce(), + |(_, label)| label(), + ); let highlight_label = CAPTION_HIGHLIGHT_STYLES .iter() .find(|(value, _)| *value == settings.highlight_style) - .map_or("Color", |(_, label)| *label); + .map_or( + tr::editor::tabs::captions::highlight::color(), + |(_, label)| label(), + ); let weight_label = TEXT_WEIGHTS .iter() .find(|(weight, _)| *weight == settings.font_weight) - .map_or("Bold", |(_, label)| *label); + .map_or(tr::editor::tabs::weight::bold(), |(_, label)| label()); let model_downloaded = snapshot.downloaded.contains(model.name); let active_download = snapshot @@ -2631,7 +2735,7 @@ impl EditorWindow { }); let downloading_label = active_download .and_then(|download| models.iter().find(|entry| entry.name == download.model)) - .map_or(model.label, |entry| entry.label); + .map_or((model.label)(), |entry| (entry.label)()); let download_message = active_download .map(|download| download.message.clone()) .filter(|message| !message.is_empty()); @@ -2680,14 +2784,14 @@ impl EditorWindow { .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.gray_12)) .truncate() - .child(model.label), + .child((model.label)()), ) .child( div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_11)) .truncate() - .child(model.description), + .child((model.description)()), ), ) .child( @@ -2727,11 +2831,11 @@ impl EditorWindow { ui::ButtonSize::Md, ) .label(if generating { - "Generating..." + tr::editor::tabs::captions::generating() } else if has_captions { - "Regenerate Captions" + tr::editor::tabs::captions::regenerate() } else { - "Generate Captions" + tr::editor::tabs::captions::generate() }) .full_width() .disabled(generating || any_deleting) @@ -2766,10 +2870,11 @@ impl EditorWindow { .text_size(px(12.)) .text_color(Hsla::from(theme.gray_10)) .truncate() - .child(SharedString::from(format!( - "{} model downloaded", - model.label - ))), + .child(SharedString::from( + tr::editor::tabs::captions::model_downloaded( + (model.label)(), + ), + )), ), ) .child( @@ -2780,7 +2885,11 @@ impl EditorWindow { ui::ButtonSize::Sm, ) .icon("icons/trash.svg") - .label(if deleting { "Deleting..." } else { "Delete" }) + .label(if deleting { + tr::editor::tabs::captions::deleting() + } else { + tr::editor::tabs::captions::delete() + }) .disabled(generating || is_downloading || deleting) .on_click(cx.listener(|this, _, _window, cx| { this.delete_caption_model(cx); @@ -2809,13 +2918,14 @@ impl EditorWindow { this.start_caption_model_download(cx); })); let download_button = if is_downloading { - download_button.label(format!( - "Downloading {downloading_label}... {download_percent}%" + download_button.label(tr::editor::tabs::captions::downloading( + downloading_label, + download_percent, )) } else { download_button .icon("icons/download.svg") - .label(format!("Download {} Model", model.label)) + .label(tr::editor::tabs::captions::download((model.label)())) }; let mut column = div().flex().flex_col().gap(px(8.)).child(download_button); @@ -2841,9 +2951,7 @@ impl EditorWindow { .text_size(px(12.)) .text_color(Hsla::from(theme.gray_10)) .child(download_message.unwrap_or_else(|| { - "Keep Cap open while the model downloads. Editor reloads will \ - reconnect automatically." - .to_string() + tr::editor::tabs::captions::keep_open().to_string() })), ); } @@ -2863,32 +2971,30 @@ impl EditorWindow { .flex_col() .gap(px(16.)) .child( - ui::Subfield::plain(&theme, "Model").child(div().w(px(220.)).child(model_trigger)), + ui::Subfield::plain(&theme, tr::editor::tabs::captions::model_label()) + .child(div().w(px(220.)).child(model_trigger)), ) .children((!transcription::supports_parakeet()).then(|| { div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_10)) - .child( - "Parakeet caption models are unavailable on Intel Macs. Whisper models \ - remain available.", - ) + .child(tr::editor::tabs::captions::intel_unavailable()) })) .child( div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_10)) - .child("One time download to your system. All captions are stored locally."), + .child(tr::editor::tabs::captions::download_note()), ) .child( - ui::Subfield::plain(&theme, "Language").child(div().w(px(200.)).child( - self.menu_select( + ui::Subfield::plain(&theme, tr::editor::tabs::captions::language_label()).child( + div().w(px(200.)).child(self.menu_select( SidebarMenu::CaptionLanguage, "caption-language", language_label, cx, - ), - )), + )), + ), ) .child(div().pt(px(8.)).child(action)); @@ -2898,17 +3004,17 @@ impl EditorWindow { .gap(px(16.)) .when(!has_captions, |this| this.opacity(0.5)) .child( - ui::Field::section(&theme, "Style") + ui::Field::section(&theme, tr::editor::tabs::captions::style()) .child(self.render_caption_presets(&settings, cx)), ) .child( - ui::Field::section(&theme, "Font Settings").child( + ui::Field::section(&theme, tr::editor::tabs::captions::font_settings()).child( div() .flex() .flex_col() .gap(px(12.)) .child(self.labelled_inline( - "Font Family", + tr::editor::tabs::captions::font_family(), self.menu_select( SidebarMenu::CaptionFont, "caption-font", @@ -2918,7 +3024,7 @@ impl EditorWindow { )) .child( self.slider_field( - "Size", + tr::editor::tabs::captions::size_label(), SliderKey::Caption(CaptionSlider::Size), "", cx, @@ -2926,18 +3032,25 @@ impl EditorWindow { .into_any_element(), ) .child( - ui::Subfield::plain(&theme, "Uppercase").child( - ui::Toggle::plain(&theme, "caption-uppercase", settings.uppercase) - .on_click(cx.listener(|this, _, window, cx| { - let next = !caption_settings(&this.project).uppercase; - this.set_caption_setting( - "caption-uppercase", - window, - cx, - move |settings| settings.uppercase = next, - ); - })), - ), + ui::Subfield::plain(&theme, tr::editor::tabs::captions::uppercase()) + .child( + ui::Toggle::plain( + &theme, + "caption-uppercase", + settings.uppercase, + ) + .on_click(cx.listener( + |this, _, window, cx| { + let next = !caption_settings(&this.project).uppercase; + this.set_caption_setting( + "caption-uppercase", + window, + cx, + move |settings| settings.uppercase = next, + ); + }, + )), + ), ) .child( div() @@ -2945,7 +3058,11 @@ impl EditorWindow { .flex_col() .gap(px(8.)) .child( - ui::Subfield::plain(&theme, "Active Word Highlight").child( + ui::Subfield::plain( + &theme, + tr::editor::tabs::captions::active_word_highlight(), + ) + .child( ui::Toggle::plain( &theme, "caption-active-word", @@ -2971,17 +3088,12 @@ impl EditorWindow { div() .text_size(px(12.)) .text_color(Hsla::from(theme.gray_10)) - .child( - "This is the first version of captions in Cap. \ - Active word highlighting may be inaccurate in \ - some situations. We're working on a fix for this \ - and it will be released in upcoming versions.", - ), + .child(tr::editor::tabs::captions::highlight_note()), ), ) .children(settings.active_word_highlight.then(|| { self.labelled_inline( - "Highlight Style", + tr::editor::tabs::captions::highlight_style(), self.menu_select( SidebarMenu::CaptionHighlightStyle, "caption-highlight-style", @@ -2991,7 +3103,7 @@ impl EditorWindow { ) })) .child(self.labelled( - "Text Color", + tr::editor::tabs::captions::text_color(), self.render_hex_field( crate::editor_panels::FieldKey::CaptionColor, &settings.color, @@ -3001,37 +3113,40 @@ impl EditorWindow { ), ) .child( - ui::Field::section(&theme, "Background Settings").child( - div() - .flex() - .flex_col() - .gap(px(12.)) - .child(self.labelled( - "Background Color", - self.render_hex_field( - crate::editor_panels::FieldKey::CaptionBackground, - &settings.background_color, - cx, + ui::Field::section(&theme, tr::editor::tabs::captions::background_settings()) + .child( + div() + .flex() + .flex_col() + .gap(px(12.)) + .child(self.labelled( + tr::editor::tabs::captions::background_color(), + self.render_hex_field( + crate::editor_panels::FieldKey::CaptionBackground, + &settings.background_color, + cx, + ), + )) + .child( + self.slider_field( + tr::editor::tabs::captions::background_opacity(), + SliderKey::Caption(CaptionSlider::BackgroundOpacity), + "", + cx, + ) + .into_any_element(), ), - )) - .child( - self.slider_field( - "Background Opacity", - SliderKey::Caption(CaptionSlider::BackgroundOpacity), - "", - cx, - ) - .into_any_element(), - ), - ), + ), ) .child( - ui::Field::inline(&theme, "Position").child(self.menu_select( - SidebarMenu::CaptionPosition, - "caption-position", - position_label, - cx, - )), + ui::Field::inline(&theme, tr::editor::tabs::captions::position()).child( + self.menu_select( + SidebarMenu::CaptionPosition, + "caption-position", + position_label, + cx, + ), + ), ) .children((settings.position == "manual").then(|| { div() @@ -3040,7 +3155,7 @@ impl EditorWindow { .gap(px(12.)) .child( self.slider_field( - "Horizontal Position", + tr::editor::tabs::captions::horizontal_position(), SliderKey::Caption(CaptionSlider::ManualX), "%", cx, @@ -3049,7 +3164,7 @@ impl EditorWindow { ) .child( self.slider_field( - "Vertical Position", + tr::editor::tabs::captions::vertical_position(), SliderKey::Caption(CaptionSlider::ManualY), "%", cx, @@ -3058,13 +3173,13 @@ impl EditorWindow { ) })) .child( - ui::Field::section(&theme, "Animation").child( + ui::Field::section(&theme, tr::editor::tabs::captions::animation_section()).child( div() .flex() .flex_col() .gap(px(12.)) .child(self.labelled_inline( - "Animation Style", + tr::editor::tabs::captions::animation_style(), self.menu_select( SidebarMenu::CaptionAnimation, "caption-animation", @@ -3073,7 +3188,7 @@ impl EditorWindow { ), )) .child(self.labelled( - "Highlight Color", + tr::editor::tabs::captions::highlight_color(), self.render_hex_field( crate::editor_panels::FieldKey::CaptionHighlight, &settings.highlight_color, @@ -3081,7 +3196,7 @@ impl EditorWindow { ), )) .child(self.labelled_readout( - "Fade Duration", + tr::editor::tabs::captions::fade_duration(), SliderKey::Caption(CaptionSlider::FadeDuration), format!("{:.0}ms", settings.fade_duration * 1000.), cx, @@ -3089,17 +3204,19 @@ impl EditorWindow { ), ) .child( - ui::Field::inline(&theme, "Font Weight").child(self.menu_select( - SidebarMenu::CaptionWeight, - "caption-weight", - weight_label, - cx, - )), + ui::Field::inline(&theme, tr::editor::tabs::captions::font_weight()).child( + self.menu_select( + SidebarMenu::CaptionWeight, + "caption-weight", + weight_label, + cx, + ), + ), ); // An extra flex ancestor here repeats intrinsic layout while scrolling. - ui::Field::section(&theme, "Captions") - .badge("Beta") + ui::Field::section(&theme, tr::editor::tabs::captions::title()) + .badge(tr::editor::tabs::captions::beta()) .child(transcription) .child(style.mt(px(8.))) .into_any_element() @@ -3155,10 +3272,10 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.gray_12)) - .child(preset.label), + .child((preset.label)()), ) .tooltip({ - let description = SharedString::from(preset.description); + let description = SharedString::from((preset.description)()); move |_window, cx| ui::Tooltip::new(&theme, description.clone()).view(cx) }) .on_click(cx.listener(move |this, _, window, cx| { @@ -3433,9 +3550,8 @@ impl EditorWindow { tracing::error!("caption generation failed: {error}"); transcription::finish_generation( &project_path, - Some(format!( - "Failed to generate captions: {}", - transcription::caption_generation_error_message(&error) + Some(tr::editor::tabs::captions::generate_failed( + transcription::caption_generation_error_message(&error), )), ); let _ = this.update(cx, |_, cx| cx.notify()); @@ -3499,16 +3615,13 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_2)) - .child("Sync"), + .child(tr::editor::tabs::sync::title()), ) .child( div() .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_3)) - .child( - "Fine-tune source offsets if audio or camera drifts out of \ - sync with the screen recording.", - ), + .child(tr::editor::tabs::sync::description()), ), ) .children((0..clips).map(|clip| { @@ -3527,25 +3640,21 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_2)) - .child(SharedString::from(format!("Clip {clip}"))) + .child(SharedString::from(tr::editor::tabs::sync::clip(clip))) .into_any_element() })) .children(auto.then(|| { div() .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_3)) - .child( - "Cap calculated these offsets automatically to keep audio in \ - sync with the video. Adjust them if anything still sounds \ - off.", - ) + .child(tr::editor::tabs::sync::auto_description()) .into_any_element() })) .children(summary.has_system_audio.then(|| { self.render_offset_field( clip, OffsetKind::SystemAudio, - "System Audio Offset", + tr::editor::tabs::sync::system_audio(), auto, cx, ) @@ -3554,7 +3663,7 @@ impl EditorWindow { self.render_offset_field( clip, OffsetKind::Mic, - "Microphone Offset", + tr::editor::tabs::sync::microphone(), auto, cx, ) @@ -3563,7 +3672,7 @@ impl EditorWindow { self.render_offset_field( clip, OffsetKind::Camera, - "Camera Offset", + tr::editor::tabs::sync::camera(), auto, cx, ) @@ -3631,7 +3740,7 @@ impl EditorWindow { let mut field = ui::Field::section(&theme, name); if auto { - field = field.badge("Auto-synced"); + field = field.badge(tr::editor::tabs::sync::auto_synced()); } field diff --git a/apps/desktop-gpui/src/editor_timeline.rs b/apps/desktop-gpui/src/editor_timeline.rs index ad0f22557c5..8bd821d4e19 100644 --- a/apps/desktop-gpui/src/editor_timeline.rs +++ b/apps/desktop-gpui/src/editor_timeline.rs @@ -51,6 +51,7 @@ use gpui::{ use crate::{ editor_edits::{Selection, clip_is_muted}, + i18n::tr, theme::Theme, }; @@ -953,17 +954,17 @@ impl TrackKind { match self { // The clip row's gutter label is "Video", not the definition's // "Clip" (`TL/index.tsx:1334`). - Self::Clip => "Video", - Self::Style => "Style", - Self::Image => "Image", - Self::Caption => "Captions", - Self::Keyboard => "Keyboard", - Self::Text => "Text", - Self::Mask => "Mask", - Self::Audio => "Audio", - Self::Zoom => "Zoom", + Self::Clip => tr::editor::timeline::track::video(), + Self::Style => tr::editor::timeline::track::style(), + Self::Image => tr::editor::timeline::track::image(), + Self::Caption => tr::editor::timeline::track::captions(), + Self::Keyboard => tr::editor::timeline::track::keyboard(), + Self::Text => tr::editor::timeline::track::text_track(), + Self::Mask => tr::editor::timeline::track::mask(), + Self::Audio => tr::editor::timeline::track::audio(), + Self::Zoom => tr::editor::timeline::track::zoom(), Self::ThreeD => "3D", - Self::Scene => "Scene", + Self::Scene => tr::editor::timeline::track::scene(), } } @@ -1002,30 +1003,48 @@ impl TrackKind { pub fn picker_label(self) -> &'static str { match self { - Self::Clip => "Clip", + Self::Clip => tr::editor::timeline::track::clip(), other => other.label(), } } pub fn picker_description(self) -> &'static str { match self { - Self::Clip => "Your recorded screen footage.", - Self::Style => "Change background, camera and cursor settings over time.", - Self::Image => "Add an image to your recording.", - Self::Zoom => "Smooth zoom-ins that follow the action.", - Self::Caption => "Auto-transcribe your recording into on-screen subtitles.", - Self::Keyboard => "Display key presses on screen as you type.", - Self::Text => "Add custom text overlays and titles to the canvas.", - Self::Mask => "Blur or black out private areas of the screen.", - Self::Audio => "Add background music or import your own audio.", - Self::Scene => "Switch layouts between your screen and camera.", - Self::ThreeD => "Tilt the scene in 3D perspective.", + Self::Clip => tr::editor::timeline::picker::clip(), + Self::Style => tr::editor::timeline::picker::style(), + Self::Image => tr::editor::timeline::picker::image(), + Self::Zoom => tr::editor::timeline::picker::zoom(), + Self::Caption => tr::editor::timeline::picker::caption(), + Self::Keyboard => tr::editor::timeline::picker::keyboard(), + Self::Text => tr::editor::timeline::picker::text_overlay(), + Self::Mask => tr::editor::timeline::picker::mask(), + Self::Audio => tr::editor::timeline::picker::audio(), + Self::Scene => tr::editor::timeline::picker::scene(), + Self::ThreeD => tr::editor::timeline::picker::three_d(), + } + } + + /// What the add-track hint says over the tile of a single-lane track + /// that is already on the timeline. + pub fn picker_remove(self) -> &'static str { + match self { + Self::Clip => tr::editor::timeline::remove::clip(), + Self::Style => tr::editor::timeline::remove::style(), + Self::Image => tr::editor::timeline::remove::image(), + Self::Caption => tr::editor::timeline::remove::captions(), + Self::Keyboard => tr::editor::timeline::remove::keyboard(), + Self::Text => tr::editor::timeline::remove::text_track(), + Self::Mask => tr::editor::timeline::remove::mask(), + Self::Audio => tr::editor::timeline::remove::audio(), + Self::Zoom => tr::editor::timeline::remove::zoom(), + Self::ThreeD => tr::editor::timeline::remove::three_d(), + Self::Scene => tr::editor::timeline::remove::scene(), } } pub fn picker_unavailable(self) -> &'static str { match self { - Self::Scene => "Record with a camera to use scenes.", + Self::Scene => tr::editor::timeline::picker::scene_unavailable(), _ => "", } } @@ -1388,7 +1407,7 @@ impl TimelineModel { lane: segment.track, detail: SegmentDetail::Text { content: if segment.content.is_empty() { - SharedString::new_static("Label") + SharedString::new_static(tr::editor::timeline::segment::label()) } else { SharedString::from(segment.content.clone()) }, @@ -1413,8 +1432,8 @@ impl TimelineModel { lane: segment.track, detail: SegmentDetail::Mask { label: match segment.mask_type { - MaskKind::Sensitive => "Sensitive", - MaskKind::Highlight => "Highlight", + MaskKind::Sensitive => tr::editor::timeline::segment::sensitive(), + MaskKind::Highlight => tr::editor::timeline::segment::highlight(), }, }, }) @@ -1432,7 +1451,10 @@ impl TimelineModel { .name .clone() .filter(|name| !name.is_empty()) - .map_or_else(|| SharedString::new_static("Audio"), SharedString::from), + .map_or_else( + || SharedString::new_static(tr::editor::timeline::segment::audio()), + SharedString::from, + ), enabled: segment.enabled, fade_in: segment.fade_in, fade_out: segment.fade_out, @@ -1451,7 +1473,7 @@ impl TimelineModel { lane: 0, detail: SegmentDetail::Caption { text: if segment.text.is_empty() { - SharedString::new_static("Caption") + SharedString::new_static(tr::editor::timeline::segment::caption()) } else { SharedString::from(segment.text.clone()) }, @@ -1702,10 +1724,10 @@ fn clip_rows( // clips sidebar (`displayName`). name: match segment.name.as_deref().map(str::trim) { Some(name) if !name.is_empty() => SharedString::from(name.to_string()), - _ if multiple_clips => { - SharedString::from(format!("Clip {}", segment.recording_clip)) - } - _ => SharedString::new_static("Clip"), + _ if multiple_clips => SharedString::from( + tr::editor::timeline::segment::numbered_clip(segment.recording_clip), + ), + _ => SharedString::new_static(tr::editor::timeline::segment::clip()), }, source_start: segment.start, // The label shows `formatTime(seg.end - seg.start)` -- the @@ -1743,11 +1765,11 @@ fn parse_hex_color(value: &str) -> Option { /// `getSceneLabel` / `getSceneIcon` (`TL/SceneTrack.tsx:80-102`). fn scene_label(mode: SceneMode) -> &'static str { match mode { - SceneMode::CameraOnly => "Camera Only", - SceneMode::HideCamera => "Hide Camera", - SceneMode::SplitScreen => "Split Screen", - SceneMode::Floating => "Floating", - SceneMode::Default => "Default", + SceneMode::CameraOnly => tr::editor::timeline::scene::camera_only(), + SceneMode::HideCamera => tr::editor::timeline::scene::hide_camera(), + SceneMode::SplitScreen => tr::editor::timeline::scene::split_screen(), + SceneMode::Floating => tr::editor::timeline::scene::floating(), + SceneMode::Default => tr::editor::timeline::scene::default(), } } @@ -2478,12 +2500,12 @@ pub fn new_camera3d_ghost(model: &TimelineModel, preview: f64) -> Option<(f64, f /// nothing but the plus. fn camera3d_ghost_label(width: f32, duration: f64) -> Option { if width >= 208. { - Some(format!( - "Add shot \u{b7} {} \u{b7} {duration:.1}s", - crate::editor_panels::CAMERA3D_DEFAULT_LOOK_NAME + Some(tr::editor::timeline::add_shot_with_look( + crate::editor_panels::CAMERA3D_DEFAULT_LOOK_NAME, + format!("{duration:.1}"), )) } else if width >= 84. { - Some("Add shot".to_string()) + Some(tr::editor::timeline::add_shot().to_string()) } else { None } @@ -2639,18 +2661,18 @@ fn render_empty_track( ) -> Option { let (copy, action) = match kind { // `TL/CaptionsTrack.tsx:146-160`. - TrackKind::Caption => ("No captions", None), + TrackKind::Caption => (tr::editor::timeline::empty::captions(), None), // `TL/KeyboardTrack.tsx:146-153`. - TrackKind::Keyboard => ("No keyboard events", None), + TrackKind::Keyboard => (tr::editor::timeline::empty::keyboard(), None), // `TL/AudioTrack.tsx:400-427`, `TL/ThreeDTrack.tsx:319-337`, and the // scene lane, which had no empty state at all. TrackKind::Audio => ( - kind.picker_description().trim_end_matches('.'), - Some("Add audio"), + tr::editor::timeline::empty::audio(), + Some(tr::editor::timeline::empty::add_audio()), ), TrackKind::Scene => ( - kind.picker_description().trim_end_matches('.'), - Some("Add scene"), + tr::editor::timeline::empty::scene(), + Some(tr::editor::timeline::empty::add_scene()), ), _ => return None, }; @@ -3244,7 +3266,7 @@ fn render_hold( .text_size(px(10.)) .font_weight(FontWeight::MEDIUM) .text_color(ink) - .child("Paused"), + .child(tr::editor::timeline::paused()), ) }) } @@ -3488,14 +3510,19 @@ fn label_body( ) => label_row() .child(label_primary(theme, color).child(name.clone())) .when(*muted, |this| { - this.child(clip_badge(theme, color, "icons/volume-x.svg", "Muted")) + this.child(clip_badge( + theme, + color, + "icons/volume-x.svg", + tr::editor::timeline::segment::muted(), + )) }) .when(*cursor_hidden, |this| { this.child(clip_badge( theme, color, "icons/mouse-pointer-ban.svg", - "Cursor hidden", + tr::editor::timeline::segment::cursor_hidden(), )) }) .child(label_secondary(theme, color).child(format_clip_time(*source_duration))) @@ -3547,12 +3574,12 @@ fn label_body( // least 140px wide (`TL/ZoomTrack.tsx:700-704`). if visible_width >= 140. { if *automatic { - "Automatic Zoom" + tr::editor::timeline::segment::automatic_zoom() } else { - "Manual Zoom" + tr::editor::timeline::segment::manual_zoom() } } else { - "Zoom" + tr::editor::timeline::track::zoom() }, ))) .child(label_secondary(theme, color).child(format!("{amount:.1}x"))) @@ -3566,7 +3593,7 @@ fn label_body( // -- Scene (`TL/SceneTrack.tsx:543-566`) -------------------------- (SegmentDetail::Scene { mode }, LabelTier::Full) => label_row() - .child(label_primary(theme, color).child("Scene")) + .child(label_primary(theme, color).child(tr::editor::timeline::track::scene())) .child(label_secondary(theme, color).child(scene_label(*mode))) .into_any_element(), (SegmentDetail::Scene { mode }, LabelTier::Compact) => label_row() @@ -3638,7 +3665,7 @@ fn label_body( // -- Mask (`TL/MaskTrack.tsx:481-495`) ---------------------------- (SegmentDetail::Mask { label }, LabelTier::Full) => label_row() - .child(label_primary(theme, color).child("Mask")) + .child(label_primary(theme, color).child(tr::editor::timeline::track::mask())) .child(label_secondary(theme, color).child(*label)) .into_any_element(), (SegmentDetail::Mask { label }, LabelTier::Compact) => label_row() diff --git a/apps/desktop-gpui/src/editor_window.rs b/apps/desktop-gpui/src/editor_window.rs index 046a18b1f47..da55e82fcd7 100644 --- a/apps/desktop-gpui/src/editor_window.rs +++ b/apps/desktop-gpui/src/editor_window.rs @@ -67,6 +67,7 @@ use gpui::{ div, point, prelude::FluentBuilder, px, svg, }; +use crate::i18n::tr; use crate::theme::LocaleFont as _; use crate::{ editor_audio::{AudioImportRequest, probe_audio_duration, replace_audio_asset}, @@ -379,7 +380,7 @@ pub struct ProjectSummary { /// Blocking; callers run it on the background executor. pub fn preflight(path: &std::path::Path) -> Result { if !path.exists() { - return Err(format!("Video path {} not found!", path.display())); + return Err(tr::editor::window::open::not_found(path.display())); } // Repairs tracks older recorders stamped at the wrong rate, and display @@ -391,11 +392,11 @@ pub fn preflight(path: &std::path::Path) -> Result { Err(error) => tracing::warn!(?path, "Track heal check failed: {error:#}"), } - let meta = RecordingMeta::load_for_project(path) - .map_err(|error| format!("Failed to load recording meta: {error}"))?; + let meta = + RecordingMeta::load_for_project(path).map_err(tr::editor::window::open::meta_failed)?; let RecordingMetaInner::Studio(studio) = &meta.inner else { - return Err("Cannot edit non-studio recordings".to_string()); + return Err(tr::editor::window::open::not_studio().to_string()); }; let (segment_count, has_camera, multiple_recording_segments) = match studio.as_ref() { @@ -420,7 +421,7 @@ pub fn preflight(path: &std::path::Path) -> Result { }; if segment_count == 0 { - return Err("Recording has no segments. It may need to be recovered first.".to_string()); + return Err(tr::editor::window::open::no_segments().to_string()); } // The panicking call, contained. `AssertUnwindSafe` because neither @@ -430,10 +431,8 @@ pub fn preflight(path: &std::path::Path) -> Result { let recordings = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { ProjectRecordingsMeta::new(&owned_path, studio.as_ref()) })) - .map_err(|_| { - "This recording's video tracks could not be opened. The bundle looks damaged.".to_string() - })? - .map_err(|error| format!("Failed to read this recording's media: {error}"))?; + .map_err(|_| tr::editor::window::open::damaged().to_string())? + .map_err(tr::editor::window::open::media_failed)?; let recordings = Arc::new(recordings); // `RecordingMeta::project_config()` loads `project-config.json` (falling @@ -508,9 +507,6 @@ pub fn preflight(path: &std::path::Path) -> Result { }) } -const NO_AUTO_ZOOM_CLICKS_MESSAGE: &str = - "No clicks found to zoom into. Drag across the lane to add one."; - fn load_recording_clicks( project_path: &std::path::Path, ) -> Vec> { @@ -1455,7 +1451,9 @@ pub struct EditorWindow { /// `isGeneratingAutoZoom` / `sessionDismissedGenerateZoomPrompt` /// (`TL/ZoomTrack.tsx:60-65`). generating_auto_zoom: bool, - auto_zoom_message: Option<&'static str>, + auto_zoom_message: Option &'static str>, + /// The language the timeline model's placeholder labels were built in. + timeline_locale: crate::i18n::Locale, zoom_prompt_dismissed: bool, hovering_generate_zoom: bool, pub(crate) generating_keyboard: bool, @@ -1938,7 +1936,10 @@ impl EditorWindow { return false; } cx.spawn(async move |_| { - crate::platform::alert_dialog("Recording retained", &message); + crate::platform::alert_dialog( + tr::editor::modal::recording_retained(), + &message, + ); }) .detach(); return false; @@ -1995,7 +1996,7 @@ impl EditorWindow { )); let preset_name_input = cx.new(|cx| { let mut input = ui::TextInputState::single_line(window, cx); - input.set_placeholder("Preset name"); + input.set_placeholder(tr::editor::window::presets::name_placeholder()); input }); text_events.push(cx.subscribe_in( @@ -2099,6 +2100,7 @@ impl EditorWindow { playback_tick: 0, generating_auto_zoom: false, auto_zoom_message: None, + timeline_locale: crate::i18n::current(), zoom_prompt_dismissed: false, hovering_generate_zoom: false, generating_keyboard: false, @@ -2197,12 +2199,10 @@ impl EditorWindow { pub(crate) fn flush_pending_saves(&mut self, cx: &mut Context) -> Result<(), String> { self.commit_pretty_name(cx)?; - self.pending_save.borrow_mut().try_flush().map_err(|error| { - format!( - "Could not save {}. Keep the editor open and try again: {error}", - self.project_path.display() - ) - }) + self.pending_save + .borrow_mut() + .try_flush() + .map_err(|error| tr::editor::window::save_failed(self.project_path.display(), error)) } /// Every lazily-created field's subscription lives here for the window's @@ -4787,7 +4787,7 @@ impl EditorWindow { }); let Some((preview, (start, end))) = ghost else { if kind == TrackKind::ThreeD { - self.show_notice(scenes::CAMERA3D_NO_ROOM, window, cx); + self.show_notice(tr::editor::window::no_room_for_3d_shot(), window, cx); } return; }; @@ -5560,11 +5560,7 @@ impl EditorWindow { fn close_stale_audio_picker(&mut self, window: &mut Window, cx: &mut Context) { self.audio_picker = None; self.audio_picker_request = None; - self.show_notice( - "This audio segment changed. Select it and choose a replacement again.", - window, - cx, - ); + self.show_notice(tr::editor::window::audio_segment_changed(), window, cx); } pub(crate) fn add_library_track( @@ -5677,7 +5673,7 @@ impl EditorWindow { cx.spawn_in(window, async move |this, cx| { #[cfg(target_os = "linux")] let source = crate::platform::open_file_panel_async( - &[("Audio", AUDIO_IMPORT_EXTENSIONS)], + &[(tr::editor::window::audio_filter(), AUDIO_IMPORT_EXTENSIONS)], None, cx, ) @@ -5765,7 +5761,7 @@ impl EditorWindow { }); if segments.is_empty() { tracing::info!("auto zoom produced no segments"); - this.auto_zoom_message = Some(NO_AUTO_ZOOM_CLICKS_MESSAGE); + this.auto_zoom_message = Some(tr::editor::window::zoom_lane::no_clicks); cx.notify(); return; } @@ -6583,6 +6579,23 @@ impl EditorWindow { self.theme.refresh(window, cx, false); } + fn auto_zoom_prompt(&self) -> &'static str { + self.auto_zoom_message + .unwrap_or(tr::editor::window::zoom_lane::prompt)() + } + + /// Track, clip and segment placeholder names live in the timeline model, + /// so a language change rebuilds it. + fn sync_locale(&mut self) { + let locale = crate::i18n::current(); + if self.timeline_locale != locale { + self.timeline_locale = locale; + if self.project_ready() { + self.rebuild_timeline(); + } + } + } + /// `NameEditor`'s commit (`Header.tsx:303-318`), verbatim. /// /// Return and Escape both call `blur()` there, and it is `onBlur` that @@ -6603,8 +6616,8 @@ impl EditorWindow { tracing::error!(%error, "recording title was not saved"); cx.spawn(async move |_, _| { crate::platform::alert_dialog( - "Title not saved", - &format!("{error}\n\nYour title is still in the editor. Try saving it again."), + tr::editor::window::title::not_saved(), + &tr::editor::window::title::not_saved_message(error), ); }) .detach(); @@ -6644,19 +6657,11 @@ impl EditorWindow { // history is `createStoreHistory` over the *project* store alone // (`ED/context.ts:1920-1930`), so a rename is not undoable there either. let path = self.project_path.clone(); - let mut meta = RecordingMeta::load_for_project(&path).map_err(|error| { - format!( - "Could not read the recording title for {}: {error}", - path.display() - ) - })?; + let mut meta = RecordingMeta::load_for_project(&path) + .map_err(|error| tr::editor::window::title::read_failed(path.display(), error))?; meta.pretty_name = draft.clone(); - meta.save_for_project().map_err(|error| { - format!( - "Could not save the recording title for {}: {error}", - path.display() - ) - })?; + meta.save_for_project() + .map_err(|error| tr::editor::window::title::save_failed(path.display(), error))?; if let LoadState::Ready(summary) = &mut self.state { summary.pretty_name = draft.clone(); } @@ -6912,7 +6917,14 @@ impl EditorWindow { ui::EditorButton::plain(&theme, id) .left_icon(icon) .disabled(!enabled) - .tooltip(&theme, if undo { "Undo" } else { "Redo" }) + .tooltip( + &theme, + if undo { + tr::editor::window::undo() + } else { + tr::editor::window::redo() + }, + ) .on_click(cx.listener(move |this, _, window, cx| { if !(this.history.can_undo() || this.history.can_redo() || this.selection.is_some()) { @@ -6933,31 +6945,47 @@ impl EditorWindow { fn aspect_ratio_label(aspect: Option<&cap_project::AspectRatio>) -> &'static str { match aspect { - None => "Auto", - Some(cap_project::AspectRatio::Wide) => "Wide", - Some(cap_project::AspectRatio::Vertical) => "Vertical", - Some(cap_project::AspectRatio::Square) => "Square", - Some(cap_project::AspectRatio::Classic) => "Classic", - Some(cap_project::AspectRatio::Tall) => "Tall", + None => tr::editor::window::aspect::auto(), + Some(cap_project::AspectRatio::Wide) => tr::editor::window::aspect::wide(), + Some(cap_project::AspectRatio::Vertical) => tr::editor::window::aspect::vertical(), + Some(cap_project::AspectRatio::Square) => tr::editor::window::aspect::square(), + Some(cap_project::AspectRatio::Classic) => tr::editor::window::aspect::classic(), + Some(cap_project::AspectRatio::Tall) => tr::editor::window::aspect::tall(), } } fn toolbar_menu_items(&self, kind: ToolbarMenu) -> Vec { match kind { ToolbarMenu::AspectRatio => { - const OPTIONS: &[(Option, &str)] = &[ - (None, "Auto"), - (Some(cap_project::AspectRatio::Wide), "Wide ⋅16:9"), - (Some(cap_project::AspectRatio::Vertical), "Vertical ⋅9:16"), - (Some(cap_project::AspectRatio::Square), "Square ⋅1:1"), - (Some(cap_project::AspectRatio::Classic), "Classic ⋅4:3"), - (Some(cap_project::AspectRatio::Tall), "Tall ⋅3:4"), + type Label = fn() -> &'static str; + const OPTIONS: &[(Option, Label)] = &[ + (None, tr::editor::window::aspect::auto), + ( + Some(cap_project::AspectRatio::Wide), + tr::editor::window::aspect::wide_ratio, + ), + ( + Some(cap_project::AspectRatio::Vertical), + tr::editor::window::aspect::vertical_ratio, + ), + ( + Some(cap_project::AspectRatio::Square), + tr::editor::window::aspect::square_ratio, + ), + ( + Some(cap_project::AspectRatio::Classic), + tr::editor::window::aspect::classic_ratio, + ), + ( + Some(cap_project::AspectRatio::Tall), + tr::editor::window::aspect::tall_ratio, + ), ]; OPTIONS .iter() .map(|(value, label)| { ui::MenuItem::new( - *label, + label(), aspect_ratio_eq(value, &self.project.aspect_ratio), ) }) @@ -7111,11 +7139,9 @@ impl EditorWindow { .as_ref() .is_some_and(|export| export.phase.is_busy()) { - Some( - "Wait for the export or its cancellation to finish before closing or deleting this recording.", - ) + Some(tr::editor::window::busy::export()) } else if self.clips.is_importing() { - Some("Wait for the clip import to finish before closing or deleting this recording.") + Some(tr::editor::window::busy::clip_import()) } else { None } @@ -7124,7 +7150,7 @@ impl EditorWindow { pub(crate) fn deletion_blocked_reason(&self) -> Option<&'static str> { self.busy_reason().or((self.instance.is_none() && !matches!(self.state, LoadState::Failed(_))) - .then_some("Wait for the recording to finish opening before deleting it.")) + .then(tr::editor::window::busy::opening)) } pub(crate) fn prepare_for_deletion( @@ -7137,7 +7163,7 @@ impl EditorWindow { self.pending_save .borrow_mut() .try_flush() - .map_err(|error| format!("Could not save the recording before deleting it: {error}"))?; + .map_err(tr::editor::window::busy::save_before_delete_failed)?; self.save_task = None; self.stop_playback(cx); self.export = None; @@ -7701,14 +7727,20 @@ impl EditorWindow { cx.spawn_in(window, async move |this, cx| { #[cfg(target_os = "linux")] let source = crate::platform::open_file_panel_async( - &[("Images", crate::import::OVERLAY_IMAGE_EXTENSIONS)], + &[( + tr::editor::window::image::filter(), + crate::import::OVERLAY_IMAGE_EXTENSIONS, + )], None, cx, ) .await; #[cfg(not(target_os = "linux"))] let source = crate::import::pick_import_file( - &[("Images", crate::import::OVERLAY_IMAGE_EXTENSIONS)], + &[( + tr::editor::window::image::filter(), + crate::import::OVERLAY_IMAGE_EXTENSIONS, + )], cx, ) .await; @@ -7731,14 +7763,23 @@ impl EditorWindow { match imported { Some(Ok(imported)) => { if let Some((index, fingerprint)) = replace { - if edits::replace_image_asset(&mut this.project, index, &fingerprint, imported.path, imported.name) { + if edits::replace_image_asset( + &mut this.project, + index, + &fingerprint, + imported.path, + imported.name, + ) { this.project_changed(window, cx); this.refresh_image_asset_status(); } else { - this.sidebar.image_import_error = Some("This image changed while choosing a replacement. Select it and try again.".into()); + this.sidebar.image_import_error = + Some(tr::editor::window::image::changed().into()); cx.notify(); } - } else { this.commit_image_import(lane, time, imported, window, cx); } + } else { + this.commit_image_import(lane, time, imported, window, cx); + } } Some(Err(error)) => { this.sidebar.image_import_error = Some(error); @@ -7762,27 +7803,21 @@ impl EditorWindow { cx: &mut Context, ) { if !edits::ensure_timeline(&mut self.project, &self.clip_display_durations) { - self.sidebar.image_import_error = Some( - "The recording is no longer available for this image. Please reopen the project." - .into(), - ); + self.sidebar.image_import_error = + Some(tr::editor::window::image::recording_unavailable().into()); cx.notify(); return; } let total = self.total_duration(); if total <= 0.0 { - self.sidebar.image_import_error = Some( - "The recording is no longer available for this image. Please reopen the project." - .into(), - ); + self.sidebar.image_import_error = + Some(tr::editor::window::image::recording_unavailable().into()); cx.notify(); return; } let Some(timeline) = self.project.timeline.as_ref() else { - self.sidebar.image_import_error = Some( - "The recording is no longer available for this image. Please reopen the project." - .into(), - ); + self.sidebar.image_import_error = + Some(tr::editor::window::image::recording_unavailable().into()); cx.notify(); return; }; @@ -8120,25 +8155,25 @@ impl EditorWindow { let count = indices.len(); let can_paste = self.text_style_clipboard.is_some(); let title: SharedString = if count > 1 { - format!("{count} text segments").into() + tr::editor::window::text_menu::segments(count).into() } else { let content = segment.content.trim(); if content.is_empty() { - "Text".into() + tr::editor::window::text_menu::untitled().into() } else { content.to_string().into() } }; let summary = crate::editor_clips::format_clip_duration(segment.end - segment.start); let paste_label: &'static str = if count > 1 { - "Paste style to all selected" + tr::editor::window::text_menu::paste_style_all() } else { - "Paste style" + tr::editor::window::text_menu::paste_style() }; let delete_label: &'static str = if count > 1 { - "Delete text segments" + tr::editor::window::text_menu::delete_segments() } else { - "Delete text segment" + tr::editor::window::text_menu::delete_segment() }; let divider = || div().h(px(1.)).bg(Hsla::from(theme.editor.line)); @@ -8203,7 +8238,7 @@ impl EditorWindow { &theme, "text-menu-copy-style", "icons/copy.svg", - "Copy style", + tr::editor::window::text_menu::copy_style(), ClipMenuTrailing::None, true, false, @@ -8225,7 +8260,9 @@ impl EditorWindow { if can_paste { ClipMenuTrailing::None } else { - ClipMenuTrailing::Hint("Copy a style first") + ClipMenuTrailing::Hint( + tr::editor::window::text_menu::copy_style_first(), + ) }, can_paste, false, @@ -8533,9 +8570,18 @@ impl EditorWindow { .map(|(_, input)| input); let speeds = [0.25, 0.5, 1.0, 1.5, 2.0, 4.0, 8.0]; let audio_modes = [ - (ClipSpeedAudioMode::Mute, "Mute"), - (ClipSpeedAudioMode::MaintainPitch, "Keep pitch"), - (ClipSpeedAudioMode::MatchSpeed, "Match speed"), + ( + ClipSpeedAudioMode::Mute, + tr::editor::window::clip_menu::mute(), + ), + ( + ClipSpeedAudioMode::MaintainPitch, + tr::editor::window::clip_menu::keep_pitch(), + ), + ( + ClipSpeedAudioMode::MatchSpeed, + tr::editor::window::clip_menu::match_speed(), + ), ]; let chip = |id: SharedString, selected: bool, label: String| { div() @@ -8630,7 +8676,10 @@ impl EditorWindow { .flex() .flex_col() .gap(px(4.)) - .child(clip_menu_section(&theme, "SPEED")) + .child(clip_menu_section( + &theme, + tr::editor::window::clip_menu::speed(), + )) .child(chip_row().children(speeds.into_iter().map(|speed| { let selected = (timescale - speed).abs() < 1e-6; chip( @@ -8650,9 +8699,12 @@ impl EditorWindow { .flex() .flex_col() .gap(px(4.)) - .child(clip_menu_section(&theme, "AUDIO")) + .child(clip_menu_section( + &theme, + tr::editor::window::clip_menu::audio(), + )) .child(self.slider_field_disabled( - "Volume", + tr::editor::window::clip_menu::volume(), crate::editor_sidebar::SliderKey::ClipVolume(index), "%", muted, @@ -8663,8 +8715,8 @@ impl EditorWindow { &theme, "clip-menu-mute", "icons/volume-x.svg", - "Mute clip", - "Silence this clip's audio", + tr::editor::window::clip_menu::mute_clip(), + tr::editor::window::clip_menu::mute_clip_description(), muted, ) .on_click(cx.listener(move |this, _, window, cx| { @@ -8695,14 +8747,17 @@ impl EditorWindow { .flex() .flex_col() .gap(px(4.)) - .child(clip_menu_section(&theme, "CURSOR")) + .child(clip_menu_section( + &theme, + tr::editor::window::clip_menu::cursor(), + )) .child( clip_menu_toggle_row( &theme, "clip-menu-hide-cursor", "icons/mouse-pointer-ban.svg", - "Hide cursor", - "Fades out while this clip plays", + tr::editor::window::clip_menu::hide_cursor(), + tr::editor::window::clip_menu::hide_cursor_description(), cursor_hidden, ) .on_click(cx.listener( @@ -8723,11 +8778,11 @@ impl EditorWindow { &theme, "clip-menu-split", "icons/scissors.svg", - "Split at playhead", + tr::editor::window::clip_menu::split(), if can_split { ClipMenuTrailing::Kbd("C") } else { - ClipMenuTrailing::Hint("Move the playhead into this clip") + ClipMenuTrailing::Hint(tr::editor::window::clip_menu::split_hint()) }, can_split, false, @@ -8743,7 +8798,7 @@ impl EditorWindow { &theme, "clip-menu-merge-previous", "icons/arrow-left-to-line.svg", - "Merge with previous clip", + tr::editor::window::clip_menu::merge_previous(), merge_previous.map_or(ClipMenuTrailing::None, |blocker| { ClipMenuTrailing::Hint(blocker.hint()) }), @@ -8766,7 +8821,7 @@ impl EditorWindow { &theme, "clip-menu-merge-next", "icons/arrow-right-to-line.svg", - "Merge with next clip", + tr::editor::window::clip_menu::merge_next(), merge_next.map_or(ClipMenuTrailing::None, |blocker| { ClipMenuTrailing::Hint(blocker.hint()) }), @@ -8792,7 +8847,7 @@ impl EditorWindow { &theme, "clip-menu-rename-row", "icons/pencil.svg", - "Rename clip", + tr::editor::window::clip_menu::rename(), ClipMenuTrailing::None, true, false, @@ -8809,11 +8864,11 @@ impl EditorWindow { &theme, "clip-menu-delete", "icons/trash.svg", - "Delete clip", + tr::editor::window::clip_menu::delete(), if can_delete { ClipMenuTrailing::Kbd("backspace") } else { - ClipMenuTrailing::Hint("Can't delete the only clip") + ClipMenuTrailing::Hint(tr::editor::window::clip_menu::delete_only_clip()) }, can_delete, true, @@ -8855,16 +8910,16 @@ impl EditorWindow { let theme = self.theme; let scene_available = self.scene_available(); let hint: SharedString = match menu.hovered { - None => "Choose a track to add to your timeline.".into(), + None => tr::editor::window::add_track::hint().into(), Some(kind) => { if kind == TrackKind::Scene && !scene_available { kind.picker_unavailable().into() } else if kind == TrackKind::ThreeD && self.tracks.is_active(kind) { // The 3D tile only shows and hides the lane; the shots on // it survive either way. - "Hide the 3D track.".into() + tr::editor::window::add_track::hide_3d().into() } else if !kind.supports_multiple() && self.tracks.is_active(kind) { - format!("Remove the {} track.", kind.picker_label()).into() + kind.picker_remove().into() } else { kind.picker_description().into() } @@ -9114,7 +9169,7 @@ impl EditorWindow { let mut rows: Vec = vec![self.render_preset_row( PresetRow::Default, - "Default".into(), + tr::editor::window::presets::default().into(), store.default.is_none(), cx, )]; @@ -9127,7 +9182,7 @@ impl EditorWindow { .h(px(32.)) .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_3)) - .child("No saved presets") + .child(tr::editor::window::presets::none_saved()) .into_any_element(), ); } else { @@ -9207,7 +9262,11 @@ impl EditorWindow { .hover(|this| this.bg(Hsla::from(theme.editor.ctl_hover))) .text_size(px(14.)) .text_color(Hsla::from(theme.editor.text_1)) - .child(div().flex_1().child("Create new preset")) + .child( + div() + .flex_1() + .child(tr::editor::window::presets::create_new()), + ) .child( svg() .path("icons/circle-plus.svg") @@ -9275,7 +9334,7 @@ impl EditorWindow { .text_size(px(11.)) .bg(Hsla::from(theme.editor.ctl)) .text_color(Hsla::from(theme.editor.text_2)) - .child("Default"), + .child(tr::editor::window::presets::default_badge()), ) }) .child( @@ -9369,20 +9428,26 @@ impl EditorWindow { .bg(Hsla::from(theme.editor.card)) .shadow(theme.editor.pop_shadow()) .child( - menu_row("preset-apply".into(), "Apply", false).on_click(cx.listener( - move |this, _, window, cx| match row { - PresetRow::Default => this.apply_default_preset(window, cx), - PresetRow::Saved(index) => this.apply_preset_at(index, window, cx), - }, - )), + menu_row( + "preset-apply".into(), + tr::editor::window::presets::apply(), + false, + ) + .on_click(cx.listener(move |this, _, window, cx| match row { + PresetRow::Default => this.apply_default_preset(window, cx), + PresetRow::Saved(index) => this.apply_preset_at(index, window, cx), + })), ) .when(row == PresetRow::Default, |this| { this.child( - menu_row("preset-clear-default".into(), "Set as default", false).on_click( - cx.listener(move |this, _, _window, cx| { - this.mutate_presets(cx, |store| store.clear_default()); - }), - ), + menu_row( + "preset-clear-default".into(), + tr::editor::window::presets::set_default(), + false, + ) + .on_click(cx.listener(move |this, _, _window, cx| { + this.mutate_presets(cx, |store| store.clear_default()); + })), ) }) .when_some( @@ -9392,29 +9457,49 @@ impl EditorWindow { }, |this, index| { this.child( - menu_row("preset-save".into(), "Save settings to preset", false).on_click( - cx.listener(move |this, _, _window, cx| { + menu_row( + "preset-save".into(), + tr::editor::window::presets::save_settings(), + false, + ) + .on_click(cx.listener( + move |this, _, _window, cx| { let config = crate::presets::preset_config(&this.project); this.mutate_presets(cx, |store| store.save_to(index, config)); - }), - ), + }, + )), ) .child( - menu_row("preset-default".into(), "Set as default", false).on_click( - cx.listener(move |this, _, _window, cx| { + menu_row( + "preset-default".into(), + tr::editor::window::presets::set_default(), + false, + ) + .on_click(cx.listener( + move |this, _, _window, cx| { this.mutate_presets(cx, |store| store.set_default(index)); - }), - ), + }, + )), ) .child( - menu_row("preset-rename".into(), "Rename", false).on_click(cx.listener( + menu_row( + "preset-rename".into(), + tr::editor::window::presets::rename(), + false, + ) + .on_click(cx.listener( move |this, _, window, cx| { this.open_preset_dialog(PresetDialog::Rename { index }, window, cx); }, )), ) .child( - menu_row("preset-delete".into(), "Delete", true).on_click(cx.listener( + menu_row( + "preset-delete".into(), + tr::editor::window::presets::delete(), + true, + ) + .on_click(cx.listener( move |this, _, window, cx| { this.open_preset_dialog(PresetDialog::Delete { index }, window, cx); }, @@ -9431,24 +9516,40 @@ impl EditorWindow { let dialog = self.preset_dialog?; let theme = self.theme; + self.preset_name_input.update(cx, |input, _| { + input.set_placeholder(tr::editor::window::presets::name_placeholder()) + }); let (title, primary, danger) = match dialog { - PresetDialog::Create { .. } => ("Create Preset", "Create", false), - PresetDialog::Rename { .. } => ("Rename Preset", "Rename", false), - PresetDialog::Delete { .. } => ("Delete Preset", "Delete", true), + PresetDialog::Create { .. } => ( + tr::editor::window::presets::create_title(), + tr::editor::window::presets::create(), + false, + ), + PresetDialog::Rename { .. } => ( + tr::editor::window::presets::rename_title(), + tr::editor::window::presets::rename(), + false, + ), + PresetDialog::Delete { .. } => ( + tr::editor::window::presets::delete_title(), + tr::editor::window::presets::delete(), + true, + ), }; let body: gpui::AnyElement = match dialog { PresetDialog::Delete { index } => div() .text_size(px(14.)) .text_color(Hsla::from(theme.editor.text_2)) - .child(SharedString::from(format!( - "Are you sure you want to delete \"{}\"?", - crate::presets::PresetsStore::load() - .presets - .get(index) - .map(|entry| entry.name.clone()) - .unwrap_or_default() - ))) + .child(SharedString::from( + tr::editor::window::presets::delete_confirm( + crate::presets::PresetsStore::load() + .presets + .get(index) + .map(|entry| entry.name.clone()) + .unwrap_or_default(), + ), + )) .into_any_element(), PresetDialog::Create { default } => div() .flex() @@ -9471,7 +9572,7 @@ impl EditorWindow { div() .text_size(px(14.)) .text_color(Hsla::from(theme.editor.text_1)) - .child("Set as default"), + .child(tr::editor::window::presets::set_default()), ) .child( ui::Toggle::plain(&theme, "preset-default", default) @@ -9557,7 +9658,7 @@ impl EditorWindow { ui::ButtonVariant::Gray, ui::ButtonSize::Md, ) - .label("Cancel") + .label(tr::common::cancel()) .on_click(cx.listener( |this, _, _window, cx| { this.preset_dialog = None; @@ -9765,7 +9866,7 @@ impl EditorWindow { .child( ui::EditorButton::plain(&theme, "open-recording-bundle") .left_icon("icons/folder.svg") - .tooltip(&theme, "Open recording bundle") + .tooltip(&theme, tr::editor::window::header::open_bundle()) .disabled(!self.visual_ready()) .on_click(cx.listener(|this, _, _window, cx| { this.open_recording_bundle(cx); @@ -9774,7 +9875,7 @@ impl EditorWindow { .child( ui::EditorButton::plain(&theme, "delete-recording") .left_icon("icons/trash.svg") - .tooltip(&theme, "Delete recording") + .tooltip(&theme, tr::editor::window::header::delete_recording()) .disabled(!self.visual_ready()) .on_click(cx.listener(|this, _, window, cx| { this.delete_recording(window, cx); @@ -9797,8 +9898,10 @@ impl EditorWindow { .child( ui::EditorButton::plain(&theme, "presets") .left_icon("icons/presets.svg") - .when(!compact, |button| button.label("Presets")) - .tooltip(&theme, "Presets") + .when(!compact, |button| { + button.label(tr::editor::window::header::presets()) + }) + .tooltip(&theme, tr::editor::window::header::presets()) .disabled(!self.project_ready()) .right_icon("icons/chevron-down.svg") .pressed(self.presets_menu.is_some()) @@ -9809,10 +9912,13 @@ impl EditorWindow { .child(self.render_clips_pill(compact, cx)) // ``: the button only exists // once a transcript with words does. - .children( - has_captions - .then(|| self.header_pill("icons/captions.svg", "Captions", compact)), - ) + .children(has_captions.then(|| { + self.header_pill( + "icons/captions.svg", + tr::editor::window::header::captions(), + compact, + ) + })) .child(div().w(px(6.)).flex_none()) .when(self.sharing.is_some(), |group| { group.child(self.render_reupload_button(cx)) @@ -9928,7 +10034,7 @@ impl EditorWindow { .flex_shrink_0() .text_color(gpui::white()), ) - .child("Export") + .child(tr::editor::window::header::export()) .when(self.project_ready(), |button| { button.on_click(cx.listener(|this, _, window, cx| { if this.project_ready() { @@ -10088,7 +10194,7 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_2)) - .child("Preview"), + .child(tr::editor::window::preview::label()), ) .child(self.render_preview_quality(cx)), ) @@ -10115,7 +10221,7 @@ impl EditorWindow { .as_ref(), )) }) - .tooltip(&theme, "Aspect Ratio") + .tooltip(&theme, tr::editor::window::aspect::tooltip()) .disabled(!self.project_ready()) .pressed( self.toolbar_menu @@ -10132,8 +10238,8 @@ impl EditorWindow { ui::EditorButton::plain(&theme, "crop") .text(&theme) .left_icon("icons/crop.svg") - .when(!narrow, |button| button.label("Crop")) - .tooltip(&theme, "Crop Video") + .when(!narrow, |button| button.label(tr::editor::window::crop())) + .tooltip(&theme, tr::editor::window::crop_tooltip()) .disabled(!self.project_ready()) .pressed(self.crop.is_some()) .on_click(cx.listener(|this, _, window, cx| { @@ -10192,7 +10298,17 @@ impl EditorWindow { .hover(|style| style.bg(Hsla::from(editor.ctl_hover))) } }) - .child(quality.label()) + .child(match quality { + crate::store::EditorPreviewQuality::Quarter => { + tr::editor::window::preview::quarter() + } + crate::store::EditorPreviewQuality::Half => { + tr::editor::window::preview::half() + } + crate::store::EditorPreviewQuality::Full => { + tr::editor::window::preview::full() + } + }) .when(self.project_ready(), |button| { button.on_click(cx.listener(move |this, _, _window, cx| { this.set_preview_quality(quality, cx); @@ -10271,7 +10387,7 @@ impl EditorWindow { .text_size(px(15.)) .font_weight(FontWeight::MEDIUM) .text_color(Hsla::from(theme.editor.text_1)) - .child("This recording could not be opened"), + .child(tr::editor::window::open::failed()), ) .child( div() @@ -10300,9 +10416,9 @@ impl EditorWindow { ) -> impl IntoElement { let theme = self.theme; let (label, key) = if factor > 1. { - ("Zoom out", "-") + (tr::editor::window::transport::zoom_out(), "-") } else { - ("Zoom in", "+") + (tr::editor::window::transport::zoom_in(), "+") }; let modifier = if cfg!(target_os = "macos") { "meta" @@ -10471,9 +10587,12 @@ impl EditorWindow { .flex() .tooltip_show_delay(ui::TOOLTIP_SHOW_DELAY) .tooltip(move |_window, cx| { - ui::Tooltip::new(&theme, "Play/Pause video") - .keys(["Space"]) - .view(cx) + ui::Tooltip::new( + &theme, + tr::editor::window::transport::play_pause(), + ) + .keys(["Space"]) + .view(cx) }) .child( ui::IconButton::new("transport-play", icon) @@ -10513,9 +10632,12 @@ impl EditorWindow { .id("transport-split-tooltip") .tooltip_show_delay(crate::ui::TOOLTIP_SHOW_DELAY) .tooltip(move |_, cx| { - ui::Tooltip::new(&theme, "Click a segment to split. Esc to finish.") - .keys(["S"]) - .view(cx) + ui::Tooltip::new( + &theme, + tr::editor::window::transport::split_tooltip(), + ) + .keys(["S"]) + .view(cx) }) .child( ui::Button::plain( @@ -10530,9 +10652,9 @@ impl EditorWindow { ) .icon("icons/scissors.svg") .label(if self.split_mode { - "Done splitting" + tr::editor::window::transport::done_splitting() } else { - "Split" + tr::editor::window::transport::split() }) .radius(px(7.)) .disabled(!self.project_ready()) @@ -10848,7 +10970,12 @@ impl EditorWindow { .flex_none() .text_color(Hsla::from(theme.editor.card)), ) - .child(div().flex_none().whitespace_nowrap().child("Add track")) + .child( + div() + .flex_none() + .whitespace_nowrap() + .child(tr::editor::window::add_track::button()), + ) .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .on_click(cx.listener(|this, _: &gpui::ClickEvent, window, cx| { this.open_add_track(window, cx); @@ -10986,14 +11113,16 @@ impl EditorWindow { // segments. The clip row has none. let delete_label = match kind { TrackKind::Clip => None, - TrackKind::Caption | TrackKind::Keyboard => Some("Delete"), + TrackKind::Caption | TrackKind::Keyboard => { + Some(tr::editor::window::track::delete()) + } TrackKind::Text | TrackKind::Mask | TrackKind::Audio | TrackKind::Style - | TrackKind::Image => Some("Delete"), + | TrackKind::Image => Some(tr::editor::window::track::delete()), TrackKind::Zoom | TrackKind::ThreeD | TrackKind::Scene => { - (!model.segments(kind).is_empty()).then_some("Clear all") + (!model.segments(kind).is_empty()).then(tr::editor::window::track::clear_all) } }; rows = rows.child( @@ -11160,9 +11289,9 @@ impl EditorWindow { cx.notify(); } })) - .child(div().min_w_0().truncate().child( - self.auto_zoom_message.unwrap_or("Add zooms automatically"), - )) + .child( + div().min_w_0().truncate().child(self.auto_zoom_prompt()), + ) .child( ui::Button::plain( &theme, @@ -11171,9 +11300,9 @@ impl EditorWindow { ui::ButtonSize::Sm, ) .label(if generating { - "Generating…" + tr::editor::window::zoom_lane::generating() } else { - "Generate" + tr::editor::window::zoom_lane::generate() }) .radius(px(7.)) .disabled(generating) @@ -11238,7 +11367,7 @@ impl EditorWindow { div() .min_w_0() .truncate() - .child("Add cinematic 3D shots to your recording"), + .child(tr::editor::window::camera3d_lane::prompt()), ) .child( ui::Button::plain( @@ -11247,7 +11376,7 @@ impl EditorWindow { ui::ButtonVariant::Primary, ui::ButtonSize::Sm, ) - .label("Auto scene") + .label(tr::editor::window::camera3d_lane::auto_scene()) .radius(px(7.)) .on_click(cx.listener( |this, _, _window, cx| { @@ -11265,7 +11394,7 @@ impl EditorWindow { .min_w_0() .truncate() .text_color(Hsla::from(theme.editor.text_2)) - .child("How many shots?"), + .child(tr::editor::window::camera3d_lane::how_many()), ) .child(self.render_camera3d_count_pills("lane", 0, maximum, cx)) .child( @@ -11286,7 +11415,7 @@ impl EditorWindow { ui::ButtonVariant::Outline, ui::ButtonSize::Sm, ) - .label("+ Add shot") + .label(tr::editor::window::camera3d_lane::add_shot()) .radius(px(7.)) .on_click(cx.listener( |this, _, window, cx| { @@ -11566,6 +11695,7 @@ fn is_playback_shortcut( impl Render for EditorWindow { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { self.sync_appearance(window, cx); + self.sync_locale(); let title_disabled = !self.visual_ready(); if self.name_input.read(cx).is_disabled() != title_disabled { self.name_input @@ -12111,12 +12241,14 @@ fn import_audio_file( .extension() .and_then(|ext| ext.to_str()) .map(|ext| ext.to_ascii_lowercase()) - .ok_or_else(|| "Audio file has no extension".to_string())?; + .ok_or_else(|| tr::editor::window::audio_import::no_extension().to_string())?; if !AUDIO_IMPORT_EXTENSIONS.contains(&extension.as_str()) { - return Err(format!("Unsupported audio format: .{extension}")); + return Err(tr::editor::window::audio_import::unsupported(extension)); } if !source.exists() { - return Err(format!("Audio file not found: {}", source.display())); + return Err(tr::editor::window::audio_import::not_found( + source.display(), + )); } let display_name = source .file_stem() @@ -12125,14 +12257,14 @@ fn import_audio_file( .to_string(); let audio_dir = project_path.join("assets").join("audio"); std::fs::create_dir_all(&audio_dir) - .map_err(|error| format!("Failed to create audio directory: {error}"))?; + .map_err(tr::editor::window::audio_import::directory_failed)?; let stamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|duration| duration.as_nanos()) .unwrap_or(0); let dest_name = format!("{stamp}.{extension}"); let dest = audio_dir.join(&dest_name); - std::fs::copy(source, &dest).map_err(|error| format!("Failed to copy audio file: {error}"))?; + std::fs::copy(source, &dest).map_err(tr::editor::window::audio_import::copy_failed)?; Ok(ImportedAudio { path: format!("assets/audio/{dest_name}"), name: display_name, diff --git a/apps/desktop-gpui/src/editor_window/frame.rs b/apps/desktop-gpui/src/editor_window/frame.rs index 1394bc61437..ba13e7bd36a 100644 --- a/apps/desktop-gpui/src/editor_window/frame.rs +++ b/apps/desktop-gpui/src/editor_window/frame.rs @@ -8,37 +8,39 @@ use gpui::{ }; use super::EditorWindow; -use crate::ui; +use crate::{i18n::tr, ui}; -const FRAME_STYLES: [(FrameStyle, &str, &str, &str); 5] = [ +type Text = fn() -> &'static str; + +const FRAME_STYLES: [(FrameStyle, Text, Text, &str); 5] = [ ( FrameStyle::None, - "None", - "Show the recording as-is", + tr::editor::window::frame::none, + tr::editor::window::frame::none_description, "icons/ban.svg", ), ( FrameStyle::MacOS, - "macOS", - "Window chrome with traffic lights", + || "macOS", + tr::editor::window::frame::macos_description, "icons/app-window-mac.svg", ), ( FrameStyle::Windows, - "Windows", - "Title bar with window controls", + || "Windows", + tr::editor::window::frame::windows_description, "icons/app-window.svg", ), ( FrameStyle::Browser, - "Browser", - "Browser chrome with address bar", + tr::editor::window::frame::browser, + tr::editor::window::frame::browser_description, "icons/globe.svg", ), ( FrameStyle::Macbook, - "MacBook", - "Laptop bezel around the recording", + || "MacBook", + tr::editor::window::frame::macbook_description, "icons/laptop.svg", ), ]; @@ -102,8 +104,15 @@ impl FrameField { fn label(self) -> &'static str { match self { - Self::Url => "URL", - Self::Title => "Title", + Self::Url => tr::editor::window::frame::url(), + Self::Title => tr::editor::window::frame::title(), + } + } + + fn placeholder(self) -> &'static str { + match self { + Self::Url => "cap.so", + Self::Title => tr::editor::window::frame::window_title_placeholder(), } } @@ -174,13 +183,19 @@ fn apply_targeted_frame_change( fn button_content(style: FrameStyle) -> (&'static str, &'static str) { if style == FrameStyle::None { - return ("Frame", "icons/app-window-mac.svg"); + return ( + tr::editor::window::frame::button(), + "icons/app-window-mac.svg", + ); } FRAME_STYLES .iter() .find(|option| option.0 == style) - .map(|option| (option.1, option.3)) - .unwrap_or(("Frame", "icons/app-window-mac.svg")) + .map(|option| ((option.1)(), option.3)) + .unwrap_or(( + tr::editor::window::frame::button(), + "icons/app-window-mac.svg", + )) } impl EditorWindow { @@ -252,7 +267,7 @@ impl EditorWindow { .left_icon(icon) .right_icon("icons/chevron-down.svg") .when(!narrow, |button| button.label(label)) - .tooltip(&self.theme, "Add a frame") + .tooltip(&self.theme, tr::editor::window::frame::add_tooltip()) .pressed(self.frame_controls.open) .disabled(self.instance.is_none()) .on_click(cx.listener(|this, _, window, cx| { @@ -287,10 +302,7 @@ impl EditorWindow { self.frame_controls.fields = Some([FrameField::Url, FrameField::Title].map(|field| { let input = cx.new(|cx| { let mut input = ui::TextInputState::single_line(window, cx); - input.set_placeholder(match field { - FrameField::Url => "cap.so", - FrameField::Title => "Window title", - }); + input.set_placeholder(field.placeholder()); input }); let subscription = @@ -305,6 +317,7 @@ impl EditorWindow { if let Some(inputs) = &self.frame_controls.fields { for field in [FrameField::Url, FrameField::Title] { let input = &inputs[field.index()]; + input.update(cx, |input, _| input.set_placeholder(field.placeholder())); if !input.read(cx).focus_handle().is_focused(window) { input.update(cx, |input, cx| { input.set_text(field.text(&frame).to_owned(), cx) @@ -415,13 +428,13 @@ impl EditorWindow { .text_size(px(13.)) .font_weight(FontWeight::SEMIBOLD) .text_color(theme.gray(12)) - .child("Frame"), + .child(tr::editor::window::frame::heading()), ) .child( div().text_size(px(11.)).text_color(theme.gray(10)).child( match &self.frame_controls.style_target { Some(target) if !target.matches(&self.project) => { - "This Style changed. Close and reopen Frame.".to_string() + tr::editor::window::frame::style_changed().to_string() } Some(target) if self @@ -435,19 +448,12 @@ impl EditorWindow { segment.overrides.background.is_none() }) => { - format!( - "Style {} only · Editing enables its background override.", - target.index + 1 - ) + tr::editor::window::frame::style_only_override(target.index + 1) } - Some(target) => format!( - "Style {} only · Global settings stay unchanged.", - target.index + 1 - ), - None => { - "Global frame · Applies wherever Style inherits background." - .to_string() + Some(target) => { + tr::editor::window::frame::style_only(target.index + 1) } + None => tr::editor::window::frame::global().to_string(), }, ), ), @@ -455,6 +461,7 @@ impl EditorWindow { .child(div().flex().flex_col().gap(px(2.)).p(px(6.)).children( FRAME_STYLES.into_iter().enumerate().map( |(index, (value, label, description, icon))| { + let (label, description) = (label(), description()); let selected = value == style; div() .id(("frame-style", index)) @@ -543,7 +550,7 @@ impl EditorWindow { .text_size(px(12.)) .font_weight(FontWeight::MEDIUM) .text_color(theme.gray(11)) - .child("Theme"), + .child(tr::editor::window::frame::theme()), ) .child( div() @@ -556,8 +563,14 @@ impl EditorWindow { .p(px(1.)) .children( [ - (FrameTheme::Light, "Light"), - (FrameTheme::Dark, "Dark"), + ( + FrameTheme::Light, + tr::editor::window::frame::light(), + ), + ( + FrameTheme::Dark, + tr::editor::window::frame::dark(), + ), ] .into_iter() .map( @@ -671,7 +684,7 @@ mod tests { #[test] fn frame_styles_and_button_labels_match_tauri() { assert_eq!( - FRAME_STYLES.map(|option| option.1), + FRAME_STYLES.map(|option| (option.1)()), ["None", "macOS", "Windows", "Browser", "MacBook"] ); assert_eq!( @@ -679,7 +692,7 @@ mod tests { ("Frame", "icons/app-window-mac.svg") ); for (style, label, _, icon) in FRAME_STYLES.into_iter().skip(1) { - assert_eq!(button_content(style), (label, icon)); + assert_eq!(button_content(style), (label(), icon)); } } diff --git a/apps/desktop-gpui/src/editor_window/scenes.rs b/apps/desktop-gpui/src/editor_window/scenes.rs index 4b2cd575237..b9ccebb3541 100644 --- a/apps/desktop-gpui/src/editor_window/scenes.rs +++ b/apps/desktop-gpui/src/editor_window/scenes.rs @@ -21,9 +21,6 @@ use cap_project::Camera3DSegment; /// What `Auto scene` builds when nobody has said how many shots they want. pub(crate) const CAMERA3D_AUTO_SHOTS: usize = 3; -/// What the editor says when the track has no room left. -pub(crate) const CAMERA3D_NO_ROOM: &str = "No room for another 3D shot"; - impl EditorWindow { /// Every shot's box, which is what the placement helper reasons over. pub(crate) fn camera3d_shot_ranges(&self) -> Vec<(f64, f64)> { @@ -75,7 +72,7 @@ impl EditorWindow { CAMERA3D_DEFAULT_SHOT_DURATION, self.total_duration(), ) else { - self.show_notice(CAMERA3D_NO_ROOM, window, cx); + self.show_notice(tr::editor::window::no_room_for_3d_shot(), window, cx); return; }; self.insert_camera3d_shots( @@ -142,7 +139,7 @@ impl EditorWindow { let cuts = self.camera3d_clip_cuts(); let generated = auto_camera3d_layout(count, 0., total, &cuts); if generated.is_empty() { - self.show_notice(CAMERA3D_NO_ROOM, window, cx); + self.show_notice(tr::editor::window::no_room_for_3d_shot(), window, cx); return; } self.camera3d_picker = false; diff --git a/apps/desktop-gpui/src/ui/segmented.rs b/apps/desktop-gpui/src/ui/segmented.rs index bdf080c6803..7caa5203869 100644 --- a/apps/desktop-gpui/src/ui/segmented.rs +++ b/apps/desktop-gpui/src/ui/segmented.rs @@ -314,7 +314,7 @@ impl RenderOnce for SegmentedControl { .when_some(item_height, |this, height| this.h(height)) .rounded(item_radius) .text_size(text_size) - .when(stretch, |this| this.flex_1()) + .when(stretch, |this| this.flex_1().min_w_0()) .when_some(font_weight, |this, weight| this.font_weight(weight)) .when(disabled, |this| this.opacity(0.5)) .map(|this| { @@ -347,7 +347,13 @@ impl RenderOnce for SegmentedControl { .flex_shrink_0() .text_color(if selected { selected_text } else { idle_text }) })) - .children(option.label) + .children(option.label.map(|label| { + if stretch { + div().min_w_0().truncate().child(label).into_any_element() + } else { + label.into_any_element() + } + })) .when_some(handler.filter(|_| !disabled), |this, handler| { this.on_click(move |_, window, cx| handler(&index, window, cx)) }) diff --git a/apps/desktop-gpui/src/ui/selection_header.rs b/apps/desktop-gpui/src/ui/selection_header.rs index e0d56fbdab7..0437c2d850f 100644 --- a/apps/desktop-gpui/src/ui/selection_header.rs +++ b/apps/desktop-gpui/src/ui/selection_header.rs @@ -19,28 +19,37 @@ use gpui::{ SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, }; -use crate::theme::Theme; +use crate::{i18n::tr, theme::Theme}; use super::{ClickHandler, EditorButton}; /// `{count} {noun} {segment|segments} selected` -- the sentence seven of the -/// eight headers build inline. +/// eight headers build inline. Each noun is its own message so translations +/// can inflect it with the count. pub fn selection_label(noun: &str, count: usize) -> String { - format!( - "{count} {noun} segment{} selected", - if count == 1 { "" } else { "s" } - ) + match noun { + "style" => tr::ui::selection::style(count), + "image" => tr::ui::selection::image(count), + "text" => tr::ui::selection::text_segments(count), + "caption" => tr::ui::selection::caption(count), + "audio" => tr::ui::selection::audio(count), + "mask" => tr::ui::selection::mask(count), + "keyboard" => tr::ui::selection::keyboard(count), + "3D" => tr::ui::selection::three_d(count), + "scene" => tr::ui::selection::scene(count), + _ => tr::ui::selection::segments(count), + } } /// The zoom header's own, terser wording (`ConfigSidebar.tsx:1528-1537`): the /// sidebar column is narrow, so the count drops the noun and gains a total. pub fn zoom_selection_label(count: usize, total: usize) -> String { if total > 1 && count == total { - format!("All {total} selected") + tr::ui::selection::zoom_all(total) } else if total > 1 { - format!("{count} of {total} selected") + tr::ui::selection::zoom_some(count, total) } else { - format!("{count} selected") + tr::ui::selection::zoom(count) } } @@ -129,7 +138,7 @@ impl RenderOnce for SelectionHeader { .child( EditorButton::plain(&theme, SharedString::from(format!("{id}-done"))) .left_icon("icons/check.svg") - .label("Done") + .label(tr::ui::selection::done()) .when_some(on_done, |button, handler| button.on_click(handler)), ) .child( @@ -146,7 +155,7 @@ impl RenderOnce for SelectionHeader { .font_weight(FontWeight::MEDIUM) .text_color(action_color) .cursor_pointer() - .child("Select all") + .child(tr::ui::selection::select_all()) .on_click(move |event, window, cx| handler(event, window, cx)), ) }), @@ -155,7 +164,7 @@ impl RenderOnce for SelectionHeader { EditorButton::plain(&theme, SharedString::from(format!("{id}-delete"))) .danger(&theme) .left_icon("icons/trash.svg") - .label("Delete") + .label(tr::ui::selection::delete()) .when_some(on_delete, |button, handler| button.on_click(handler)), ) } diff --git a/apps/desktop-gpui/src/ui/tooltip.rs b/apps/desktop-gpui/src/ui/tooltip.rs index 768883a5f77..3189c131564 100644 --- a/apps/desktop-gpui/src/ui/tooltip.rs +++ b/apps/desktop-gpui/src/ui/tooltip.rs @@ -21,6 +21,7 @@ use gpui::{ }; use crate::{ + i18n::tr, theme::Theme, ui::kbd::{KbdChip, kbd_symbol}, }; @@ -61,7 +62,7 @@ impl Tooltip { /// `ComingSoonTooltip`: always the literal text, instant delay at the call /// site (`openDelay={0} closeDelay={0}`). pub fn coming_soon(theme: &Theme) -> Self { - Self::new(theme, "Coming Soon") + Self::new(theme, tr::ui::coming_soon()) } /// `EditorButton`'s `kbd` prop: one chip per key, in order. From 075af0ca51b4505d8fde0254abbccc0914880822 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:54:34 +0000 Subject: [PATCH 09/25] test(desktop-gpui): pin the editor shell's English to the literals it replaced --- .../src/i18n/editor_shell_surface_tests.rs | 1453 +++++++++++++++++ apps/desktop-gpui/src/i18n/mod.rs | 2 + apps/desktop-gpui/src/i18n/tests.rs | 1 + 3 files changed, 1456 insertions(+) create mode 100644 apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs diff --git a/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs b/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs new file mode 100644 index 00000000000..aae6efb6922 --- /dev/null +++ b/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs @@ -0,0 +1,1453 @@ +use super::*; + +pub(super) fn is_editor_shell_surface_key(key: &str) -> bool { + [ + "editor.canvas.", + "editor.clips.", + "editor.export.", + "editor.modal.", + "editor.preparing.", + "editor.tabs.", + "editor.timeline.", + "editor.window.", + "ui.", + ] + .iter() + .any(|prefix| key.starts_with(prefix)) +} + +const FORMATTED: &[&str] = &[ + "editor.clips.append_error", + "editor.clips.clip", + "editor.clips.error.cannot_decode", + "editor.clips.error.in_use", + "editor.clips.error.missing_video", + "editor.clips.error.source_failed", + "editor.clips.error.unsupported_video", + "editor.clips.import_error", + "editor.clips.prepare_controls_failed", + "editor.clips.save_before_recording_failed", + "editor.clips.split", + "editor.export.estimate.gigabytes", + "editor.export.estimate.gigabytes_range", + "editor.export.estimate.hours", + "editor.export.estimate.hours_range", + "editor.export.estimate.megabytes", + "editor.export.estimate.megabytes_range", + "editor.export.estimate.minutes", + "editor.export.estimate.minutes_range", + "editor.export.estimate.seconds", + "editor.export.estimate.seconds_range", + "editor.export.fps", + "editor.export.frames", + "editor.export.stats.output_value", + "editor.tabs.captions.download", + "editor.tabs.captions.downloading", + "editor.tabs.captions.generate_failed", + "editor.tabs.captions.model_downloaded", + "editor.tabs.keyboard.load_failed", + "editor.tabs.sync.clip", + "editor.timeline.add_shot_with_look", + "editor.timeline.segment.numbered_clip", + "editor.window.audio_import.copy_failed", + "editor.window.audio_import.directory_failed", + "editor.window.audio_import.not_found", + "editor.window.audio_import.unsupported", + "editor.window.busy.save_before_delete_failed", + "editor.window.frame.style_only", + "editor.window.frame.style_only_override", + "editor.window.open.media_failed", + "editor.window.open.meta_failed", + "editor.window.open.not_found", + "editor.window.presets.delete_confirm", + "editor.window.save_failed", + "editor.window.text_menu.segments", + "editor.window.title.not_saved_message", + "editor.window.title.read_failed", + "editor.window.title.save_failed", + "ui.selection.audio", + "ui.selection.caption", + "ui.selection.image", + "ui.selection.keyboard", + "ui.selection.mask", + "ui.selection.scene", + "ui.selection.segments", + "ui.selection.style", + "ui.selection.text_segments", + "ui.selection.three_d", + "ui.selection.zoom", + "ui.selection.zoom_all", + "ui.selection.zoom_some", +]; + +#[test] +fn editor_shell_surfaces_match_the_literals_they_replaced() { + let pairs: &[(&str, &str)] = &[ + (tr::editor::canvas::camera(), "Camera"), + ( + tr::editor::canvas::camera_follows_zoom(), + "Camera size follows the zoom — drag to move", + ), + (tr::editor::canvas::edit_zoom(), "Edit zoom"), + (tr::editor::canvas::image(), "Image"), + (tr::editor::canvas::mask(), "Mask"), + (tr::editor::canvas::screen(), "Screen"), + ( + tr::editor::canvas::screen_locked(), + "Screen is locked while a zoom is active", + ), + (tr::editor::canvas::text_item(), "Text"), + (tr::editor::clips::append_error_title(), "Recording Error"), + (tr::editor::clips::back(), "Back to editor"), + (tr::editor::clips::cap_filter(), "Cap Recording"), + ( + tr::editor::clips::empty_description(), + "Record or import a clip and it will show up here.", + ), + (tr::editor::clips::empty_title(), "No clips yet"), + (tr::editor::clips::error::cancelled(), "Import cancelled"), + ( + tr::editor::clips::error::instant_source(), + "Instant recordings can't be imported into a project yet", + ), + ( + tr::editor::clips::error::instant_target(), + "Instant mode recordings cannot be edited", + ), + ( + tr::editor::clips::error::into_itself(), + "Cannot import a recording into itself", + ), + ( + tr::editor::clips::error::no_duration(), + "Imported video has no valid duration", + ), + ( + tr::editor::clips::error::not_a_project(), + "Select a Cap project folder", + ), + ( + tr::editor::clips::error::source_empty(), + "Source Cap project has no recording segments", + ), + ( + tr::editor::clips::error::source_recording(), + "Source Cap project is still recording", + ), + ( + tr::editor::clips::error::source_unimportable(), + "Source Cap project has no importable recording segments", + ), + ( + tr::editor::clips::error::unfinished_target(), + "Finish or recover this recording before importing a clip", + ), + (tr::editor::clips::import(), "Import"), + (tr::editor::clips::import_error_title(), "Import Error"), + (tr::editor::clips::import_existing(), "Existing recording"), + (tr::editor::clips::import_mp4(), "MP4 Video…"), + (tr::editor::clips::importing(), "Importing…"), + (tr::editor::clips::mp4_filter(), "MP4 Video"), + (tr::editor::clips::record_new(), "Record a new clip"), + (tr::editor::clips::title(), "Clips"), + ( + tr::editor::export::advanced::bits_per_pixel(), + "Bits per pixel", + ), + ( + tr::editor::export::advanced::cursor_only(), + "Export cursor only", + ), + ( + tr::editor::export::advanced::cursor_only_description(), + "Keeps the same cursor motion and clicks on a transparent background", + ), + ( + tr::editor::export::advanced::custom_bitrate(), + "Using a custom bitrate", + ), + ( + tr::editor::export::advanced::force_ffmpeg(), + "Force FFmpeg decoder", + ), + ( + tr::editor::export::advanced::force_ffmpeg_description(), + "Skip hardware decoder (auto-fallback enabled)", + ), + (tr::editor::export::advanced::title(), "Advanced"), + ( + tr::editor::export::advanced::transparent_mov(), + "Exports as a transparent MOV. Files are large and best for compositing or editing.", + ), + (tr::editor::export::back(), "Back to editor"), + (tr::editor::export::cta::cancel_sign_in(), "Cancel Sign In"), + (tr::editor::export::cta::clipboard(), "Export to Clipboard"), + ( + tr::editor::export::cta::create_link(), + "Create shareable link", + ), + (tr::editor::export::cta::file(), "Export to File"), + (tr::editor::export::cta::reupload(), "Reupload to same link"), + (tr::editor::export::cta::sign_in(), "Sign in to share"), + (tr::editor::export::destination::clipboard(), "Clipboard"), + (tr::editor::export::destination::file(), "File"), + (tr::editor::export::destination::link(), "Shareable Link"), + (tr::editor::export::done::copy_link(), "Copy link"), + ( + tr::editor::export::done::instant_hint(), + "Use Instant Mode for your next recording if you want a link the moment you stop.", + ), + (tr::editor::export::done::link_copied(), "Link copied"), + (tr::editor::export::done::open_file(), "Open File"), + (tr::editor::export::done::open_link(), "Open link"), + (tr::editor::export::done::retry_copy(), "Retry Copy"), + ( + tr::editor::export::done::reuploaded(), + "Your latest edit is ready at the same link", + ), + ( + tr::editor::export::done::uploaded(), + "Your Cap has been uploaded successfully", + ), + (tr::editor::export::estimate::under_megabyte(), "< 1 MB"), + (tr::editor::export::estimate::under_second(), "< 1s"), + ( + tr::editor::export::existing_link_description(), + "Reupload replaces the video at this link with your latest edit. Everyone with the link will see the updated version.", + ), + ( + tr::editor::export::existing_link_title(), + "Update your existing link", + ), + (tr::editor::export::failed(), "Export failed"), + ( + tr::editor::export::generating_preview(), + "Generating preview…", + ), + ( + tr::editor::export::link_unavailable_cursor(), + "Cursor-only exports can only be saved to a file or clipboard.", + ), + ( + tr::editor::export::link_unavailable_transparent(), + "Transparent exports can only be saved to a file or clipboard.", + ), + ( + tr::editor::export::optimize::description(), + "Re-encodes with software for much smaller files (slower)", + ), + (tr::editor::export::optimize::title(), "Optimize file size"), + (tr::editor::export::organization(), "Organization"), + ( + tr::editor::export::phase::clipboard_failed(), + "Export complete; clipboard copy failed", + ), + (tr::editor::export::phase::complete(), "Export complete"), + (tr::editor::export::phase::copied(), "Copied to clipboard"), + ( + tr::editor::export::phase::copying_to_clipboard(), + "Copying to clipboard", + ), + ( + tr::editor::export::phase::rendering_cursor(), + "Rendering cursor track", + ), + (tr::editor::export::phase::rendering_gif(), "Rendering GIF"), + ( + tr::editor::export::phase::rendering_video(), + "Rendering video", + ), + ( + tr::editor::export::phase::reupload_complete(), + "Reupload complete", + ), + ( + tr::editor::export::phase::reuploading(), + "Reuploading to your link", + ), + ( + tr::editor::export::phase::saving_to_file(), + "Saving to file", + ), + (tr::editor::export::phase::starting(), "Preparing export"), + ( + tr::editor::export::phase::upload_complete(), + "Upload complete", + ), + ( + tr::editor::export::phase::uploading(), + "Creating shareable link", + ), + (tr::editor::export::preview(), "Preview"), + ( + tr::editor::export::preview_info(), + "This is a rendered frame from your video. Adjust the settings to see the quality of the final export.", + ), + ( + tr::editor::export::preview_unavailable(), + "Preview unavailable", + ), + (tr::editor::export::quality::larger(), "Larger file"), + (tr::editor::export::quality::maximum(), "Maximum"), + (tr::editor::export::quality::potato(), "Potato"), + (tr::editor::export::quality::smaller(), "Smaller file"), + (tr::editor::export::quality::social(), "Social"), + (tr::editor::export::quality::web(), "Web"), + (tr::editor::export::resources::proceed(), "Proceed anyway"), + ( + tr::editor::export::resources::title(), + "Check export resources", + ), + (tr::editor::export::reupload(), "Reupload"), + ( + tr::editor::export::reupload_tooltip(), + "Upload your latest edit to the same link", + ), + ( + tr::editor::export::reupload_transparent(), + "Share links require a background without transparency", + ), + (tr::editor::export::section::destination(), "Destination"), + (tr::editor::export::section::format(), "Format"), + (tr::editor::export::section::frame_rate(), "Frame rate"), + (tr::editor::export::section::quality(), "Quality"), + (tr::editor::export::section::resolution(), "Resolution"), + ( + tr::editor::export::sign_in_required(), + "You need to sign in to share recordings", + ), + (tr::editor::export::stats::calculating(), "Calculating…"), + (tr::editor::export::stats::duration(), "Duration"), + ( + tr::editor::export::stats::estimated_size(), + "Estimated size", + ), + (tr::editor::export::stats::export_time(), "Export time"), + (tr::editor::export::stats::output(), "Output"), + (tr::editor::export::stats::refining_size(), "Refining size…"), + (tr::editor::export::stats::refining_time(), "Refining time…"), + (tr::editor::export::stats::unavailable(), "Unavailable"), + (tr::editor::export::task_failed(), "Export task failed"), + (tr::editor::export::title(), "Export"), + ( + tr::editor::export::upgrade_message(), + "This feature requires an upgraded plan", + ), + (tr::editor::export::upgrade_title(), "Upgrade required"), + ( + tr::editor::export::upload_failed(), + "Failed to upload recording", + ), + (tr::editor::modal::cancel_export(), "Cancel export"), + ( + tr::editor::modal::cancel_export_message(), + "Are you sure you want to cancel the export?", + ), + (tr::editor::modal::cancel_export_title(), "Cancel export?"), + (tr::editor::modal::keep_exporting(), "Keep exporting"), + ( + tr::editor::modal::recording_retained(), + "Recording retained", + ), + (tr::editor::tabs::audio::isolation_balanced(), "Balanced"), + ( + tr::editor::tabs::audio::isolation_balanced_description(), + "Clearer isolation with natural voice detail.", + ), + (tr::editor::tabs::audio::isolation_light(), "Light"), + ( + tr::editor::tabs::audio::isolation_light_description(), + "Keep more of your original voice and room sound.", + ), + (tr::editor::tabs::audio::isolation_strong(), "Strong"), + ( + tr::editor::tabs::audio::isolation_strong_description(), + "More isolation for noisy spaces. May change voice texture.", + ), + (tr::editor::tabs::audio::mic_volume(), "Microphone Volume"), + (tr::editor::tabs::audio::mute(), "Mute Audio"), + (tr::editor::tabs::audio::stereo::mono_left(), "Mono L"), + (tr::editor::tabs::audio::stereo::mono_right(), "Mono R"), + (tr::editor::tabs::audio::stereo::stereo(), "Stereo"), + ( + tr::editor::tabs::audio::stereo_mode(), + "Microphone Stereo Mode", + ), + ( + tr::editor::tabs::audio::studio_sound_description(), + "Reduces background noise and balances your voice level.", + ), + ( + tr::editor::tabs::audio::studio_sound_save_failed(), + "Could not save the Studio Sound default", + ), + ( + tr::editor::tabs::audio::system_volume(), + "System Audio Volume", + ), + (tr::editor::tabs::audio::title(), "Audio Controls"), + ( + tr::editor::tabs::audio::use_for_new(), + "Use for new recordings", + ), + ( + tr::editor::tabs::camera::advanced_shadow(), + "Advanced shadow settings", + ), + (tr::editor::tabs::camera::background(), "Background"), + (tr::editor::tabs::camera::blur::heavy(), "Heavy Blur"), + (tr::editor::tabs::camera::blur::light(), "Light Blur"), + (tr::editor::tabs::camera::blur::off(), "Off"), + ( + tr::editor::tabs::camera::blur::remove(), + "Remove Background", + ), + (tr::editor::tabs::camera::corner_style(), "Corner Style"), + ( + tr::editor::tabs::camera::custom_position(), + "Custom position (dragged on canvas)", + ), + (tr::editor::tabs::camera::hide(), "Hide Camera"), + ( + tr::editor::tabs::camera::keep_size_during_zoom(), + "Keep original size during zoom", + ), + (tr::editor::tabs::camera::mirror(), "Mirror Camera"), + (tr::editor::tabs::camera::position(), "Position"), + (tr::editor::tabs::camera::reset(), "Reset"), + ( + tr::editor::tabs::camera::rounded_corners(), + "Rounded Corners", + ), + (tr::editor::tabs::camera::shadow(), "Shadow"), + (tr::editor::tabs::camera::shadow_blur(), "Blur"), + (tr::editor::tabs::camera::shadow_opacity(), "Opacity"), + (tr::editor::tabs::camera::shape::source(), "Source"), + (tr::editor::tabs::camera::shape::square(), "Square"), + (tr::editor::tabs::camera::shape_label(), "Shape"), + ( + tr::editor::tabs::camera::size_during_zoom(), + "Size During Zoom", + ), + (tr::editor::tabs::camera::size_label(), "Size"), + (tr::editor::tabs::camera::title(), "Camera"), + ( + tr::editor::tabs::captions::active_word_highlight(), + "Active Word Highlight", + ), + (tr::editor::tabs::captions::animation::bounce(), "Bounce"), + (tr::editor::tabs::captions::animation::none(), "None"), + (tr::editor::tabs::captions::animation::pop(), "Pop"), + (tr::editor::tabs::captions::animation_section(), "Animation"), + ( + tr::editor::tabs::captions::animation_style(), + "Animation Style", + ), + ( + tr::editor::tabs::captions::background_color(), + "Background Color", + ), + ( + tr::editor::tabs::captions::background_opacity(), + "Background Opacity", + ), + ( + tr::editor::tabs::captions::background_settings(), + "Background Settings", + ), + (tr::editor::tabs::captions::beta(), "Beta"), + (tr::editor::tabs::captions::delete(), "Delete"), + (tr::editor::tabs::captions::deleting(), "Deleting..."), + ( + tr::editor::tabs::captions::download_note(), + "One time download to your system. All captions are stored locally.", + ), + (tr::editor::tabs::captions::fade_duration(), "Fade Duration"), + (tr::editor::tabs::captions::font_family(), "Font Family"), + (tr::editor::tabs::captions::font_settings(), "Font Settings"), + (tr::editor::tabs::captions::font_weight(), "Font Weight"), + (tr::editor::tabs::captions::generate(), "Generate Captions"), + (tr::editor::tabs::captions::generating(), "Generating..."), + (tr::editor::tabs::captions::highlight::color(), "Color"), + (tr::editor::tabs::captions::highlight::pill(), "Pill"), + ( + tr::editor::tabs::captions::highlight_color(), + "Highlight Color", + ), + ( + tr::editor::tabs::captions::highlight_note(), + "This is the first version of captions in Cap. Active word highlighting may be inaccurate in some situations. We're working on a fix for this and it will be released in upcoming versions.", + ), + ( + tr::editor::tabs::captions::highlight_style(), + "Highlight Style", + ), + ( + tr::editor::tabs::captions::horizontal_position(), + "Horizontal Position", + ), + ( + tr::editor::tabs::captions::intel_unavailable(), + "Parakeet caption models are unavailable on Intel Macs. Whisper models remain available.", + ), + ( + tr::editor::tabs::captions::keep_open(), + "Keep Cap open while the model downloads. Editor reloads will reconnect automatically.", + ), + (tr::editor::tabs::captions::language::ar(), "Arabic"), + (tr::editor::tabs::captions::language::auto(), "Auto Detect"), + (tr::editor::tabs::captions::language::bn(), "Bengali"), + (tr::editor::tabs::captions::language::de(), "German"), + (tr::editor::tabs::captions::language::en(), "English"), + (tr::editor::tabs::captions::language::es(), "Spanish"), + (tr::editor::tabs::captions::language::fa(), "Persian"), + (tr::editor::tabs::captions::language::fr(), "French"), + (tr::editor::tabs::captions::language::gu(), "Gujarati"), + (tr::editor::tabs::captions::language::he(), "Hebrew"), + (tr::editor::tabs::captions::language::hi(), "Hindi"), + (tr::editor::tabs::captions::language::it(), "Italian"), + (tr::editor::tabs::captions::language::ja(), "Japanese"), + (tr::editor::tabs::captions::language::ko(), "Korean"), + (tr::editor::tabs::captions::language::mr(), "Marathi"), + (tr::editor::tabs::captions::language::nl(), "Dutch"), + (tr::editor::tabs::captions::language::pa(), "Punjabi"), + (tr::editor::tabs::captions::language::pl(), "Polish"), + (tr::editor::tabs::captions::language::pt(), "Portuguese"), + (tr::editor::tabs::captions::language::ru(), "Russian"), + (tr::editor::tabs::captions::language::sk(), "Slovak"), + (tr::editor::tabs::captions::language::ta(), "Tamil"), + (tr::editor::tabs::captions::language::te(), "Telugu"), + (tr::editor::tabs::captions::language::tr(), "Turkish"), + (tr::editor::tabs::captions::language::ur(), "Urdu"), + (tr::editor::tabs::captions::language::zh(), "Chinese"), + (tr::editor::tabs::captions::language_label(), "Language"), + ( + tr::editor::tabs::captions::model::high_accuracy(), + "High Accuracy", + ), + ( + tr::editor::tabs::captions::model::high_accuracy_description(), + "Larger download, higher accuracy", + ), + (tr::editor::tabs::captions::model::medium(), "Medium"), + ( + tr::editor::tabs::captions::model::medium_description(), + "Slower, more accurate", + ), + ( + tr::editor::tabs::captions::model::recommended(), + "Recommended", + ), + ( + tr::editor::tabs::captions::model::recommended_description(), + "Best balance for most recordings", + ), + (tr::editor::tabs::captions::model::small(), "Small"), + ( + tr::editor::tabs::captions::model::small_description(), + "Smallest download", + ), + (tr::editor::tabs::captions::model_label(), "Model"), + (tr::editor::tabs::captions::position(), "Position"), + (tr::editor::tabs::captions::preset::classic(), "Classic"), + ( + tr::editor::tabs::captions::preset::classic_description(), + "Clean text on a solid rounded background.", + ), + (tr::editor::tabs::captions::preset::highlight(), "Highlight"), + ( + tr::editor::tabs::captions::preset::highlight_description(), + "Bold caps with a pill behind the active word.", + ), + (tr::editor::tabs::captions::preset::karaoke(), "Karaoke"), + ( + tr::editor::tabs::captions::preset::karaoke_description(), + "Words light up in sync with speech.", + ), + (tr::editor::tabs::captions::preset::minimal(), "Minimal"), + ( + tr::editor::tabs::captions::preset::minimal_description(), + "Subtle outlined text with no background.", + ), + (tr::editor::tabs::captions::preset::pop(), "Pop"), + ( + tr::editor::tabs::captions::preset::pop_description(), + "Playful caps that pop in with a vibrant accent.", + ), + ( + tr::editor::tabs::captions::regenerate(), + "Regenerate Captions", + ), + (tr::editor::tabs::captions::size_label(), "Size"), + (tr::editor::tabs::captions::style(), "Style"), + (tr::editor::tabs::captions::text_color(), "Text Color"), + (tr::editor::tabs::captions::title(), "Captions"), + (tr::editor::tabs::captions::uppercase(), "Uppercase"), + ( + tr::editor::tabs::captions::vertical_position(), + "Vertical Position", + ), + (tr::editor::tabs::corner::rounded(), "Rounded"), + (tr::editor::tabs::corner::squircle(), "Squircle"), + (tr::editor::tabs::cursor::friction(), "Friction"), + (tr::editor::tabs::cursor::hide_when_idle(), "Hide When Idle"), + ( + tr::editor::tabs::cursor::inactivity_delay(), + "Inactivity Delay", + ), + (tr::editor::tabs::cursor::mass(), "Mass"), + ( + tr::editor::tabs::cursor::movement_style(), + "Cursor Movement Style", + ), + (tr::editor::tabs::cursor::show(), "Show cursor"), + (tr::editor::tabs::cursor::size_label(), "Size"), + ( + tr::editor::tabs::cursor::smooth_movement(), + "Smooth Movement", + ), + (tr::editor::tabs::cursor::style::fast(), "Fast"), + ( + tr::editor::tabs::cursor::style::fast_description(), + "Snappy, close tracking", + ), + (tr::editor::tabs::cursor::style::mellow(), "Mellow"), + ( + tr::editor::tabs::cursor::style::mellow_description(), + "Balanced, natural movement", + ), + (tr::editor::tabs::cursor::style::slow(), "Slow"), + ( + tr::editor::tabs::cursor::style::slow_description(), + "Long, drifting movement", + ), + (tr::editor::tabs::cursor::style::smooth(), "Smooth"), + ( + tr::editor::tabs::cursor::style::smooth_description(), + "Soft, floaty movement", + ), + (tr::editor::tabs::cursor::style_label(), "Cursor Style"), + ( + tr::editor::tabs::cursor::svg_cursors(), + "High Quality SVG Cursors", + ), + (tr::editor::tabs::cursor::tension(), "Tension"), + (tr::editor::tabs::cursor::tilt(), "Tilt"), + (tr::editor::tabs::keyboard::animation(), "Animation"), + ( + tr::editor::tabs::keyboard::background_color(), + "Background Color", + ), + ( + tr::editor::tabs::keyboard::background_opacity(), + "Background Opacity", + ), + ( + tr::editor::tabs::keyboard::background_settings(), + "Background Settings", + ), + (tr::editor::tabs::keyboard::behavior(), "Behavior"), + (tr::editor::tabs::keyboard::beta(), "Beta"), + ( + tr::editor::tabs::keyboard::changed_during_generation(), + "The timeline or keyboard settings changed during generation. Try again.", + ), + ( + tr::editor::tabs::keyboard::empty_description(), + "Click \"Generate Keyboard Segments\" to create segments from recorded keyboard presses.", + ), + ( + tr::editor::tabs::keyboard::empty_title(), + "No keyboard segments yet.", + ), + (tr::editor::tabs::keyboard::fade_duration(), "Fade Duration"), + (tr::editor::tabs::keyboard::font_family(), "Font Family"), + (tr::editor::tabs::keyboard::font_settings(), "Font Settings"), + (tr::editor::tabs::keyboard::font_weight(), "Font Weight"), + ( + tr::editor::tabs::keyboard::generate(), + "Generate Keyboard Segments", + ), + ( + tr::editor::tabs::keyboard::generating(), + "Generating Keyboard Segments...", + ), + ( + tr::editor::tabs::keyboard::grouping_threshold(), + "Grouping Threshold", + ), + ( + tr::editor::tabs::keyboard::linger_duration(), + "Linger Duration", + ), + ( + tr::editor::tabs::keyboard::no_presses(), + "No recorded keyboard presses were found.", + ), + (tr::editor::tabs::keyboard::position(), "Position"), + ( + tr::editor::tabs::keyboard::regenerate(), + "Regenerate Keyboard Segments", + ), + ( + tr::editor::tabs::keyboard::show_modifiers(), + "Show Modifiers", + ), + ( + tr::editor::tabs::keyboard::show_special_keys(), + "Show Special Keys", + ), + (tr::editor::tabs::keyboard::size_label(), "Size"), + (tr::editor::tabs::keyboard::text_color(), "Text Color"), + ( + tr::editor::tabs::keyboard::timeline_unavailable(), + "The project timeline is unavailable.", + ), + ( + tr::editor::tabs::keyboard::timeline_unreadable(), + "The project timeline could not be read.", + ), + (tr::editor::tabs::keyboard::title(), "Show keyboard"), + (tr::editor::tabs::keyboard::uppercase(), "Uppercase"), + (tr::editor::tabs::position::bottom_center(), "Bottom Center"), + (tr::editor::tabs::position::bottom_left(), "Bottom Left"), + (tr::editor::tabs::position::bottom_right(), "Bottom Right"), + (tr::editor::tabs::position::manual(), "Manual"), + (tr::editor::tabs::position::top_center(), "Top Center"), + (tr::editor::tabs::position::top_left(), "Top Left"), + (tr::editor::tabs::position::top_right(), "Top Right"), + ( + tr::editor::tabs::sync::auto_description(), + "Cap calculated these offsets automatically to keep audio in sync with the video. Adjust them if anything still sounds off.", + ), + (tr::editor::tabs::sync::auto_synced(), "Auto-synced"), + (tr::editor::tabs::sync::camera(), "Camera Offset"), + ( + tr::editor::tabs::sync::description(), + "Fine-tune source offsets if audio or camera drifts out of sync with the screen recording.", + ), + (tr::editor::tabs::sync::microphone(), "Microphone Offset"), + ( + tr::editor::tabs::sync::system_audio(), + "System Audio Offset", + ), + (tr::editor::tabs::sync::title(), "Sync"), + (tr::editor::tabs::weight::bold(), "Bold"), + (tr::editor::tabs::weight::medium(), "Medium"), + (tr::editor::tabs::weight::normal(), "Normal"), + (tr::editor::timeline::add_shot(), "Add shot"), + (tr::editor::timeline::empty::add_audio(), "Add audio"), + (tr::editor::timeline::empty::add_scene(), "Add scene"), + ( + tr::editor::timeline::empty::audio(), + "Add background music or import your own audio", + ), + (tr::editor::timeline::empty::captions(), "No captions"), + ( + tr::editor::timeline::empty::keyboard(), + "No keyboard events", + ), + ( + tr::editor::timeline::empty::scene(), + "Switch layouts between your screen and camera", + ), + (tr::editor::timeline::paused(), "Paused"), + ( + tr::editor::timeline::picker::audio(), + "Add background music or import your own audio.", + ), + ( + tr::editor::timeline::picker::caption(), + "Auto-transcribe your recording into on-screen subtitles.", + ), + ( + tr::editor::timeline::picker::clip(), + "Your recorded screen footage.", + ), + ( + tr::editor::timeline::picker::image(), + "Add an image to your recording.", + ), + ( + tr::editor::timeline::picker::keyboard(), + "Display key presses on screen as you type.", + ), + ( + tr::editor::timeline::picker::mask(), + "Blur or black out private areas of the screen.", + ), + ( + tr::editor::timeline::picker::scene(), + "Switch layouts between your screen and camera.", + ), + ( + tr::editor::timeline::picker::scene_unavailable(), + "Record with a camera to use scenes.", + ), + ( + tr::editor::timeline::picker::style(), + "Change background, camera and cursor settings over time.", + ), + ( + tr::editor::timeline::picker::text_overlay(), + "Add custom text overlays and titles to the canvas.", + ), + ( + tr::editor::timeline::picker::three_d(), + "Tilt the scene in 3D perspective.", + ), + ( + tr::editor::timeline::picker::zoom(), + "Smooth zoom-ins that follow the action.", + ), + ( + tr::editor::timeline::remove::audio(), + "Remove the Audio track.", + ), + ( + tr::editor::timeline::remove::captions(), + "Remove the Captions track.", + ), + ( + tr::editor::timeline::remove::clip(), + "Remove the Clip track.", + ), + ( + tr::editor::timeline::remove::image(), + "Remove the Image track.", + ), + ( + tr::editor::timeline::remove::keyboard(), + "Remove the Keyboard track.", + ), + ( + tr::editor::timeline::remove::mask(), + "Remove the Mask track.", + ), + ( + tr::editor::timeline::remove::scene(), + "Remove the Scene track.", + ), + ( + tr::editor::timeline::remove::style(), + "Remove the Style track.", + ), + ( + tr::editor::timeline::remove::text_track(), + "Remove the Text track.", + ), + ( + tr::editor::timeline::remove::three_d(), + "Remove the 3D track.", + ), + ( + tr::editor::timeline::remove::zoom(), + "Remove the Zoom track.", + ), + (tr::editor::timeline::scene::camera_only(), "Camera Only"), + (tr::editor::timeline::scene::default(), "Default"), + (tr::editor::timeline::scene::floating(), "Floating"), + (tr::editor::timeline::scene::hide_camera(), "Hide Camera"), + (tr::editor::timeline::scene::split_screen(), "Split Screen"), + (tr::editor::timeline::segment::audio(), "Audio"), + ( + tr::editor::timeline::segment::automatic_zoom(), + "Automatic Zoom", + ), + (tr::editor::timeline::segment::caption(), "Caption"), + (tr::editor::timeline::segment::clip(), "Clip"), + ( + tr::editor::timeline::segment::cursor_hidden(), + "Cursor hidden", + ), + (tr::editor::timeline::segment::highlight(), "Highlight"), + (tr::editor::timeline::segment::label(), "Label"), + (tr::editor::timeline::segment::manual_zoom(), "Manual Zoom"), + (tr::editor::timeline::segment::muted(), "Muted"), + (tr::editor::timeline::segment::sensitive(), "Sensitive"), + (tr::editor::timeline::track::audio(), "Audio"), + (tr::editor::timeline::track::captions(), "Captions"), + (tr::editor::timeline::track::clip(), "Clip"), + (tr::editor::timeline::track::image(), "Image"), + (tr::editor::timeline::track::keyboard(), "Keyboard"), + (tr::editor::timeline::track::mask(), "Mask"), + (tr::editor::timeline::track::scene(), "Scene"), + (tr::editor::timeline::track::style(), "Style"), + (tr::editor::timeline::track::text_track(), "Text"), + (tr::editor::timeline::track::video(), "Video"), + (tr::editor::timeline::track::zoom(), "Zoom"), + (tr::editor::window::add_track::button(), "Add track"), + ( + tr::editor::window::add_track::hide_3d(), + "Hide the 3D track.", + ), + ( + tr::editor::window::add_track::hint(), + "Choose a track to add to your timeline.", + ), + (tr::editor::window::aspect::auto(), "Auto"), + (tr::editor::window::aspect::classic(), "Classic"), + (tr::editor::window::aspect::classic_ratio(), "Classic ⋅4:3"), + (tr::editor::window::aspect::square(), "Square"), + (tr::editor::window::aspect::square_ratio(), "Square ⋅1:1"), + (tr::editor::window::aspect::tall(), "Tall"), + (tr::editor::window::aspect::tall_ratio(), "Tall ⋅3:4"), + (tr::editor::window::aspect::tooltip(), "Aspect Ratio"), + (tr::editor::window::aspect::vertical(), "Vertical"), + ( + tr::editor::window::aspect::vertical_ratio(), + "Vertical ⋅9:16", + ), + (tr::editor::window::aspect::wide(), "Wide"), + (tr::editor::window::aspect::wide_ratio(), "Wide ⋅16:9"), + (tr::editor::window::audio_filter(), "Audio"), + ( + tr::editor::window::audio_import::no_extension(), + "Audio file has no extension", + ), + ( + tr::editor::window::audio_segment_changed(), + "This audio segment changed. Select it and choose a replacement again.", + ), + ( + tr::editor::window::busy::clip_import(), + "Wait for the clip import to finish before closing or deleting this recording.", + ), + ( + tr::editor::window::busy::export(), + "Wait for the export or its cancellation to finish before closing or deleting this recording.", + ), + ( + tr::editor::window::busy::opening(), + "Wait for the recording to finish opening before deleting it.", + ), + (tr::editor::window::camera3d_lane::add_shot(), "+ Add shot"), + ( + tr::editor::window::camera3d_lane::auto_scene(), + "Auto scene", + ), + ( + tr::editor::window::camera3d_lane::how_many(), + "How many shots?", + ), + ( + tr::editor::window::camera3d_lane::prompt(), + "Add cinematic 3D shots to your recording", + ), + (tr::editor::window::clip_menu::audio(), "AUDIO"), + (tr::editor::window::clip_menu::cursor(), "CURSOR"), + (tr::editor::window::clip_menu::delete(), "Delete clip"), + ( + tr::editor::window::clip_menu::delete_only_clip(), + "Can't delete the only clip", + ), + (tr::editor::window::clip_menu::hide_cursor(), "Hide cursor"), + ( + tr::editor::window::clip_menu::hide_cursor_description(), + "Fades out while this clip plays", + ), + (tr::editor::window::clip_menu::keep_pitch(), "Keep pitch"), + (tr::editor::window::clip_menu::match_speed(), "Match speed"), + ( + tr::editor::window::clip_menu::merge_next(), + "Merge with next clip", + ), + ( + tr::editor::window::clip_menu::merge_previous(), + "Merge with previous clip", + ), + (tr::editor::window::clip_menu::mute(), "Mute"), + (tr::editor::window::clip_menu::mute_clip(), "Mute clip"), + ( + tr::editor::window::clip_menu::mute_clip_description(), + "Silence this clip's audio", + ), + (tr::editor::window::clip_menu::rename(), "Rename clip"), + (tr::editor::window::clip_menu::speed(), "SPEED"), + (tr::editor::window::clip_menu::split(), "Split at playhead"), + ( + tr::editor::window::clip_menu::split_hint(), + "Move the playhead into this clip", + ), + (tr::editor::window::clip_menu::volume(), "Volume"), + (tr::editor::window::crop(), "Crop"), + (tr::editor::window::crop_tooltip(), "Crop Video"), + (tr::editor::window::frame::add_tooltip(), "Add a frame"), + (tr::editor::window::frame::browser(), "Browser"), + ( + tr::editor::window::frame::browser_description(), + "Browser chrome with address bar", + ), + (tr::editor::window::frame::button(), "Frame"), + (tr::editor::window::frame::dark(), "Dark"), + ( + tr::editor::window::frame::global(), + "Global frame · Applies wherever Style inherits background.", + ), + (tr::editor::window::frame::heading(), "Frame"), + (tr::editor::window::frame::light(), "Light"), + ( + tr::editor::window::frame::macbook_description(), + "Laptop bezel around the recording", + ), + ( + tr::editor::window::frame::macos_description(), + "Window chrome with traffic lights", + ), + (tr::editor::window::frame::none(), "None"), + ( + tr::editor::window::frame::none_description(), + "Show the recording as-is", + ), + ( + tr::editor::window::frame::style_changed(), + "This Style changed. Close and reopen Frame.", + ), + (tr::editor::window::frame::theme(), "Theme"), + (tr::editor::window::frame::title(), "Title"), + (tr::editor::window::frame::url(), "URL"), + ( + tr::editor::window::frame::window_title_placeholder(), + "Window title", + ), + ( + tr::editor::window::frame::windows_description(), + "Title bar with window controls", + ), + (tr::editor::window::header::captions(), "Captions"), + ( + tr::editor::window::header::delete_recording(), + "Delete recording", + ), + (tr::editor::window::header::export(), "Export"), + ( + tr::editor::window::header::open_bundle(), + "Open recording bundle", + ), + (tr::editor::window::header::presets(), "Presets"), + ( + tr::editor::window::image::changed(), + "This image changed while choosing a replacement. Select it and try again.", + ), + (tr::editor::window::image::filter(), "Images"), + ( + tr::editor::window::image::recording_unavailable(), + "The recording is no longer available for this image. Please reopen the project.", + ), + ( + tr::editor::window::no_room_for_3d_shot(), + "No room for another 3D shot", + ), + ( + tr::editor::window::open::damaged(), + "This recording's video tracks could not be opened. The bundle looks damaged.", + ), + ( + tr::editor::window::open::failed(), + "This recording could not be opened", + ), + ( + tr::editor::window::open::no_segments(), + "Recording has no segments. It may need to be recovered first.", + ), + ( + tr::editor::window::open::not_studio(), + "Cannot edit non-studio recordings", + ), + (tr::editor::window::presets::apply(), "Apply"), + (tr::editor::window::presets::create(), "Create"), + ( + tr::editor::window::presets::create_new(), + "Create new preset", + ), + (tr::editor::window::presets::create_title(), "Create Preset"), + (tr::editor::window::presets::default(), "Default"), + (tr::editor::window::presets::default_badge(), "Default"), + (tr::editor::window::presets::delete(), "Delete"), + (tr::editor::window::presets::delete_title(), "Delete Preset"), + ( + tr::editor::window::presets::name_placeholder(), + "Preset name", + ), + ( + tr::editor::window::presets::none_saved(), + "No saved presets", + ), + (tr::editor::window::presets::rename(), "Rename"), + (tr::editor::window::presets::rename_title(), "Rename Preset"), + ( + tr::editor::window::presets::save_settings(), + "Save settings to preset", + ), + (tr::editor::window::presets::set_default(), "Set as default"), + (tr::editor::window::preview::full(), "Full"), + (tr::editor::window::preview::half(), "Half"), + (tr::editor::window::preview::label(), "Preview"), + (tr::editor::window::preview::quarter(), "Quarter"), + (tr::editor::window::redo(), "Redo"), + (tr::editor::window::text_menu::copy_style(), "Copy style"), + ( + tr::editor::window::text_menu::copy_style_first(), + "Copy a style first", + ), + ( + tr::editor::window::text_menu::delete_segment(), + "Delete text segment", + ), + ( + tr::editor::window::text_menu::delete_segments(), + "Delete text segments", + ), + (tr::editor::window::text_menu::paste_style(), "Paste style"), + ( + tr::editor::window::text_menu::paste_style_all(), + "Paste style to all selected", + ), + (tr::editor::window::text_menu::untitled(), "Text"), + (tr::editor::window::title::not_saved(), "Title not saved"), + (tr::editor::window::track::clear_all(), "Clear all"), + (tr::editor::window::track::delete(), "Delete"), + ( + tr::editor::window::transport::done_splitting(), + "Done splitting", + ), + ( + tr::editor::window::transport::play_pause(), + "Play/Pause video", + ), + (tr::editor::window::transport::split(), "Split"), + ( + tr::editor::window::transport::split_tooltip(), + "Click a segment to split. Esc to finish.", + ), + (tr::editor::window::transport::zoom_in(), "Zoom in"), + (tr::editor::window::transport::zoom_out(), "Zoom out"), + (tr::editor::window::undo(), "Undo"), + (tr::editor::window::zoom_lane::generate(), "Generate"), + (tr::editor::window::zoom_lane::generating(), "Generating…"), + ( + tr::editor::window::zoom_lane::no_clicks(), + "No clicks found to zoom into. Drag across the lane to add one.", + ), + ( + tr::editor::window::zoom_lane::prompt(), + "Add zooms automatically", + ), + (tr::ui::coming_soon(), "Coming Soon"), + (tr::ui::selection::delete(), "Delete"), + (tr::ui::selection::done(), "Done"), + (tr::ui::selection::select_all(), "Select all"), + ]; + for (translated, literal) in pairs { + assert_eq!(translated, literal); + } + let plain_keys = KEYS + .iter() + .filter(|key| is_editor_shell_surface_key(key)) + .count() + - FORMATTED.len(); + assert_eq!( + pairs.len(), + plain_keys, + "every plain key has a literal above" + ); + for key in FORMATTED { + assert!(KEYS.contains(key), "{key}"); + } +} + +#[test] +fn derived_track_copy_matches_the_sentences_it_replaced() { + use crate::editor_timeline::TrackKind; + for kind in [ + TrackKind::Clip, + TrackKind::Style, + TrackKind::Image, + TrackKind::Caption, + TrackKind::Keyboard, + TrackKind::Text, + TrackKind::Mask, + TrackKind::Audio, + TrackKind::Zoom, + TrackKind::ThreeD, + TrackKind::Scene, + ] { + assert_eq!( + kind.picker_remove(), + format!("Remove the {} track.", kind.picker_label()) + ); + } + assert_eq!( + tr::editor::timeline::empty::audio(), + TrackKind::Audio.picker_description().trim_end_matches('.') + ); + assert_eq!( + tr::editor::timeline::empty::scene(), + TrackKind::Scene.picker_description().trim_end_matches('.') + ); + assert_eq!(tr::editor::window::preview::quarter(), "Quarter"); + assert_eq!(tr::editor::window::preview::half(), "Half"); + assert_eq!(tr::editor::window::preview::full(), "Full"); +} + +#[test] +fn editor_shell_messages_format_like_the_code_they_replaced() { + let error = "disk full".to_string(); + let path = std::path::Path::new("/tmp/Demo.cap"); + let message = "Source Cap project is still recording".to_string(); + + assert_eq!( + tr::editor::clips::append_error(&message), + format!("Failed to add clip: {message}") + ); + assert_eq!( + tr::editor::clips::import_error(&message), + format!("Failed to import clip: {message}") + ); + for index in [0_usize, 1, 2, 5] { + assert_eq!( + tr::editor::clips::clip(index + 1), + format!("Clip {}", index + 1) + ); + assert_eq!(tr::editor::clips::split(index), format!("Split {}", index)); + assert_eq!(tr::editor::tabs::sync::clip(index), format!("Clip {index}")); + assert_eq!( + tr::editor::timeline::segment::numbered_clip(index as u32), + format!("Clip {}", index as u32) + ); + assert_eq!( + tr::editor::window::frame::style_only(index + 1), + format!("Style {} only · Global settings stay unchanged.", index + 1) + ); + assert_eq!( + tr::editor::window::frame::style_only_override(index + 1), + format!( + "Style {} only · Editing enables its background override.", + index + 1 + ) + ); + } + assert_eq!( + tr::editor::clips::error::cannot_decode(path.display(), &error), + format!("Cannot decode video {}: {error}", path.display()) + ); + assert_eq!( + tr::editor::clips::error::in_use(&error), + format!("Cannot import while this recording is in use: {error}") + ); + assert_eq!( + tr::editor::clips::error::missing_video(path.display()), + format!("Missing video file: {}", path.display()) + ); + assert_eq!( + tr::editor::clips::error::source_failed(&error), + format!("Source Cap project failed: {error}") + ); + assert_eq!( + tr::editor::clips::error::unsupported_video(path.display()), + format!("Unsupported video file: {}", path.display()) + ); + assert_eq!( + tr::editor::clips::prepare_controls_failed(&error), + format!("Could not prepare the recording controls: {error}") + ); + assert_eq!( + tr::editor::clips::save_before_recording_failed(&error), + format!("Could not save the current project before recording: {error}") + ); + + for (lower, upper) in [(1.5_f64, 2.0_f64), (12.0, 41.0), (0.1, 3.4)] { + for (single, range, unit) in [ + ( + tr::editor::export::estimate::gigabytes(lower), + tr::editor::export::estimate::gigabytes_range(lower, upper), + "GB", + ), + ( + tr::editor::export::estimate::megabytes(lower), + tr::editor::export::estimate::megabytes_range(lower, upper), + "MB", + ), + ( + tr::editor::export::estimate::hours(lower), + tr::editor::export::estimate::hours_range(lower, upper), + "hr", + ), + ( + tr::editor::export::estimate::minutes(lower), + tr::editor::export::estimate::minutes_range(lower, upper), + "min", + ), + ( + tr::editor::export::estimate::seconds(lower), + tr::editor::export::estimate::seconds_range(lower, upper), + "s", + ), + ] { + assert_eq!(single, format!("~{lower} {unit}")); + assert_eq!(range, format!("~{lower}–{upper} {unit}")); + } + } + for fps in [24_u32, 30, 60] { + assert_eq!(tr::editor::export::fps(&fps), format!("{fps} FPS")); + assert_eq!( + tr::editor::export::stats::output_value(1920_u32, 1080_u32, fps), + format!("{}×{} · {} fps", 1920, 1080, fps) + ); + } + for total in [0_u32, 1, 2, 5] { + let rendered = total / 2; + let expected = if total == 1 { + format!("{rendered} / {total} frame") + } else { + format!("{rendered} / {total} frames") + }; + assert_eq!( + tr::editor::export::frames(total as usize, rendered), + expected + ); + } + + let model = "Recommended"; + assert_eq!( + tr::editor::tabs::captions::download(model), + format!("Download {} Model", model) + ); + for percent in [0_u32, 1, 42, 100] { + assert_eq!( + tr::editor::tabs::captions::downloading(model, percent), + format!("Downloading {model}... {percent}%") + ); + } + assert_eq!( + tr::editor::tabs::captions::model_downloaded(model), + format!("{} model downloaded", model) + ); + let reason = "No audio found in the video file".to_string(); + assert_eq!( + tr::editor::tabs::captions::generate_failed(&reason), + format!("Failed to generate captions: {}", reason) + ); + assert_eq!( + tr::editor::tabs::keyboard::load_failed(&error), + format!("Failed to load recording data: {error}") + ); + + let look = "Glide across"; + for duration in [0.5_f64, 2.0, 3.25] { + assert_eq!( + tr::editor::timeline::add_shot_with_look(look, format!("{duration:.1}")), + format!("Add shot \u{b7} {} \u{b7} {duration:.1}s", look) + ); + } + + assert_eq!( + tr::editor::window::audio_import::copy_failed(&error), + format!("Failed to copy audio file: {error}") + ); + assert_eq!( + tr::editor::window::audio_import::directory_failed(&error), + format!("Failed to create audio directory: {error}") + ); + assert_eq!( + tr::editor::window::audio_import::not_found(path.display()), + format!("Audio file not found: {}", path.display()) + ); + let extension = "ogg".to_string(); + assert_eq!( + tr::editor::window::audio_import::unsupported(&extension), + format!("Unsupported audio format: .{extension}") + ); + assert_eq!( + tr::editor::window::busy::save_before_delete_failed(&error), + format!("Could not save the recording before deleting it: {error}") + ); + assert_eq!( + tr::editor::window::open::media_failed(&error), + format!("Failed to read this recording's media: {error}") + ); + assert_eq!( + tr::editor::window::open::meta_failed(&error), + format!("Failed to load recording meta: {error}") + ); + assert_eq!( + tr::editor::window::open::not_found(path.display()), + format!("Video path {} not found!", path.display()) + ); + let name = "Webinar".to_string(); + assert_eq!( + tr::editor::window::presets::delete_confirm(&name), + format!("Are you sure you want to delete \"{}\"?", name) + ); + assert_eq!( + tr::editor::window::save_failed(path.display(), &error), + format!( + "Could not save {}. Keep the editor open and try again: {error}", + path.display() + ) + ); + for count in [2_usize, 3, 5] { + assert_eq!( + tr::editor::window::text_menu::segments(count), + format!("{count} text segments") + ); + } + assert_eq!( + tr::editor::window::title::not_saved_message(&error), + format!("{error}\n\nYour title is still in the editor. Try saving it again.") + ); + assert_eq!( + tr::editor::window::title::read_failed(path.display(), &error), + format!( + "Could not read the recording title for {}: {error}", + path.display() + ) + ); + assert_eq!( + tr::editor::window::title::save_failed(path.display(), &error), + format!( + "Could not save the recording title for {}: {error}", + path.display() + ) + ); +} + +#[test] +fn selection_headers_read_as_whole_sentences() { + let old = |noun: &str, count: usize| { + format!( + "{count} {noun} segment{} selected", + if count == 1 { "" } else { "s" } + ) + }; + for count in [0, 1, 2, 5] { + for noun in [ + "style", "image", "text", "caption", "audio", "mask", "keyboard", "3D", "scene", + ] { + assert_eq!(crate::ui::selection_label(noun, count), old(noun, count)); + } + assert_eq!( + tr::ui::selection::segments(count), + if count == 1 { + format!("{count} segment selected") + } else { + format!("{count} segments selected") + } + ); + } + let old_zoom = |count: usize, total: usize| { + if total > 1 && count == total { + format!("All {total} selected") + } else if total > 1 { + format!("{count} of {total} selected") + } else { + format!("{count} selected") + } + }; + for (count, total) in [(0, 0), (1, 1), (1, 2), (2, 2), (2, 5), (5, 5)] { + assert_eq!( + crate::ui::zoom_selection_label(count, total), + old_zoom(count, total) + ); + } +} diff --git a/apps/desktop-gpui/src/i18n/mod.rs b/apps/desktop-gpui/src/i18n/mod.rs index d7d487d1a50..1ee4e8b6946 100644 --- a/apps/desktop-gpui/src/i18n/mod.rs +++ b/apps/desktop-gpui/src/i18n/mod.rs @@ -158,6 +158,8 @@ pub fn apply(preference: Preference, cx: &mut App) { cx.refresh_windows(); } +#[cfg(test)] +mod editor_shell_surface_tests; #[cfg(test)] mod surface_tests; #[cfg(test)] diff --git a/apps/desktop-gpui/src/i18n/tests.rs b/apps/desktop-gpui/src/i18n/tests.rs index c3b43e28840..654d46b31c1 100644 --- a/apps/desktop-gpui/src/i18n/tests.rs +++ b/apps/desktop-gpui/src/i18n/tests.rs @@ -440,6 +440,7 @@ fn english_matches_the_literals_it_replaced() { .filter(|key| { !key.starts_with(NEW_IN_THE_LANGUAGE_PICKER) && !super::surface_tests::is_main_surface_key(key) + && !super::editor_shell_surface_tests::is_editor_shell_surface_key(key) }) .count() - 3; From 92f1aac2e761cafa8afea0b4b55c57c1933e3aa8 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:02:29 +0000 Subject: [PATCH 10/25] docs(desktop-gpui): add caption, track and preset to the translation glossary --- apps/desktop-gpui/locales/GLOSSARY.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/desktop-gpui/locales/GLOSSARY.md b/apps/desktop-gpui/locales/GLOSSARY.md index 8715a29e252..f28176635b1 100644 --- a/apps/desktop-gpui/locales/GLOSSARY.md +++ b/apps/desktop-gpui/locales/GLOSSARY.md @@ -79,6 +79,9 @@ recording engine, which stays in English. | segment (of a recording) | 分段 | セグメント | segment | segmento | Segment | सेगमेंट | | recover / discard | 恢复 / 丢弃 | 復元 / 破棄 | récupérer / supprimer | recuperar / descartar | wiederherstellen / verwerfen | रिकवर करें / हटाएँ | | Personal / Commercial plan | 个人版 / 商业版 | 個人 / 商用 | Personnel / Commercial | Personal / Comercial | Privat / Kommerziell | पर्सनल / कमर्शियल | +| caption (subtitle) | 字幕 | 字幕 | sous-titre | subtítulo | Untertitel | कैप्शन | +| track (timeline) | 轨道 | トラック | piste | pista | Spur | ट्रैक | +| preset | 预设 | プリセット | préréglage | preajuste | Vorlage | प्रीसेट | ## Fitting the UI From 423f2a315a354e348dc4ee4311b621d160b8a5e5 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:16:20 +0000 Subject: [PATCH 11/25] fix(desktop-gpui): show localized mode names on the mode selection cards --- apps/desktop-gpui/src/main_window.rs | 2 +- apps/desktop-gpui/src/mode_select_window.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 9a4e77a3ee2..b26bbebc618 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -169,7 +169,7 @@ impl Mode { /// [`Self::panel_title`] in the UI language. `panel_title` stays English: /// it names element ids and log fields. - fn display_name(self) -> &'static str { + pub(crate) fn display_name(self) -> &'static str { match self { Self::Instant => tr::main::mode::name::instant(), Self::Studio => tr::main::mode::name::studio(), diff --git a/apps/desktop-gpui/src/mode_select_window.rs b/apps/desktop-gpui/src/mode_select_window.rs index 122c7e6df7f..70e28e36949 100644 --- a/apps/desktop-gpui/src/mode_select_window.rs +++ b/apps/desktop-gpui/src/mode_select_window.rs @@ -112,7 +112,7 @@ impl ModeSelectWindow { .id(SharedString::from(mode.panel_title())) .when(self.keyboard_nav, keyboard_nav::stop) .role(Role::Button) - .aria_label(mode.panel_title()) + .aria_label(mode.display_name()) .aria_description(mode.card_description()) .aria_selected(selected) .relative() @@ -228,7 +228,7 @@ impl ModeSelectWindow { } else { Hsla::from(theme.gray_12) }) - .child(mode.panel_title()), + .child(mode.display_name()), ) .child( // `text-xs leading-relaxed text-gray-11 line-clamp-3` From a49aae39c4ce83b785c97e2200769d4c7af1198f Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:18:06 +0000 Subject: [PATCH 12/25] refactor(desktop-gpui): drop comments that restate the new label helpers --- apps/desktop-gpui/src/editor_panels.rs | 1 - apps/desktop-gpui/src/editor_sidebar.rs | 2 -- apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs | 5 +---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/desktop-gpui/src/editor_panels.rs b/apps/desktop-gpui/src/editor_panels.rs index e64dc294d2d..60b6c5c9d61 100644 --- a/apps/desktop-gpui/src/editor_panels.rs +++ b/apps/desktop-gpui/src/editor_panels.rs @@ -186,7 +186,6 @@ pub struct TextPresetStyle { /// placement, and it is the one field that moves the box. pub struct TextPreset { pub id: &'static str, - /// Which [`TEXT_PRESET_GROUPS`] chip shows it, by that chip's id. pub group: &'static str, pub name: fn() -> &'static str, pub sample: fn() -> &'static str, diff --git a/apps/desktop-gpui/src/editor_sidebar.rs b/apps/desktop-gpui/src/editor_sidebar.rs index 4519c854d0f..443ce9226d2 100644 --- a/apps/desktop-gpui/src/editor_sidebar.rs +++ b/apps/desktop-gpui/src/editor_sidebar.rs @@ -139,7 +139,6 @@ pub const BACKGROUND_THEMES: [(&str, &str); 6] = [ ("orange", "Orange"), ]; -/// A [`BACKGROUND_THEMES`] tab's label in the current language. pub fn background_theme_label(id: &str) -> &'static str { match id { "dark" => tr::editor::sidebar::wallpaper_theme::dark(), @@ -279,7 +278,6 @@ impl SidebarTab { } } - /// What the rail's tooltip calls the tab, in the current language. pub fn title(self) -> &'static str { match self { Self::Background => tr::editor::sidebar::tab::background(), diff --git a/apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs b/apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs index 83c15a10b2c..5f6662ddd9b 100644 --- a/apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs +++ b/apps/desktop-gpui/src/editor_sidebar/animated_gradient.rs @@ -107,8 +107,7 @@ impl AnimatedGradientState { } } -/// A control's label in the current language. The catalog's own label is the -/// English one the Tauri app shares. +/// The catalog's own labels are the English ones the Tauri app shares. fn control_label(control: &AnimatedGradientControl) -> SharedString { SharedString::new_static(match control.key { AnimatedGradientParameter::Direction => { @@ -159,8 +158,6 @@ fn control_label(control: &AnimatedGradientControl) -> SharedString { }) } -/// A control group's tab label in the current language, by the catalog's -/// English group name. fn group_label(name: &str) -> SharedString { match name { "Flow" => SharedString::new_static(tr::editor::sidebar::animated_gradient::group::flow()), From ab36da51b63b37701c52cadcf9f2daa4d6def913 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 22:20:32 +0000 Subject: [PATCH 13/25] fix(desktop-gpui): translate the look name in the 3D shot ghost --- apps/desktop-gpui/src/editor_panels.rs | 3 +-- apps/desktop-gpui/src/editor_timeline.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/desktop-gpui/src/editor_panels.rs b/apps/desktop-gpui/src/editor_panels.rs index 60b6c5c9d61..098fafba179 100644 --- a/apps/desktop-gpui/src/editor_panels.rs +++ b/apps/desktop-gpui/src/editor_panels.rs @@ -2097,9 +2097,8 @@ pub fn apply_camera3d_shots_to_range( /// `DEFAULT_SHOT_DURATION`: the length a new 3D shot opens at. pub const CAMERA3D_DEFAULT_SHOT_DURATION: f64 = 4.; -/// The move a brand-new shot is born with, and what the ghost calls it. +/// The move a brand-new shot is born with. pub const CAMERA3D_DEFAULT_LOOK: &str = "glide-across"; -pub const CAMERA3D_DEFAULT_LOOK_NAME: &str = "Glide across"; /// Where a new shot lands, given the shots already on the track. /// diff --git a/apps/desktop-gpui/src/editor_timeline.rs b/apps/desktop-gpui/src/editor_timeline.rs index 8bd821d4e19..45b1f9f172d 100644 --- a/apps/desktop-gpui/src/editor_timeline.rs +++ b/apps/desktop-gpui/src/editor_timeline.rs @@ -2501,7 +2501,7 @@ pub fn new_camera3d_ghost(model: &TimelineModel, preview: f64) -> Option<(f64, f fn camera3d_ghost_label(width: f32, duration: f64) -> Option { if width >= 208. { Some(tr::editor::timeline::add_shot_with_look( - crate::editor_panels::CAMERA3D_DEFAULT_LOOK_NAME, + tr::editor::panel::camera3d::look::glide_across(), format!("{duration:.1}"), )) } else if width >= 84. { From 0e20783f25563dabb6b873371ebd0f24496e1a2a Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:42:54 +0000 Subject: [PATCH 14/25] feat(desktop-gpui): show the language picker in release builds and follow the system language --- apps/desktop-gpui/src/i18n/mod.rs | 25 +++--------------------- apps/desktop-gpui/src/i18n/tests.rs | 21 +++----------------- apps/desktop-gpui/src/settings_window.rs | 4 +--- 3 files changed, 7 insertions(+), 43 deletions(-) diff --git a/apps/desktop-gpui/src/i18n/mod.rs b/apps/desktop-gpui/src/i18n/mod.rs index 447afcba3e4..85873876e30 100644 --- a/apps/desktop-gpui/src/i18n/mod.rs +++ b/apps/desktop-gpui/src/i18n/mod.rs @@ -12,10 +12,6 @@ pub const SETTING_KEY: &str = "uiLanguage"; /// `CAP_GPUI_LOCALE=ja` (or `pseudo` in a debug build). pub const LOCALE_ENV: &str = "CAP_GPUI_LOCALE"; const SYSTEM_SETTING: &str = "system"; -/// Shows the language picker in a release build. Release builds hide it until -/// the main surfaces are translated; `uiLanguage` and [`LOCALE_ENV`] work -/// either way. -pub const PICKER_ENV: &str = "CAP_GPUI_LANGUAGE_PICKER"; static CURRENT: AtomicU8 = AtomicU8::new(Locale::En as u8); @@ -57,14 +53,6 @@ impl Preference { } } -/// Read once: the General page asks on every render. -pub fn picker_enabled() -> bool { - static ENABLED: OnceLock = OnceLock::new(); - *ENABLED.get_or_init(|| { - cfg!(debug_assertions) || std::env::var(PICKER_ENV).is_ok_and(|value| value == "1") - }) -} - /// Before the first lookup: `main` calls this ahead of the theme, menus and /// tray. pub fn init(setting: Option<&str>) { @@ -79,20 +67,13 @@ pub fn resolve(preference: Preference) -> Locale { { return locale; } - resolve_preference(preference, picker_enabled(), system) + resolve_preference(preference, system) } -/// Without the picker a release build stays English on a non-English system, -/// so nobody gets a half-translated app they can't switch back from. -fn resolve_preference( - preference: Preference, - picker_enabled: bool, - system: impl FnOnce() -> Locale, -) -> Locale { +fn resolve_preference(preference: Preference, system: impl FnOnce() -> Locale) -> Locale { match preference { Preference::Fixed(locale) => locale, - Preference::System if picker_enabled => system(), - Preference::System => Locale::En, + Preference::System => system(), } } diff --git a/apps/desktop-gpui/src/i18n/tests.rs b/apps/desktop-gpui/src/i18n/tests.rs index 859c5314a6b..24e98f959c5 100644 --- a/apps/desktop-gpui/src/i18n/tests.rs +++ b/apps/desktop-gpui/src/i18n/tests.rs @@ -573,13 +573,6 @@ fn language_picker_english() { ); } -#[test] -fn the_picker_shows_in_debug_builds() { - if cfg!(debug_assertions) { - assert!(picker_enabled()); - } -} - #[test] fn system_tags_resolve_to_catalogs() { for (tag, expected) in [ @@ -619,21 +612,13 @@ fn traditional_chinese_falls_through_to_the_next_language() { } #[test] -fn system_language_waits_for_the_picker() { +fn system_default_follows_the_system_language_and_a_choice_overrides_it() { assert_eq!( - resolve_preference(Preference::System, false, || Locale::Ja), - Locale::En - ); - assert_eq!( - resolve_preference(Preference::System, true, || Locale::Ja), + resolve_preference(Preference::System, || Locale::Ja), Locale::Ja ); assert_eq!( - resolve_preference(Preference::Fixed(Locale::De), false, || Locale::Ja), - Locale::De - ); - assert_eq!( - resolve_preference(Preference::Fixed(Locale::De), true, || Locale::Ja), + resolve_preference(Preference::Fixed(Locale::De), || Locale::Ja), Locale::De ); } diff --git a/apps/desktop-gpui/src/settings_window.rs b/apps/desktop-gpui/src/settings_window.rs index 74ef683bf07..4dbf67548ba 100644 --- a/apps/desktop-gpui/src/settings_window.rs +++ b/apps/desktop-gpui/src/settings_window.rs @@ -3214,9 +3214,7 @@ impl SettingsWindow { fn render_general(&self, window: &Window, cx: &mut Context) -> Vec { let mut sections = Vec::with_capacity(10); sections.push(self.render_appearance(cx).into_any_element()); - if i18n::picker_enabled() { - sections.push(self.render_language(cx).into_any_element()); - } + sections.push(self.render_language(cx).into_any_element()); sections.extend([ self.render_app_section(cx).into_any_element(), self.render_recording(cx).into_any_element(), From 3b6ced64fce6feebd9283ef785d3446b216ba79a Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:42:54 +0000 Subject: [PATCH 15/25] feat(desktop-gpui): declare the app's languages to macOS and localize the permission prompts --- apps/desktop-gpui/Info.plist | 12 ++++++++++++ apps/desktop-gpui/macos/de.lproj/InfoPlist.strings | 3 +++ apps/desktop-gpui/macos/en.lproj/InfoPlist.strings | 3 +++ apps/desktop-gpui/macos/es.lproj/InfoPlist.strings | 3 +++ apps/desktop-gpui/macos/fr.lproj/InfoPlist.strings | 3 +++ apps/desktop-gpui/macos/hi.lproj/InfoPlist.strings | 3 +++ apps/desktop-gpui/macos/ja.lproj/InfoPlist.strings | 3 +++ .../macos/zh-Hans.lproj/InfoPlist.strings | 3 +++ apps/desktop-gpui/tauri.conf.json | 9 +++++++++ 9 files changed, 42 insertions(+) create mode 100644 apps/desktop-gpui/macos/de.lproj/InfoPlist.strings create mode 100644 apps/desktop-gpui/macos/en.lproj/InfoPlist.strings create mode 100644 apps/desktop-gpui/macos/es.lproj/InfoPlist.strings create mode 100644 apps/desktop-gpui/macos/fr.lproj/InfoPlist.strings create mode 100644 apps/desktop-gpui/macos/hi.lproj/InfoPlist.strings create mode 100644 apps/desktop-gpui/macos/ja.lproj/InfoPlist.strings create mode 100644 apps/desktop-gpui/macos/zh-Hans.lproj/InfoPlist.strings diff --git a/apps/desktop-gpui/Info.plist b/apps/desktop-gpui/Info.plist index dd740dcf952..4939978f032 100644 --- a/apps/desktop-gpui/Info.plist +++ b/apps/desktop-gpui/Info.plist @@ -2,6 +2,18 @@ + CFBundleDevelopmentRegion + en + CFBundleLocalizations + + en + zh-Hans + ja + fr + es + de + hi + CFBundleDocumentTypes diff --git a/apps/desktop-gpui/macos/de.lproj/InfoPlist.strings b/apps/desktop-gpui/macos/de.lproj/InfoPlist.strings new file mode 100644 index 00000000000..46f34840915 --- /dev/null +++ b/apps/desktop-gpui/macos/de.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +NSCameraUsageDescription = "Cap verwendet Ihre Kamera, um Sie in Ihren Caps aufzunehmen."; +NSMicrophoneUsageDescription = "Cap verwendet Ihr Mikrofon, um Sie in Ihren Caps aufzunehmen."; +NSScreenCaptureUsageDescription = "Cap nimmt Ihren Bildschirm als Teil Ihrer Caps auf."; diff --git a/apps/desktop-gpui/macos/en.lproj/InfoPlist.strings b/apps/desktop-gpui/macos/en.lproj/InfoPlist.strings new file mode 100644 index 00000000000..a5d10d3e488 --- /dev/null +++ b/apps/desktop-gpui/macos/en.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +NSCameraUsageDescription = "Cap uses your camera to record you in your Caps"; +NSMicrophoneUsageDescription = "Cap uses your microphone to record you in your Caps"; +NSScreenCaptureUsageDescription = "Cap captures your screen as part of recording your Caps"; diff --git a/apps/desktop-gpui/macos/es.lproj/InfoPlist.strings b/apps/desktop-gpui/macos/es.lproj/InfoPlist.strings new file mode 100644 index 00000000000..809129ea539 --- /dev/null +++ b/apps/desktop-gpui/macos/es.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +NSCameraUsageDescription = "Cap usa tu cámara para grabarte en tus Caps."; +NSMicrophoneUsageDescription = "Cap usa tu micrófono para grabarte en tus Caps."; +NSScreenCaptureUsageDescription = "Cap graba tu pantalla para crear tus Caps."; diff --git a/apps/desktop-gpui/macos/fr.lproj/InfoPlist.strings b/apps/desktop-gpui/macos/fr.lproj/InfoPlist.strings new file mode 100644 index 00000000000..9d9dedbf325 --- /dev/null +++ b/apps/desktop-gpui/macos/fr.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +NSCameraUsageDescription = "Cap utilise votre caméra pour vous enregistrer dans vos Caps."; +NSMicrophoneUsageDescription = "Cap utilise votre micro pour vous enregistrer dans vos Caps."; +NSScreenCaptureUsageDescription = "Cap enregistre votre écran pour créer vos Caps."; diff --git a/apps/desktop-gpui/macos/hi.lproj/InfoPlist.strings b/apps/desktop-gpui/macos/hi.lproj/InfoPlist.strings new file mode 100644 index 00000000000..0f5383a83b0 --- /dev/null +++ b/apps/desktop-gpui/macos/hi.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +NSCameraUsageDescription = "Cap आपकी Caps में आपको रिकॉर्ड करने के लिए आपके कैमरा का उपयोग करता है।"; +NSMicrophoneUsageDescription = "Cap आपकी Caps में आपकी आवाज़ रिकॉर्ड करने के लिए आपके माइक्रोफ़ोन का उपयोग करता है।"; +NSScreenCaptureUsageDescription = "Cap आपकी Caps बनाने के लिए आपकी स्क्रीन रिकॉर्ड करता है।"; diff --git a/apps/desktop-gpui/macos/ja.lproj/InfoPlist.strings b/apps/desktop-gpui/macos/ja.lproj/InfoPlist.strings new file mode 100644 index 00000000000..52c7f6ded93 --- /dev/null +++ b/apps/desktop-gpui/macos/ja.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +NSCameraUsageDescription = "CapはCapsであなたを録画するためにカメラを使用します。"; +NSMicrophoneUsageDescription = "CapはCapsであなたの声を録音するためにマイクを使用します。"; +NSScreenCaptureUsageDescription = "CapはCapsを作成するために画面を録画します。"; diff --git a/apps/desktop-gpui/macos/zh-Hans.lproj/InfoPlist.strings b/apps/desktop-gpui/macos/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 00000000000..aa003520246 --- /dev/null +++ b/apps/desktop-gpui/macos/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +NSCameraUsageDescription = "Cap 使用你的摄像头,在 Caps 中录制你。"; +NSMicrophoneUsageDescription = "Cap 使用你的麦克风,在 Caps 中录制你的声音。"; +NSScreenCaptureUsageDescription = "Cap 录制你的屏幕来制作 Caps。"; diff --git a/apps/desktop-gpui/tauri.conf.json b/apps/desktop-gpui/tauri.conf.json index 314e0f27dd9..bc6696d7056 100644 --- a/apps/desktop-gpui/tauri.conf.json +++ b/apps/desktop-gpui/tauri.conf.json @@ -54,6 +54,15 @@ "minimumSystemVersion": "12.3", "entitlements": "../desktop/src-tauri/Entitlements.plist", "frameworks": ["../../target/native-deps/Spacedrive.framework"], + "files": { + "Resources/en.lproj/InfoPlist.strings": "macos/en.lproj/InfoPlist.strings", + "Resources/zh-Hans.lproj/InfoPlist.strings": "macos/zh-Hans.lproj/InfoPlist.strings", + "Resources/ja.lproj/InfoPlist.strings": "macos/ja.lproj/InfoPlist.strings", + "Resources/fr.lproj/InfoPlist.strings": "macos/fr.lproj/InfoPlist.strings", + "Resources/es.lproj/InfoPlist.strings": "macos/es.lproj/InfoPlist.strings", + "Resources/de.lproj/InfoPlist.strings": "macos/de.lproj/InfoPlist.strings", + "Resources/hi.lproj/InfoPlist.strings": "macos/hi.lproj/InfoPlist.strings" + }, "dmg": { "background": "../desktop/src-tauri/assets/dmg-background.png", "appPosition": { From 5f0c0be03438dbbfa0e0ed6e4c7ef57ddc71f5c3 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 20:10:44 +0000 Subject: [PATCH 16/25] feat(desktop-gpui): localize the update, quit and Open Recording dialogs --- apps/desktop-gpui/locales/de.toml | 26 ++++++ apps/desktop-gpui/locales/en.toml | 26 ++++++ apps/desktop-gpui/locales/es.toml | 26 ++++++ apps/desktop-gpui/locales/fr.toml | 26 ++++++ apps/desktop-gpui/locales/hi.toml | 26 ++++++ apps/desktop-gpui/locales/ja.toml | 26 ++++++ apps/desktop-gpui/locales/zh-CN.toml | 26 ++++++ .../src/i18n/dialogs_surface_tests.rs | 83 +++++++++++++++++++ apps/desktop-gpui/src/i18n/tests.rs | 1 + apps/desktop-gpui/src/installer.rs | 2 +- apps/desktop-gpui/src/menus.rs | 16 ++-- apps/desktop-gpui/src/updates.rs | 55 ++++++------ 12 files changed, 300 insertions(+), 39 deletions(-) diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index 4d0557cdb90..043a1f543bd 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -30,6 +30,7 @@ website = "Website" commit = "Commit" [menu.file] +open_prompt = "Aufnahme öffnen" close_window = "Fenster schließen" title = "Ablage" open_file = "Datei öffnen…" @@ -2875,3 +2876,28 @@ three_d = { one = "{count} 3D-Segment ausgewählt", other = "{count} 3D-Segmente zoom = { one = "{count} ausgewählt", other = "{count} ausgewählt" } zoom_all = { one = "Alle {count} ausgewählt", other = "Alle {count} ausgewählt" } zoom_some = { one = "{count} von {total} ausgewählt", other = "{count} von {total} ausgewählt" } + +[dialog.quit] +canceled = "Beenden abgebrochen: {error}. Cap bleibt geöffnet, damit Sie die Aufnahme abschließen oder wiederherstellen können." +pending = "Beenden abgebrochen, weil die Aufnahme noch nicht beendet ist. Cap bleibt geöffnet. Verwenden Sie „Stoppen“, sobald es verfügbar ist, und beenden Sie Cap dann erneut." +still_open = "Cap ist noch geöffnet" +unconfirmed = "Beenden abgebrochen, weil nicht bestätigt werden konnte, dass die Aufnahme beendet wurde. Cap bleibt geöffnet. Verwenden Sie „Stoppen“ und beenden Sie Cap dann erneut." + +[dialog.update] +already_getting = "Cap bereitet Version {version} bereits vor." +available = "Version {version} von Cap ist verfügbar. Cap lädt sie im Hintergrund herunter und informiert Sie, sobald sie installiert werden kann." +busy = "Schließen Sie Ihre Aufnahme, Ihren Export, Upload, Import oder Ihre Transkription ab, bevor Sie Cap aktualisieren." +busy_title = "Cap ist beschäftigt" +check_failed = "Nach Updates konnte nicht gesucht werden: {error}" +download = "Laden" +download_failed = "Version {version} konnte nicht geladen werden: {error}" +failed = "{message}\n\nSie können die neueste Version jederzeit unter Cap.so/download laden." +install_failed = "Version {version} konnte nicht installiert werden: {error}" +later = "Später" +no_update = "Sie verwenden bereits die neueste Version von Cap." +no_update_title = "Kein Update verfügbar" +not_now = "Nicht jetzt" +package_not_installed = "Das Paket wurde nicht installiert. Bestätigen Sie die Systemabfrage oder installieren Sie es über Cap.so/download." +ready = "Cap {version} ist bereit. Starten Sie Cap neu, um das Update abzuschließen." +restart_now = "Jetzt neu starten" +title = "Cap aktualisieren" diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index 0297a8fcaf4..f738c45262f 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -30,6 +30,7 @@ website = "Website" commit = "Commit" [menu.file] +open_prompt = "Open Recording" close_window = "Close Window" title = "File" open_file = "Open File…" @@ -2877,3 +2878,28 @@ three_d = { one = "{count} 3D segment selected", other = "{count} 3D segments se zoom = { one = "{count} selected", other = "{count} selected", context = "Zoom segments selected when the track has only one." } zoom_all = { one = "All {count} selected", other = "All {count} selected", context = "Every zoom segment on the track is selected; only shown for two or more." } zoom_some = { one = "{count} of {total} selected", other = "{count} of {total} selected", context = "Some of the zoom segments on the track are selected." } + +[dialog.quit] +canceled = { text = "Quit canceled: {error}. Cap will stay open so you can finish or recover the recording.", context = "{error} explains why the recording could not stop." } +pending = "Quit canceled because recording shutdown is still pending. Cap will stay open. Use Stop once available, then try Quit again." +still_open = "Cap is still open" +unconfirmed = "Quit canceled because recording shutdown could not be confirmed. Cap will stay open. Use Stop, then try Quit again." + +[dialog.update] +already_getting = { text = "Cap is already getting version {version} ready.", context = "{version} is a version number such as 0.6.2." } +available = { text = "Version {version} of Cap is available. Cap will download it in the background and let you know when it's ready to install.", context = "{version} is a version number such as 0.6.2." } +busy = "Finish your recording, export, upload, import, or transcription task before updating Cap." +busy_title = "Cap is busy" +check_failed = { text = "Couldn't check for updates: {error}", context = "{error} is a technical reason, usually in English." } +download = { text = "Download", context = "Button: download the update now." } +download_failed = { text = "Couldn't download version {version}: {error}", context = "{error} is a technical reason, usually in English." } +failed = { text = "{message}\n\nYou can always download the latest version from Cap.so/download.", context = "{message} is one of the other update error sentences." } +install_failed = { text = "Couldn't install version {version}: {error}", context = "{error} is a technical reason, usually in English." } +later = "Later" +no_update = "You're already using the latest version of Cap." +no_update_title = "No Update Available" +not_now = "Not Now" +package_not_installed = "The package wasn't installed. Approve the system prompt, or install it from Cap.so/download." +ready = { text = "Cap {version} is ready. Restart Cap to finish updating.", context = "{version} is a version number such as 0.6.2." } +restart_now = "Restart Now" +title = { text = "Update Cap", context = "Title of every update dialog." } diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index ff6fa45abd3..fb7a28a680e 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -30,6 +30,7 @@ website = "Sitio web" commit = "Commit" [menu.file] +open_prompt = "Abrir grabación" close_window = "Cerrar ventana" title = "Archivo" open_file = "Abrir archivo…" @@ -2875,3 +2876,28 @@ three_d = { one = "{count} segmento 3D seleccionado", other = "{count} segmentos zoom = { one = "{count} seleccionado", other = "{count} seleccionados" } zoom_all = { one = "{count} seleccionado", other = "Los {count} seleccionados" } zoom_some = { one = "{count} de {total} seleccionado", other = "{count} de {total} seleccionados" } + +[dialog.quit] +canceled = "Se canceló la salida: {error}. Cap seguirá abierto para que puedas terminar o recuperar la grabación." +pending = "Se canceló la salida porque la grabación todavía se está deteniendo. Cap seguirá abierto. Usa Detener cuando esté disponible y vuelve a intentar salir." +still_open = "Cap sigue abierto" +unconfirmed = "Se canceló la salida porque no se pudo confirmar que la grabación se detuviera. Cap seguirá abierto. Usa Detener y vuelve a intentar salir." + +[dialog.update] +already_getting = "Cap ya está preparando la versión {version}." +available = "La versión {version} de Cap está disponible. Cap la descargará en segundo plano y te avisará cuando esté lista para instalarse." +busy = "Termina tu grabación, exportación, subida, importación o transcripción antes de actualizar Cap." +busy_title = "Cap está ocupado" +check_failed = "No se pudieron buscar actualizaciones: {error}" +download = "Descargar" +download_failed = "No se pudo descargar la versión {version}: {error}" +failed = "{message}\n\nSiempre puedes descargar la última versión desde Cap.so/download." +install_failed = "No se pudo instalar la versión {version}: {error}" +later = "Más tarde" +no_update = "Ya estás usando la última versión de Cap." +no_update_title = "No hay actualizaciones disponibles" +not_now = "Ahora no" +package_not_installed = "No se instaló el paquete. Acepta el aviso del sistema o instálalo desde Cap.so/download." +ready = "Cap {version} está listo. Reinicia Cap para terminar de actualizar." +restart_now = "Reiniciar ahora" +title = "Actualizar Cap" diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index 032b8eb34c9..97b827f4105 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -30,6 +30,7 @@ website = "Site web" commit = "Commit" [menu.file] +open_prompt = "Ouvrir l’enregistrement" close_window = "Fermer la fenêtre" title = "Fichier" open_file = "Ouvrir un fichier…" @@ -2875,3 +2876,28 @@ three_d = { one = "{count} segment 3D sélectionné", other = "{count} segments zoom = { one = "{count} sélectionné", other = "{count} sélectionnés" } zoom_all = { one = "{count} sélectionné", other = "Les {count} sélectionnés" } zoom_some = { one = "{count} sur {total} sélectionné", other = "{count} sur {total} sélectionnés" } + +[dialog.quit] +canceled = "Fermeture annulée : {error}. Cap reste ouvert pour que vous puissiez terminer ou récupérer l’enregistrement." +pending = "Fermeture annulée, car l’arrêt de l’enregistrement est toujours en cours. Cap reste ouvert. Utilisez Arrêter dès que possible, puis quittez à nouveau." +still_open = "Cap est toujours ouvert" +unconfirmed = "Fermeture annulée, car l’arrêt de l’enregistrement n’a pas pu être confirmé. Cap reste ouvert. Utilisez Arrêter, puis quittez à nouveau." + +[dialog.update] +already_getting = "Cap prépare déjà la version {version}." +available = "La version {version} de Cap est disponible. Cap la télécharge en arrière-plan et vous prévient quand elle est prête à être installée." +busy = "Terminez votre enregistrement, exportation, mise en ligne, importation ou transcription avant de mettre à jour Cap." +busy_title = "Cap est occupé" +check_failed = "Impossible de rechercher des mises à jour : {error}" +download = "Télécharger" +download_failed = "Impossible de télécharger la version {version} : {error}" +failed = "{message}\n\nVous pouvez toujours télécharger la dernière version sur Cap.so/download." +install_failed = "Impossible d’installer la version {version} : {error}" +later = "Plus tard" +no_update = "Vous utilisez déjà la dernière version de Cap." +no_update_title = "Aucune mise à jour disponible" +not_now = "Plus tard" +package_not_installed = "Le paquet n’a pas été installé. Acceptez la demande du système, ou installez-le depuis Cap.so/download." +ready = "Cap {version} est prêt. Redémarrez Cap pour terminer la mise à jour." +restart_now = "Redémarrer maintenant" +title = "Mettre à jour Cap" diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index f2bd96a33cd..4eb91d5bc26 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -30,6 +30,7 @@ website = "वेबसाइट" commit = "कमिट" [menu.file] +open_prompt = "रिकॉर्डिंग खोलें" close_window = "विंडो बंद करें" title = "फ़ाइल" open_file = "फ़ाइल खोलें…" @@ -2875,3 +2876,28 @@ three_d = { one = "{count} 3D सेगमेंट चुना गया", oth zoom = { one = "{count} चुना गया", other = "{count} चुने गए" } zoom_all = { one = "{count} चुना गया", other = "सभी {count} चुने गए" } zoom_some = { one = "{total} में से {count} चुना गया", other = "{total} में से {count} चुने गए" } + +[dialog.quit] +canceled = "बाहर निकलना रद्द किया गया: {error}। Cap खुला रहेगा ताकि आप रिकॉर्डिंग पूरी या रिकवर कर सकें।" +pending = "रिकॉर्डिंग अभी बंद हो रही है, इसलिए बाहर निकलना रद्द किया गया। Cap खुला रहेगा। उपलब्ध होते ही रोकें का इस्तेमाल करें, फिर दोबारा बाहर निकलने की कोशिश करें।" +still_open = "Cap अभी भी खुला है" +unconfirmed = "रिकॉर्डिंग बंद होने की पुष्टि नहीं हो सकी, इसलिए बाहर निकलना रद्द किया गया। Cap खुला रहेगा। रोकें का इस्तेमाल करें, फिर दोबारा बाहर निकलने की कोशिश करें।" + +[dialog.update] +already_getting = "Cap पहले से ही वर्ज़न {version} तैयार कर रहा है।" +available = "Cap का वर्ज़न {version} उपलब्ध है। Cap इसे बैकग्राउंड में डाउनलोड करेगा और इंस्टॉल के लिए तैयार होने पर आपको बताएगा।" +busy = "Cap अपडेट करने से पहले अपनी रिकॉर्डिंग, एक्सपोर्ट, अपलोड, इंपोर्ट या ट्रांसक्रिप्शन पूरा करें।" +busy_title = "Cap व्यस्त है" +check_failed = "अपडेट नहीं देखे जा सके: {error}" +download = "डाउनलोड करें" +download_failed = "वर्ज़न {version} डाउनलोड नहीं हो सका: {error}" +failed = "{message}\n\nआप नया वर्ज़न कभी भी Cap.so/download से डाउनलोड कर सकते हैं।" +install_failed = "वर्ज़न {version} इंस्टॉल नहीं हो सका: {error}" +later = "बाद में" +no_update = "आप पहले से ही Cap का नया वर्ज़न इस्तेमाल कर रहे हैं।" +no_update_title = "कोई अपडेट उपलब्ध नहीं है" +not_now = "अभी नहीं" +package_not_installed = "पैकेज इंस्टॉल नहीं हुआ। सिस्टम प्रॉम्प्ट को मंज़ूरी दें, या इसे Cap.so/download से इंस्टॉल करें।" +ready = "Cap {version} तैयार है। अपडेट पूरा करने के लिए Cap रीस्टार्ट करें।" +restart_now = "अभी रीस्टार्ट करें" +title = "Cap अपडेट करें" diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index f02af39458d..9f827a4d94c 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -30,6 +30,7 @@ website = "ウェブサイト" commit = "コミット" [menu.file] +open_prompt = "録画を開く" close_window = "ウィンドウを閉じる" title = "ファイル" open_file = "ファイルを開く…" @@ -2875,3 +2876,28 @@ three_d = { other = "{count}個の3Dセグメントを選択中" } zoom = { other = "{count}個を選択中" } zoom_all = { other = "{count}個すべてを選択中" } zoom_some = { other = "{total}個中{count}個を選択中" } + +[dialog.quit] +canceled = "終了をキャンセルしました:{error}。録画を完了または復元できるよう、Capは開いたままになります。" +pending = "録画の停止処理がまだ終わっていないため、終了をキャンセルしました。Capは開いたままになります。「停止」が使えるようになったら停止してから、もう一度終了してください。" +still_open = "Capはまだ開いています" +unconfirmed = "録画の停止を確認できなかったため、終了をキャンセルしました。Capは開いたままになります。「停止」を使ってから、もう一度終了してください。" + +[dialog.update] +already_getting = "Capはすでにバージョン{version}を準備しています。" +available = "Capのバージョン{version}が利用可能です。バックグラウンドでダウンロードし、インストールの準備ができたらお知らせします。" +busy = "Capをアップデートする前に、録画、書き出し、アップロード、読み込み、または文字起こしを完了してください。" +busy_title = "Capは処理中です" +check_failed = "アップデートを確認できませんでした:{error}" +download = "ダウンロード" +download_failed = "バージョン{version}をダウンロードできませんでした:{error}" +failed = "{message}\n\n最新バージョンはいつでもCap.so/downloadからダウンロードできます。" +install_failed = "バージョン{version}をインストールできませんでした:{error}" +later = "後で" +no_update = "Capはすでに最新バージョンです。" +no_update_title = "アップデートはありません" +not_now = "今はしない" +package_not_installed = "パッケージはインストールされませんでした。システムの確認を承認するか、Cap.so/downloadからインストールしてください。" +ready = "Cap {version}の準備ができました。アップデートを完了するにはCapを再起動してください。" +restart_now = "今すぐ再起動" +title = "Capをアップデート" diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index 26ea525df4a..a9473f3965c 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -30,6 +30,7 @@ website = "网站" commit = "提交" [menu.file] +open_prompt = "打开录制" close_window = "关闭窗口" title = "文件" open_file = "打开文件…" @@ -2875,3 +2876,28 @@ three_d = { other = "已选择 {count} 个 3D 分段" } zoom = { other = "已选择 {count} 个" } zoom_all = { other = "已选择全部 {count} 个" } zoom_some = { other = "已选择 {count}/{total} 个" } + +[dialog.quit] +canceled = "已取消退出:{error}。Cap 将保持打开,以便你完成或恢复录制。" +pending = "录制仍在停止中,已取消退出。Cap 将保持打开。请在可用时点按“停止”,然后再次尝试退出。" +still_open = "Cap 仍在运行" +unconfirmed = "无法确认录制已停止,已取消退出。Cap 将保持打开。请点按“停止”,然后再次尝试退出。" + +[dialog.update] +already_getting = "Cap 已在准备版本 {version}。" +available = "Cap {version} 版本现已推出。Cap 会在后台下载,并在可以安装时通知你。" +busy = "请先完成录制、导出、上传、导入或转写任务,再更新 Cap。" +busy_title = "Cap 正忙" +check_failed = "无法检查更新:{error}" +download = "下载" +download_failed = "无法下载版本 {version}:{error}" +failed = "{message}\n\n你随时可以从 Cap.so/download 下载最新版本。" +install_failed = "无法安装版本 {version}:{error}" +later = "稍后" +no_update = "你使用的已是最新版 Cap。" +no_update_title = "没有可用的更新" +not_now = "以后再说" +package_not_installed = "软件包未安装。请批准系统提示,或从 Cap.so/download 安装。" +ready = "Cap {version} 已准备就绪。重新启动 Cap 以完成更新。" +restart_now = "立即重新启动" +title = "更新 Cap" diff --git a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs index 1cd744f95c6..b7a04b038a8 100644 --- a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs +++ b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs @@ -54,6 +54,14 @@ const FORMATTED: &[&str] = &[ "transcription.download.failed", "transcription.download.file_progress", "transcription.download.progress", + "dialog.quit.canceled", + "dialog.update.already_getting", + "dialog.update.available", + "dialog.update.check_failed", + "dialog.update.download_failed", + "dialog.update.failed", + "dialog.update.install_failed", + "dialog.update.ready", ]; #[test] @@ -732,6 +740,34 @@ fn dialog_surfaces_match_the_literals_they_replaced() { tr::transcription::error::unsupported_language(), "The Recommended and High Accuracy models only transcribe English and 24 other European languages. Download the Small or Medium model to caption this language", ), + (tr::dialog::quit::still_open(), "Cap is still open"), + ( + tr::dialog::quit::pending(), + "Quit canceled because recording shutdown is still pending. Cap will stay open. Use Stop once available, then try Quit again.", + ), + ( + tr::dialog::quit::unconfirmed(), + "Quit canceled because recording shutdown could not be confirmed. Cap will stay open. Use Stop, then try Quit again.", + ), + (tr::dialog::update::title(), "Update Cap"), + (tr::dialog::update::busy_title(), "Cap is busy"), + ( + tr::dialog::update::busy(), + "Finish your recording, export, upload, import, or transcription task before updating Cap.", + ), + (tr::dialog::update::no_update_title(), "No Update Available"), + ( + tr::dialog::update::no_update(), + "You're already using the latest version of Cap.", + ), + (tr::dialog::update::download(), "Download"), + (tr::dialog::update::not_now(), "Not Now"), + (tr::dialog::update::restart_now(), "Restart Now"), + (tr::dialog::update::later(), "Later"), + ( + tr::dialog::update::package_not_installed(), + "The package wasn't installed. Approve the system prompt, or install it from Cap.so/download.", + ), ]; for (translated, literal) in pairs { assert_eq!(translated, literal); @@ -994,3 +1030,50 @@ fn dialog_messages_format_like_the_code_they_replaced() { ); } } + +#[test] +fn update_and_quit_messages_format_like_the_code_they_replaced() { + let error = "connection reset by peer"; + let version = semver::Version::new(0, 6, 2); + let pairs: Vec<(String, String)> = vec![ + ( + tr::dialog::quit::canceled(error), + format!( + "Quit canceled: {error}. Cap will stay open so you can finish or recover the recording." + ), + ), + ( + tr::dialog::update::already_getting(&version), + format!("Cap is already getting version {version} ready."), + ), + ( + tr::dialog::update::available(&version), + format!( + "Version {version} of Cap is available. Cap will download it in the background and let you know when it's ready to install." + ), + ), + ( + tr::dialog::update::check_failed(error), + format!("Couldn't check for updates: {error}"), + ), + ( + tr::dialog::update::download_failed(&version, error), + format!("Couldn't download version {version}: {error}"), + ), + ( + tr::dialog::update::install_failed(&version, error), + format!("Couldn't install version {version}: {error}"), + ), + ( + tr::dialog::update::ready(&version), + format!("Cap {version} is ready. Restart Cap to finish updating."), + ), + ( + tr::dialog::update::failed(error), + format!("{error}\n\nYou can always download the latest version from Cap.so/download."), + ), + ]; + for (translated, literal) in pairs { + assert_eq!(translated, literal); + } +} diff --git a/apps/desktop-gpui/src/i18n/tests.rs b/apps/desktop-gpui/src/i18n/tests.rs index 24e98f959c5..db36127d61b 100644 --- a/apps/desktop-gpui/src/i18n/tests.rs +++ b/apps/desktop-gpui/src/i18n/tests.rs @@ -25,6 +25,7 @@ fn english_matches_the_literals_it_replaced() { (tr::menu::app::settings(), "Settings…".into()), (tr::menu::file::close_window(), "Close Window".into()), (tr::menu::file::open_file(), "Open File…".into()), + (tr::menu::file::open_prompt(), "Open Recording".into()), (tr::menu::file::open_recent(), "Open Recent".into()), (tr::menu::file::import_media(), "Import Media…".into()), (tr::menu::file::title(), "File".into()), diff --git a/apps/desktop-gpui/src/installer.rs b/apps/desktop-gpui/src/installer.rs index 5f3eb3ec890..413d6f528bb 100644 --- a/apps/desktop-gpui/src/installer.rs +++ b/apps/desktop-gpui/src/installer.rs @@ -576,7 +576,7 @@ pub(crate) fn install_deb(package: &Path) -> Result<(), String> { if status.success() { Ok(()) } else { - Err("The package wasn't installed. Approve the system prompt, or install it from cap.so/download.".to_string()) + Err(crate::i18n::tr::dialog::update::package_not_installed().to_string()) } } diff --git a/apps/desktop-gpui/src/menus.rs b/apps/desktop-gpui/src/menus.rs index 675c96b63cb..37134670281 100644 --- a/apps/desktop-gpui/src/menus.rs +++ b/apps/desktop-gpui/src/menus.rs @@ -190,7 +190,7 @@ pub fn init(cx: &mut App) { files, directories, multiple: false, - prompt: Some("Open Recording".into()), + prompt: Some(tr::menu::file::open_prompt().into()), }); cx.spawn(async move |cx| { if let Ok(Ok(Some(paths))) = paths.await @@ -761,7 +761,7 @@ fn apply_quit_action(action: QuitAction, cx: &mut App) { cx.global_mut::().gate.cancel_exit(); tracing::error!(%error, "quit canceled because editor changes could not be saved"); cx.spawn(async move |_| { - platform::alert_dialog("Cap is still open", &error); + platform::alert_dialog(tr::dialog::quit::still_open(), &error); }) .detach(); return; @@ -775,17 +775,19 @@ fn apply_quit_action(action: QuitAction, cx: &mut App) { cx.global_mut::().observer = None; let message = RecordingSession::global(cx).update(cx, |session, cx| { let message = match session.error.as_deref() { - Some(error) => format!("Quit canceled: {error}. Cap will stay open so you can finish or recover the recording."), - None if action == QuitAction::TimedOut => "Quit canceled because recording shutdown is still pending. Cap will stay open. Use Stop once available, then try Quit again.".into(), - None => "Quit canceled because recording shutdown could not be confirmed. Cap will stay open. Use Stop, then try Quit again.".into(), + Some(error) => tr::dialog::quit::canceled(error), + None if action == QuitAction::TimedOut => tr::dialog::quit::pending().into(), + None => tr::dialog::quit::unconfirmed().into(), }; session.error = Some(message.clone()); cx.notify(); message }); tracing::warn!(%message); - cx.spawn(async move |_| platform::alert_dialog("Cap is still open", &message)) - .detach(); + cx.spawn(async move |_| { + platform::alert_dialog(tr::dialog::quit::still_open(), &message) + }) + .detach(); } } } diff --git a/apps/desktop-gpui/src/updates.rs b/apps/desktop-gpui/src/updates.rs index f1f97418e75..a4ccb06ad9f 100644 --- a/apps/desktop-gpui/src/updates.rs +++ b/apps/desktop-gpui/src/updates.rs @@ -5,6 +5,7 @@ use gpui::{App, Global}; use semver::Version; use crate::{ + i18n::tr, installer::{self, ArtifactKind, Release}, session::RecordingSession, store::{GeneralSettings, UpdateChannel, UpdateDownloadPolicy}, @@ -14,8 +15,6 @@ const STABLE_FIRST_CHECK_DELAY: Duration = Duration::from_secs(10); const NIGHTLY_FIRST_CHECK_DELAY: Duration = Duration::from_secs(60); const NIGHTLY_CHECK_INTERVAL: Duration = Duration::from_secs(2 * 60 * 60); const BUSY_RETRY_DELAY: Duration = Duration::from_secs(5 * 60); -const BUSY_MESSAGE: &str = - "Finish your recording, export, upload, import, or transcription task before updating Cap."; #[derive(Clone, Debug, Default, PartialEq)] pub(crate) enum UpdateStatus { @@ -204,8 +203,8 @@ pub(crate) fn check_manually(cx: &mut App) { cx.spawn(async move |_| { crate::platform::activate_app(); crate::platform::alert_dialog( - "Update Cap", - &format!("Cap is already getting version {version} ready."), + tr::dialog::update::title(), + &tr::dialog::update::already_getting(version), ); }) .detach(); @@ -271,7 +270,7 @@ fn finish_manual_check(cx: &mut App, manual: bool) { fn busy_alert(cx: &mut App) { cx.spawn(async move |_| { crate::platform::activate_app(); - crate::platform::alert_dialog("Cap is busy", BUSY_MESSAGE); + crate::platform::alert_dialog(tr::dialog::update::busy_title(), tr::dialog::update::busy()); }) .detach(); } @@ -392,8 +391,7 @@ pub(crate) fn schedule_startup_check(cx: &mut App) { .background_executor() .spawn(async { GeneralSettings::load().update_channel }) .await; - let mut policy = cx - .update(|cx| cx.global::().download_policy); + let mut policy = cx.update(|cx| cx.global::().download_policy); let mut delay = if policy == UpdateDownloadPolicy::Never { None } else { @@ -501,8 +499,8 @@ pub(crate) fn schedule_startup_check(cx: &mut App) { if manual { crate::platform::activate_app(); crate::platform::alert_dialog( - "No Update Available", - "You're already using the latest version of Cap.", + tr::dialog::update::no_update_title(), + tr::dialog::update::no_update(), ); cx.update(|cx| finish_manual_check(cx, true)); } @@ -514,8 +512,8 @@ pub(crate) fn schedule_startup_check(cx: &mut App) { if manual { crate::platform::activate_app(); crate::platform::alert_dialog( - "Update Cap", - &format!("Couldn't check for updates: {error}"), + tr::dialog::update::title(), + &tr::dialog::update::check_failed(error), ); cx.update(|cx| finish_manual_check(cx, true)); } @@ -544,9 +542,8 @@ pub(crate) fn schedule_startup_check(cx: &mut App) { if let Either::Right((Err(_), _)) = select(timer, request).await { return; } - superseded = cx.update(|cx| { - supersedes_held_prompt(&cx.global::().pending) - }); + superseded = + cx.update(|cx| supersedes_held_prompt(&cx.global::().pending)); if superseded { break; } @@ -574,13 +571,10 @@ pub(crate) fn schedule_startup_check(cx: &mut App) { crate::platform::activate_app(); let accepted = crate::platform::confirm_dialog( - "Update Cap", - &format!( - "Version {} of Cap is available. Cap will download it in the background and let you know when it's ready to install.", - release.version - ), - "Download", - "Not Now", + tr::dialog::update::title(), + &tr::dialog::update::available(&release.version), + tr::dialog::update::download(), + tr::dialog::update::not_now(), false, ); cx.update(|cx| finish_manual_check(cx, manual)); @@ -680,7 +674,7 @@ fn start_download(release: Release, cx: &mut App) { } Err(error) => { tracing::warn!(%version, "update download failed: {error}"); - update_failed(&format!("Couldn't download version {version}: {error}"), cx); + update_failed(&tr::dialog::update::download_failed(&version, error), cx); } } }); @@ -690,11 +684,10 @@ fn start_download(release: Release, cx: &mut App) { fn update_failed(message: &str, cx: &mut App) { set_status(UpdateStatus::Idle, cx); - let message = - format!("{message}\n\nYou can always download the latest version from cap.so/download."); + let message = tr::dialog::update::failed(message); cx.spawn(async move |_| { crate::platform::activate_app(); - crate::platform::alert_dialog("Update Cap", &message); + crate::platform::alert_dialog(tr::dialog::update::title(), &message); }) .detach(); } @@ -717,10 +710,10 @@ fn prompt_restart(cx: &mut App) { cx.spawn(async move |cx| { crate::platform::activate_app(); if crate::platform::confirm_dialog( - "Update Cap", - &format!("Cap {version} is ready. Restart Cap to finish updating."), - "Restart Now", - "Later", + tr::dialog::update::title(), + &tr::dialog::update::ready(&version), + tr::dialog::update::restart_now(), + tr::dialog::update::later(), false, ) { cx.update(install_and_relaunch); @@ -746,7 +739,7 @@ pub(crate) fn install_and_relaunch(cx: &mut App) { } if let Err(error) = crate::app_windows::flush_pending_editor_saves(cx) { cx.spawn(async move |_| { - crate::platform::alert_dialog("Cap is still open", &error); + crate::platform::alert_dialog(tr::dialog::quit::still_open(), &error); }) .detach(); return; @@ -791,7 +784,7 @@ pub(crate) fn install_and_relaunch(cx: &mut App) { Err(error) => { tracing::error!(%version, "update install failed: {error}"); cx.global_mut::().staged = None; - update_failed(&format!("Couldn't install version {version}: {error}"), cx); + update_failed(&tr::dialog::update::install_failed(&version, error), cx); } }); }) From b6f26ae89f9a6310b58f3705ae02a6419bd35624 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 22:24:44 +0000 Subject: [PATCH 17/25] fix(desktop-gpui): keep a running update download disabled in the menu --- apps/desktop-gpui/src/menus.rs | 25 ++++++++++++++------- apps/desktop-gpui/src/settings_window.rs | 10 +++++---- apps/desktop-gpui/src/updates.rs | 28 +++++++++++------------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/apps/desktop-gpui/src/menus.rs b/apps/desktop-gpui/src/menus.rs index 37134670281..125471a1977 100644 --- a/apps/desktop-gpui/src/menus.rs +++ b/apps/desktop-gpui/src/menus.rs @@ -65,7 +65,7 @@ impl UpdateRow { let title = match status { UpdateStatus::Idle => tr::menu::update::check(), UpdateStatus::Checking => tr::menu::update::checking(), - UpdateStatus::Downloading { fraction: None, .. } => tr::menu::update::download(), + UpdateStatus::Available { .. } => tr::menu::update::download(), UpdateStatus::Downloading { .. } | UpdateStatus::Installing { .. } => { tr::menu::update::downloading() } @@ -78,10 +78,7 @@ impl UpdateRow { enabled: !matches!( status, UpdateStatus::Checking - | UpdateStatus::Downloading { - fraction: Some(_), - .. - } + | UpdateStatus::Downloading { .. } | UpdateStatus::Installing { .. } ), badge: update_menu_badge(status), @@ -101,7 +98,8 @@ fn update_menu_badge(status: &updates::UpdateStatus) -> Option { version, (fraction * 100.0).round() as u32, )), - UpdateStatus::Downloading { version, .. } + UpdateStatus::Available { version } + | UpdateStatus::Downloading { version, .. } | UpdateStatus::Ready { version } | UpdateStatus::Installing { version } | UpdateStatus::Installed { version, .. } => Some(tr::menu::update::badge_version(version)), @@ -1356,10 +1354,15 @@ mod tests { fraction, }; + let available = UpdateRow::of(&UpdateStatus::Available { + version: version.clone(), + }); let cold = UpdateRow::of(&downloading(None)); let warm = UpdateRow::of(&downloading(Some(0.42))); - assert_eq!(cold.title, "Download update…"); - assert!(cold.enabled); + assert_eq!(available.title, "Download update…"); + assert!(available.enabled); + assert_eq!(cold.title, "Downloading update…"); + assert!(!cold.enabled); assert_eq!(warm.title, "Downloading update…"); assert!(!warm.enabled); @@ -1390,6 +1393,12 @@ mod tests { let version = Version::parse("0.4.0").unwrap(); let badge = |status| UpdateRow::of(&status).badge; + assert_eq!( + badge(UpdateStatus::Available { + version: version.clone(), + }), + Some("v0.4.0".to_string()) + ); assert_eq!( badge(UpdateStatus::Downloading { version: version.clone(), diff --git a/apps/desktop-gpui/src/settings_window.rs b/apps/desktop-gpui/src/settings_window.rs index 4dbf67548ba..c0e416e5bf8 100644 --- a/apps/desktop-gpui/src/settings_window.rs +++ b/apps/desktop-gpui/src/settings_window.rs @@ -2058,10 +2058,12 @@ impl SettingsWindow { fn render_account(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; let (update_label, update_ready) = match crate::updates::status(cx) { - crate::updates::UpdateStatus::Idle => ( - tr::settings::sidebar::check_for_updates().to_string(), - false, - ), + crate::updates::UpdateStatus::Idle | crate::updates::UpdateStatus::Available { .. } => { + ( + tr::settings::sidebar::check_for_updates().to_string(), + false, + ) + } crate::updates::UpdateStatus::Checking => ( tr::settings::sidebar::checking_for_updates().to_string(), false, diff --git a/apps/desktop-gpui/src/updates.rs b/apps/desktop-gpui/src/updates.rs index a4ccb06ad9f..63b57dfc2be 100644 --- a/apps/desktop-gpui/src/updates.rs +++ b/apps/desktop-gpui/src/updates.rs @@ -21,6 +21,12 @@ pub(crate) enum UpdateStatus { #[default] Idle, Checking, + /// Only the debug update simulation waits here. A real check asks in a + /// dialog first, so a real update goes straight to `Downloading`. + #[cfg_attr(not(debug_assertions), allow(dead_code))] + Available { + version: Version, + }, Downloading { version: Version, fraction: Option, @@ -71,7 +77,6 @@ struct UpdateScheduler { manual_in_flight: bool, status: UpdateStatus, download_policy: UpdateDownloadPolicy, - simulation_version: Option, staged: Option, download: Option>, download_epoch: u64, @@ -188,14 +193,7 @@ pub(crate) fn check_manually(cx: &mut App) { prompt_restart(cx); return; } - UpdateStatus::Downloading { - version, - fraction: None, - } if cx - .try_global::() - .and_then(|scheduler| scheduler.simulation_version.as_ref()) - == Some(&version) => - { + UpdateStatus::Available { version } => { simulate_download(version, cx); return; } @@ -235,7 +233,10 @@ pub(crate) fn update_channel_changed(channel: UpdateChannel, cx: &mut App) { fn channel_switch_drops_pending_package(status: &UpdateStatus) -> bool { matches!( status, - UpdateStatus::Checking | UpdateStatus::Downloading { .. } | UpdateStatus::Ready { .. } + UpdateStatus::Checking + | UpdateStatus::Available { .. } + | UpdateStatus::Downloading { .. } + | UpdateStatus::Ready { .. } ) } @@ -296,11 +297,9 @@ fn harness_update_version() -> Option { #[cfg(debug_assertions)] fn simulate_update(version: Version, cx: &mut App) { tracing::info!(%version, "CAP_GPUI_UPDATE_SIMULATION_VERSION: available"); - cx.global_mut::().simulation_version = Some(version.clone()); set_status( - UpdateStatus::Downloading { + UpdateStatus::Available { version: version.clone(), - fraction: None, }, cx, ); @@ -325,7 +324,7 @@ fn simulate_update(version: Version, cx: &mut App) { #[cfg(debug_assertions)] fn simulate_download(version: Version, cx: &mut App) { - if !matches!(status(cx), UpdateStatus::Downloading { fraction: None, .. }) { + if !matches!(status(cx), UpdateStatus::Available { .. }) { return; } tracing::info!(%version, "CAP_GPUI_UPDATE_SIMULATION_VERSION: downloading"); @@ -370,7 +369,6 @@ pub(crate) fn schedule_startup_check(cx: &mut App) { manual_in_flight: false, status: UpdateStatus::Idle, download_policy: GeneralSettings::load().update_download_policy, - simulation_version: None, staged: None, download: None, download_epoch: 0, From 3169d52405ecc321ccf3fe492a431b0ff99de3d0 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:29:46 +0000 Subject: [PATCH 18/25] fix(desktop-gpui): correct and align translations after a full native review of every catalog --- apps/desktop-gpui/locales/de.toml | 212 +++++++++++----------- apps/desktop-gpui/locales/es.toml | 114 ++++++------ apps/desktop-gpui/locales/fr.toml | 120 ++++++------- apps/desktop-gpui/locales/hi.toml | 260 +++++++++++++-------------- apps/desktop-gpui/locales/ja.toml | 260 +++++++++++++-------------- apps/desktop-gpui/locales/zh-CN.toml | 140 +++++++-------- 6 files changed, 553 insertions(+), 553 deletions(-) diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index 7fa0d3fee25..ab2ee96077a 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -34,7 +34,7 @@ open_prompt = "Aufnahme öffnen" close_window = "Fenster schließen" title = "Ablage" open_file = "Datei öffnen…" -open_recent = "Zuletzt geöffnet" +open_recent = "Benutzte Dokumente" import_media = "Medien importieren…" [menu.edit] @@ -63,7 +63,7 @@ changelog = "Änderungsprotokoll" dashboard = "Dashboard" documentation = "Dokumentation" faq = "FAQ" -self_hosting = "Selbsthosting" +self_hosting = "Selbst hosten" help_center = "Hilfe-Center" system_status = "Systemstatus" discord = "Discord-Community beitreten" @@ -161,8 +161,8 @@ description = "Was mit dem Hauptfenster passiert, sobald eine Aufnahme beginnt." label = "Hauptfenster beim Aufnahmestart" [settings.general.recording.window_capture] -description = "Wie eine Fensteraufnahme das gewählte Fenster erfasst. „Fokussiertes Fenster“ nimmt nur dieses Fenster auf, zentriert und folgt ihm, wenn es verschoben wird; „Bildschirmbereich“ nimmt den Bereich des Bildschirms auf, den es abdeckt." -label = "Fensteraufnahme" +description = "Wie eine Fensteraufnahme das gewählte Fenster erfasst. „Fokussiertes Fenster“ nimmt nur dieses Fenster auf, zentriert es und folgt ihm, wenn es verschoben wird. „Bildschirmbereich“ nimmt den Bereich des Bildschirms auf, den das Fenster abdeckt." +label = "Art der Fensteraufnahme" [settings.general.recording.post_deletion] description = "Ob das Aufnahmefenster wieder geöffnet werden soll." @@ -221,13 +221,13 @@ title = "Standard-Projektname" date_time = "Datum & Uhrzeit" date_values = "das Datum der Aufnahme" mode_values = "\"studio\", \"instant\" oder \"screenshot\"" -random = "Zufall" +random = "Zufallswert" random_values = "10 für Dateinamen geeignete Hexadezimalzeichen; {{random:N}} für 1–32." recording_mode = "Aufnahmemodus" recording_mode_values = "\"Studio\", \"Instant\" oder \"Screenshot\"" target = "Ziel" target_kind_values = "\"Display\", \"Window\" oder \"Area\"" -target_name_values = "Name des Monitors oder Titel des Fensters." +target_name_values = "Bildschirmname oder Fenstertitel." time_values = "die Uhrzeit der Aufnahme" [settings.general.excluded_windows] @@ -269,11 +269,11 @@ ok = "OK" account = "Konto" cancel_sign_in = "Anmeldung abbrechen" check_for_updates = "Nach Updates suchen" -checking_for_updates = "Suche nach Updates …" +checking_for_updates = "Suche nach Updates…" click_to_sign_in = "Zum Anmelden klicken" -downloading = "v{version} wird geladen …" -downloading_progress = "v{version} wird geladen … {percent} %" -installing = "v{version} wird installiert …" +downloading = "v{version} wird geladen…" +downloading_progress = "v{version} wird geladen… {percent} %" +installing = "v{version} wird installiert…" previous_versions = "Frühere Versionen anzeigen" restart_to_update = "Neu starten, um auf v{version} zu aktualisieren" sign_in = "Anmelden" @@ -323,7 +323,7 @@ save_failed = "Ihre Aufnahmeeinstellungen konnten nicht gespeichert werden. Bitt [settings.quality.studio] balanced = "Ausgewogen" -balanced_description = "Scharfe Details bei vernünftiger Dateigröße. Richtig für die meisten Aufnahmen." +balanced_description = "Scharfe Details bei vernünftiger Dateigröße. Die richtige Wahl für die meisten Aufnahmen." compatibility = "Kleinere Dateien" compatibility_description = "Spart Speicherplatz. Feine Details können weicher werden, vor allem bei der Kamera." description = "Werden zum Bearbeiten auf Ihrem Computer gespeichert. Die Exportgröße wählen Sie später im Editor." @@ -366,8 +366,8 @@ open_editor = "Editor öffnen" show_cap = "Cap anzeigen" [settings.options.post_screenshot] -copy = "In Zwischenablage kopieren" -copy_and_open_editor = "Kopieren, Editor öffnen" +copy = "In die Zwischenablage kopieren" +copy_and_open_editor = "Kopieren und Editor öffnen" do_nothing = "Nichts tun" open_editor = "Editor öffnen" @@ -386,13 +386,13 @@ needs_modifier_macos = "Verwenden Sie ⌘, ⌃ oder ⌥, damit das Tastenkürzel needs_modifier_other = "Verwenden Sie Strg, Alt oder Win, damit das Tastenkürzel beim Tippen nicht stört." record_shortcut = "Kürzel festlegen" recording_description = "Starten Sie eine Aufnahme, ohne Cap vorher zu öffnen." -recording_hint = "Drücken Sie die gewünschten Tasten. Esc bricht ab, Entf entfernt das Kürzel." +recording_hint = "Drücken Sie die gewünschten Tasten. Esc bricht ab, die Rücktaste entfernt das Kürzel." recording_title = "Aufnahme" saved = "Gespeichert. Drücken Sie {keys} in einer beliebigen App, um {effect}." saved_moved = "Gespeichert. Drücken Sie {keys} in einer beliebigen App, um {effect}. Von „{shortcut}“ entfernt." screenshots_description = "Tastenkürzel funktionieren in jeder App. Klicken Sie auf eines, um es zu ändern." screenshots_title = "Bildschirmfotos" -type_shortcut = "Kürzel eingeben …" +type_shortcut = "Kürzel eingeben…" unavailable = "Eine andere App oder das System verwendet dieses Tastenkürzel bereits. Klicken Sie, um ein anderes zu wählen." unsupported_key = "Diese Taste kann nicht in einem Tastenkürzel verwendet werden. Versuchen Sie einen Buchstaben, eine Zahl oder eine F-Taste." while_recording_title = "Während der Aufnahme" @@ -516,16 +516,16 @@ copy = "Kopieren" description = "Installieren Sie den Cap-Befehl für Terminals, Agenten, Skripte und lokale Automatisierung." install = "CLI installieren" installed = "Installiert" -installing = "Wird installiert ..." -installs_command = "Die Desktop-App installiert einen lokalen\u00A0{command}\u00A0Befehl, der auf die mitgelieferte CLI verweist." +installing = "Wird installiert..." +installs_command = "Die Desktop-App installiert den lokalen Befehl {command} und verknüpft ihn mit der mitgelieferten CLI." load_failed = "CLI-Status konnte nicht geladen werden: {error}" not_installed = "Nicht installiert" path_configured = "cap wurde zu Ihrem PATH hinzugefügt. Starten Sie Ihr Terminal neu, um es zu verwenden, oder führen Sie jetzt Folgendes aus:" path_missing = "Fügen Sie {path} zu Ihrem PATH hinzu, um cap in einem neuen Terminal zu verwenden." remove = "Entfernen" -removing = "Wird entfernt ..." +removing = "Wird entfernt..." repair = "Reparieren" -repairing = "Wird repariert ..." +repairing = "Wird repariert..." retry = "Erneut versuchen" target = "Ziel" title = "Befehlszeile" @@ -538,7 +538,7 @@ count = { one = "{count} Eintrag", other = "{count} Einträge" } description = "Fügen Sie Namen, Schreibweisen, Domains und Vorgaben zur Groß- und Kleinschreibung hinzu, die bei der Untertitelerstellung berücksichtigt werden sollen." placeholder = "Begriff hinzufügen" saved = "Gespeichert" -saving = "Wird gespeichert ..." +saving = "Wird gespeichert..." terms_description = "Fügen Sie jeweils einen Begriff hinzu, um Tipp- und Formatierungsfehler zu vermeiden." terms_title = "Gemerkte Begriffe" title = "Transkription" @@ -562,7 +562,7 @@ open_failed = "Die klassische Cap-App konnte nicht geöffnet werden: {error}" opening = "Die klassische Cap-App wird geöffnet" opening_detail = "Dieses Fenster wird geschlossen, sobald die klassische Cap-App bereit ist." save_failed = "Ihre App-Einstellung konnte nicht gespeichert werden. Cap bleibt geöffnet." -switching = "Zurück zur klassischen Cap-App." +switching = "Es wird zur klassischen Cap-App zurückgewechselt." timed_out = "Die klassische App wurde nicht geöffnet. Prüfen Sie das Entwicklungsterminal auf Build-Fehler und versuchen Sie es dann erneut." [settings.feedback] @@ -577,18 +577,18 @@ debug_title = "Debug-Informationen" description = "Helfen Sie uns, Cap zu verbessern, indem Sie Feedback senden oder Fehler melden. Wir kümmern uns sofort darum." join_discord = "Discord beitreten" operating_system = "Betriebssystem" -placeholder = "Sagen Sie uns, was Sie von Cap halten ..." +placeholder = "Sagen Sie uns, was Sie von Cap halten..." submit = "Feedback senden" submit_failed = "Feedback konnte nicht gesendet werden" -submitting = "Wird gesendet ..." -system_loading = "Systeminformationen werden geladen ..." +submitting = "Wird gesendet..." +system_loading = "Systeminformationen werden geladen..." system_title = "Systeminformationen" thanks = "Vielen Dank für Ihr Feedback!" title = "Feedback" upload_interrupted = "Der Upload wurde unterbrochen" upload_logs = "Protokolle hochladen" upload_sent = "Gesendet. Vielen Dank, das hilft uns, das Problem zu finden." -uploading = "Wird hochgeladen ..." +uploading = "Wird hochgeladen..." [settings.feedback.diagnostic] confirm_body = "Cap belegt Ihren Bildschirm mit einem blinkenden Muster und spielt pro Durchlauf etwa {seconds} Sekunden lang laute Pieptöne ab. Nehmen Sie die Kopfhörer ab, lassen Sie die Lautstärke hörbar und benutzen Sie den Computer erst wieder, wenn der Test fertig ist." @@ -596,14 +596,14 @@ confirm_title = "Diagnose starten?" description = "Nimmt ein kurzes blinkendes und piepsendes Testmuster auf, misst, wie weit Bild und Ton auseinanderdriften, und erfasst Ihre Bildschirme, Kameras, Mikrofone, den Speicherplatz und die Aufnahmeeinstellungen. Es wird nichts gesendet, bis Sie sich dafür entscheiden." mic_description = "Nimmt auch Ihr Mikrofon auf und prüft, ob es die Pieptöne rechtzeitig hört. Benötigt Lautsprecher, die das Mikrofon tatsächlich hören kann." mic_label = "Mikrofon testen" -mode_description = "Welcher Ablauf getestet wird. Beide führt erst den Studio-Teil und dann den Instant-Teil aus." +mode_description = "Welcher Ablauf getestet wird. „Beide“ testet zuerst Studio und dann Instant." mode_label = "Aufnahmemodus" no_cli = "Das Cap-Befehlszeilenprogramm wurde neben diesem Build nicht gefunden, daher umfasst der Bericht nur Ihre Umgebung." no_cli_sync = "Das Cap-Befehlszeilenprogramm wurde neben diesem Build nicht gefunden, daher konnte der Synchronisationstest nicht laufen." run = "Diagnose starten" run_again = "Erneut ausführen" send = "An Cap senden" -sending = "Wird gesendet ..." +sending = "Wird gesendet..." show_file = "Datei anzeigen" stop_recording_first = "Stoppen Sie die laufende Aufnahme, bevor Sie eine Diagnose starten." title = "Diagnosebericht" @@ -624,15 +624,15 @@ title = "Anmeldung erforderlich" [settings.changelog] fetch_failed = "Änderungsprotokoll konnte nicht abgerufen werden" -loading = "Änderungsprotokoll wird geladen ..." +loading = "Änderungsprotokoll wird geladen..." new = "NEU" version = "Version {version} - {date}" [settings.license] activate = "Lizenz aktivieren" -activating = "Wird aktiviert ..." +activating = "Wird aktiviert..." active_title = "Desktop-Lizenz" -checking = "Wird geprüft …" +checking = "Wird geprüft…" deactivate = "Lizenz deaktivieren" description = "Ihr Cap-Tarif und Ihre Desktop-Lizenz an einem Ort." desktop_description = "Ihre Desktop-Lizenz deckt kommerzielles Aufnehmen und Bearbeiten ab. Cap Pro bietet zusätzlich Teilen über die Cloud und Funktionen für die Zusammenarbeit." @@ -683,16 +683,16 @@ other_provider = "Anderer S3-kompatibler Dienst" provider = "Speicheranbieter" region = "Region" remove = "Konfiguration entfernen" -removing = "Wird entfernt ..." +removing = "Wird entfernt..." save_failed = "S3-Konfiguration konnte nicht gespeichert werden" saved = "S3-Konfiguration erfolgreich gespeichert" -saving = "Wird gespeichert ..." +saving = "Wird gespeichert..." secret_key = "Geheimer Zugriffsschlüssel" test = "Verbindung testen" test_failed = "S3-Verbindungstest fehlgeschlagen. Prüfen Sie Ihre Konfiguration und Netzwerkverbindung." test_ok = "S3-Konfigurationstest erfolgreich! Die Verbindung funktioniert." test_timeout = "Der Verbindungstest wurde nach 5 Sekunden abgebrochen. Bitte prüfen Sie die Endpunkt-URL und Ihre Netzwerkverbindung." -testing = "Wird getestet ..." +testing = "Wird getestet..." [settings.integrations.gdrive] active = "Aktiv" @@ -709,22 +709,22 @@ disconnected = "Google Drive getrennt" drive_files = "Drive-Dateien" finish_in_browser = "Schließen Sie die Verbindung mit Google Drive in Ihrem Browser ab, kehren Sie dann hierher zurück und aktualisieren Sie." not_connected = "Nicht verbunden" -opening = "Wird geöffnet ..." +opening = "Wird geöffnet..." refresh = "Aktualisieren" -refreshing = "Wird aktualisiert ..." +refreshing = "Wird aktualisiert..." remaining = "Verbleibend" set_active_failed = "Der aktive Speicheranbieter konnte nicht geändert werden" storage = "Speicher" test = "Testen" test_failed = "Google-Drive-Verbindungstest fehlgeschlagen" -testing = "Wird getestet ..." +testing = "Wird getestet..." trash = "Papierkorb" updated = "Aktualisiert {time}" use_drive = "Google Drive verwenden" use_s3 = "S3 verwenden" used = "{usage} belegt" used_of = "{usage} von {limit} belegt" -waiting = "Warten ..." +waiting = "Warten..." working = "Die Google-Drive-Verbindung funktioniert" working_for = "Die Google-Drive-Verbindung funktioniert für {email}" @@ -732,7 +732,7 @@ working_for = "Die Google-Drive-Verbindung funktioniert für {email}" action_no_effect = "Diese Aktion hat beim gewählten Auslöser keine Wirkung." add_action = "Aktion hinzufügen" add_condition = "Bedingung hinzufügen" -auto_name_empty = "{trigger}-Automatisierung" +auto_name_empty = "Automatisierung: {trigger}" browse = "Durchsuchen" check_compatibility = "Kompatibilität prüfen" compatibility_checked = "Kompatibilität geprüft: Diese Automatisierung läuft noch nicht in Cap. Gespeicherte Regeln bleiben für die Cap-CLI verfügbar. Es wurden keine Aktionen ausgeführt." @@ -744,11 +744,11 @@ description = "Regeln werden mit der Cap-CLI geteilt. Cap speichert diese Regeln empty_description = "Wählen Sie unten eine Vorlage, um mit einem Klick zu starten, oder erstellen Sie Ihre eigene von Grund auf." empty_title = "Noch keine Automatisierungen" enabled = "Aktiviert" -match_all = "Alle erfüllt" -match_any = "Eine erfüllt" +match_all = "Alle Bedingungen" +match_any = "Eine Bedingung" name = "Name" new = "Neue Automatisierung" -no_presets = "Noch keine Vorgaben. Erstellen Sie zuerst eine im Editor." +no_presets = "Noch keine Vorlagen. Erstellen Sie zuerst eine im Editor." only_if = "NUR AUSFÜHREN, WENN" open_in_browser = "Im Browser öffnen" retry_save = "Speichern wiederholen" @@ -780,7 +780,7 @@ format = "Format" frame_rate = "Bildrate" method = "Methode" organization = "Organisations-ID (optional)" -preset = "Vorgabe" +preset = "Vorlage" program = "Programm" resolution = "Auflösung" source = "Quelle" @@ -808,43 +808,43 @@ social = "Social Media" web = "Web" [settings.automations.trigger.screenshot_taken] -label = "Bei aufgenommenem Bildschirmfoto" +label = "Nach einem Bildschirmfoto" noun = "Bildschirmfoto" phrase = "Bildschirmfoto aufgenommen" runs = "Wird bei jedem Bildschirmfoto ausgeführt." [settings.automations.trigger.studio_finished] -label = "Bei beendeter Studio-Aufnahme" +label = "Nach einer Studio-Aufnahme" noun = "Studio-Aufnahme" phrase = "Studio-Aufnahme endet" runs = "Wird nach jeder Studio-Aufnahme ausgeführt." [settings.automations.trigger.instant_finished] -label = "Bei beendeter Instant-Aufnahme" +label = "Nach einer Instant-Aufnahme" noun = "Instant-Aufnahme" phrase = "Instant-Aufnahme endet" runs = "Wird nach jeder Instant-Aufnahme ausgeführt." [settings.automations.trigger.recording_started] -label = "Bei gestarteter Aufnahme" +label = "Beim Start einer Aufnahme" noun = "Aufnahmestart" phrase = "Aufnahme startet" runs = "Wird bei jedem Aufnahmestart ausgeführt." [settings.automations.trigger.upload_completed] -label = "Bei abgeschlossenem Upload" +label = "Nach einem Upload" noun = "Upload" phrase = "Upload abgeschlossen" runs = "Wird nach jedem abgeschlossenen Upload ausgeführt." [settings.automations.trigger.video_imported] -label = "Bei importiertem Video" +label = "Nach einem Videoimport" noun = "Import" phrase = "Video importiert" runs = "Wird bei jedem importierten Video ausgeführt." [settings.automations.trigger.recording_deleted] -label = "Bei gelöschter Aufnahme" +label = "Nach dem Löschen einer Aufnahme" noun = "Löschen" phrase = "Aufnahme gelöscht" runs = "Wird bei jeder gelöschten Aufnahme ausgeführt." @@ -855,7 +855,7 @@ noun = "Zwischenablage" short = "In die Zwischenablage kopieren" [settings.automations.action.save_to_location] -label = "An Speicherort sichern" +label = "Am gewählten Ort speichern" noun = "Ordner" short = "In Ordner speichern" @@ -900,9 +900,9 @@ noun = "Ohne Editor" short = "Editor überspringen" [settings.automations.action.apply_preset] -label = "Editor-Vorgabe anwenden" -noun = "Vorgabe" -short = "Vorgabe anwenden" +label = "Editor-Vorlage anwenden" +noun = "Vorlage" +short = "Vorlage anwenden" [settings.automations.action.run_command] label = "Befehl ausführen" @@ -936,7 +936,7 @@ description = "Cap liest den Text in Ihrem Bildschirmfoto und kopiert ihn für S name = "Text aus Bildschirmfotos holen" [settings.automations.template.save_screenshot] -description = "Jedes neue Bildschirmfoto direkt in einen Ordner Ihrer Wahl." +description = "Jedes neue Bildschirmfoto direkt in einem Ordner Ihrer Wahl speichern." name = "Bildschirmfotos in einem Ordner ablegen" [settings.automations.template.reveal_screenshot] @@ -983,8 +983,8 @@ no_matching_cameras = "Keine passenden Kameras" no_matching_microphones = "Keine passenden Mikrofone" no_microphone = "Kein Mikrofon" no_microphones = "Keine Mikrofone gefunden" -system_audio_off = "Kein Systemton" -system_audio_on = "Systemton aufnehmen" +system_audio_off = "Kein Systemaudio" +system_audio_on = "Systemaudio aufnehmen" [main.device.access] allow_access = "Zugriff erlauben" @@ -996,7 +996,7 @@ open_system_settings = "Systemeinstellungen öffnen" turn_on_in_settings = "Einstellungen öffnen" [main.device.format] -apply_failed = "Gerätformat konnte nicht angewendet werden: {error}" +apply_failed = "Geräteformat konnte nicht angewendet werden: {error}" applying = "Format wird angewendet..." available = "Verfügbar: {format}" camera_changed = "Die Kamera wurde gewechselt oder getrennt. Öffnen Sie ihre Formateinstellungen erneut." @@ -1020,7 +1020,7 @@ on = "Ein" [main.error] camera_only_needs_camera = "Für reine Kameraaufnahmen muss eine Kamera ausgewählt sein." copy = "Fehler kopieren" -devices_not_ready = "Die Aufnahmegeräte sind nicht bereit. Öffnen Sie den Recorder und versuchen Sie es erneut." +devices_not_ready = "Die Aufnahmegeräte sind nicht bereit. Öffnen Sie den Rekorder und versuchen Sie es erneut." title = "Etwas ist schiefgelaufen" [main.header] @@ -1047,7 +1047,7 @@ view_all_recordings = "Alle Aufnahmen anzeigen" view_all_screenshots = "Alle Bildschirmfotos anzeigen" [main.mic_warning] -body = "Diese Aufnahme enthält Ihre Stimme nicht. Wählen Sie ein Mikrofon oder fahren Sie ohne fort." +body = "Diese Aufnahme enthält Ihre Stimme nicht. Wählen Sie ein Mikrofon oder fahren Sie ohne Mikrofon fort." dont_show_again = "Nicht mehr anzeigen" record_without = "Ohne Mikrofon aufnehmen" save_failed = "Ihre Einstellung konnte nicht gespeichert werden. Versuchen Sie es erneut oder lassen Sie das Kästchen leer." @@ -1097,8 +1097,8 @@ stopping = "Wird beendet..." [main.recovery] discard = "Verwerfen" duration_minutes = "~{minutes} Min." -duration_minutes_seconds = "~{minutes} Min. {seconds} Sek." -duration_seconds = "~{seconds} Sek." +duration_minutes_seconds = "~{minutes} Min. {seconds} s" +duration_seconds = "~{seconds} s" recover = "Wiederherstellen" segments = { one = "{count} Segment", other = "{count} Segmente" } segments_with_duration = { one = "{count} Segment · {duration}", other = "{count} Segmente · {duration}" } @@ -1108,7 +1108,7 @@ title = "Unvollständige Aufnahme" message = "Der Instant-Modus lädt schon während der Aufnahme hoch, sodass Ihr Link zum Teilen sofort nach dem Stoppen bereit ist." not_now = "Nicht jetzt" open_browser_again = "Browser erneut öffnen" -pending_message = "Schließen Sie die Anmeldung bei Cap in Ihrem Browser ab. Danach kehrt Cap hierher zurück und öffnet den Recorder." +pending_message = "Schließen Sie die Anmeldung bei Cap in Ihrem Browser ab. Danach kehrt Cap hierher zurück und öffnet den Rekorder." sign_in = "Anmelden" timed_out = "Zeitüberschreitung bei der Anmeldung. Versuchen Sie es erneut." title = "Melden Sie sich an, um den Instant-Modus zu nutzen" @@ -1186,7 +1186,7 @@ unavailable_title = "Kamera nicht verfügbar" [onboarding.customize] footer = "Alles davon können Sie jederzeit in den Einstellungen ändern" subtitle = "Passen Sie alles an, von Tastenkürzeln bis zum Speicherort. Cap richtet sich nach Ihrer Arbeitsweise." -title = "Machen Sie Cap zu Ihrem" +title = "Cap ganz nach Ihren Wünschen" [onboarding.customize.domain] description = "Nutzen Sie Ihre eigene Domain für Links zum Teilen statt cap.so" @@ -1209,12 +1209,12 @@ background = "HINTERGRUND" copied = "Kopiert!" copied_to_clipboard = "In die Zwischenablage kopiert" copy = "Kopieren" -editor = "Cap Editor" +editor = "Cap-Editor" export = "Exportieren" export_complete = "Export abgeschlossen!" exporting = "Wird exportiert..." link_ready = "Link bereit zum Teilen!" -save = "Sichern" +save = "Speichern" start_recording = "Aufnahme starten" stopped = "Beendet" style = "STIL" @@ -1246,7 +1246,7 @@ answer = "Der Instant-Modus lädt schon während der Aufnahme hoch: Stoppen Sie, question = "Was ist der Unterschied zwischen Instant und Studio?" [onboarding.faq.sharing] -answer = "Im Instant-Modus erhalten Sie automatisch einen Link zum Teilen, sobald Sie die Aufnahme beenden. Im Studio-Modus exportieren Sie Ihr bearbeitetes Video und teilen es über die Cap Cloud oder speichern es lokal." +answer = "Im Instant-Modus erhalten Sie automatisch einen Link zum Teilen, sobald Sie die Aufnahme beenden. Im Studio-Modus exportieren Sie Ihr bearbeitetes Video und teilen es über die Cap-Cloud oder speichern es lokal." question = "Wie funktioniert das Teilen?" [onboarding.faq.shortcuts] @@ -1254,7 +1254,7 @@ answer = "Unter Einstellungen → Tastenkürzel können Sie jederzeit alle Ihre question = "Kann ich meine Tastenkürzel später ändern?" [onboarding.faq.storage] -answer = "Alle Aufnahmen werden lokal auf Ihrem Computer gespeichert. Im Instant-Modus werden sie zusätzlich in die Cap Cloud hochgeladen, damit Sie sie leicht teilen können. Den Speicher verwalten Sie in den Einstellungen." +answer = "Alle Aufnahmen werden lokal auf Ihrem Computer gespeichert. Im Instant-Modus werden sie zusätzlich in die Cap-Cloud hochgeladen, damit Sie sie leicht teilen können. Den Speicher verwalten Sie in den Einstellungen." question = "Wo werden meine Aufnahmen gespeichert?" [onboarding.modes.instant] @@ -1271,7 +1271,7 @@ description = "Machen Sie Bildschirmfotos mit einem einzigen Tastenkürzel, füg feature_1 = "Aufnahme per Tastenkürzel" feature_2 = "Beschriftungs- & Zeichenwerkzeuge" feature_3 = "Schöne Hintergründe" -feature_4 = "Kopieren, sichern oder teilen" +feature_4 = "Kopieren, speichern oder teilen" tagline = "Sofort aufnehmen & verschönern" title = "Bildschirmfoto-Modus" @@ -1287,7 +1287,7 @@ title = "Studio-Modus" [onboarding.nav] back = "← Zurück" continue_to_cap = "Weiter zu Cap" -keyboard_hint = "Drücken Sie Eingabe ↵ oder nutzen Sie die Pfeiltasten ← →" +keyboard_hint = "Drücken Sie die Eingabetaste ↵ oder nutzen Sie die Pfeiltasten ← →" next = "Weiter" permissions_needed = "Erteilen Sie die erforderlichen Berechtigungen, um fortzufahren" relaunch = "Cap neu starten" @@ -1319,7 +1319,7 @@ restart = "Zum Aktualisieren neu starten…" [settings.general.updates.download_policy] auto = "Automatisch" -description = "Wähle, ob Cap Updates automatisch herunterlädt." +description = "Wählen Sie, ob Cap Updates automatisch herunterlädt." label = "Update-Downloads" manual = "Vorher fragen" never = "Nie" @@ -1333,12 +1333,12 @@ mode_both = "Beide" [diagnostics.error] create_folder = "Der Diagnoseordner konnte nicht erstellt werden: {error}" -exited = "Der Selbsttest wurde beendet mit {status}" +exited = "Der Selbsttest wurde mit {status} beendet" no_output = "Der Selbsttest hat keine Ausgabe geliefert" serialize = "Der Bericht konnte nicht serialisiert werden: {error}" start_failed = "{program} konnte nicht gestartet werden: {error}" upload_logs = "Protokolle konnten nicht hochgeladen werden: {error}" -upload_status = "Upload fehlgeschlagen mit Status: {status}" +upload_status = "Upload fehlgeschlagen. Status: {status}" wait_failed = "Auf den Selbsttest konnte nicht gewartet werden: {error}" write_failed = "{path} konnte nicht geschrieben werden: {error}" @@ -1378,7 +1378,7 @@ camera_notice = "Cap blendet die Kameravorschau und die Aufnahmesteuerung aus. E camera_notice_instant = "Cap blendet Vorschau und Steuerung aus. Eine ausgewählte Kamera erscheint an der Position ihrer Vorschau, mit der gewählten Form und den gewählten Effekten. Halten Sie die gesamte Vorschau innerhalb des Aufnahmebereichs. Wenn Sie Cap öffnen, wird diese Instant-Aufnahme beendet, bevor die Steuerung erscheint." camera_outside_area = "Verschieben Sie die gesamte Kameravorschau in den Aufnahmebereich, bevor Sie die Instant-Aufnahme starten" camera_preview_closed = "Öffnen Sie die Vorschau der ausgewählten Kamera vor der Instant-Aufnahme" -cleanup_incomplete = "Die Aufräumarbeiten der Aufnahme sind unvollständig: {error}. Eine neue Aufnahme ist gesperrt." +cleanup_incomplete = "Das Aufräumen nach der Aufnahme wurde nicht abgeschlossen: {error}. Eine neue Aufnahme ist nicht möglich." devices_changed = "Die ausgewählten Geräte haben sich geändert. Starten Sie erneut, um die neuen Aufnahmequellen zu bestätigen." devices_changed_before_start = "Die ausgewählten Geräte haben sich vor Beginn der Aufnahme geändert. Starten Sie erneut, um die Quellen zu bestätigen." shortcut_conflict = "Ctrl+Shift+F9 ist einer anderen Cap-Aktion zugewiesen. Ändern Sie dieses Kürzel vor der Aufnahme." @@ -1427,15 +1427,15 @@ subtitle = "Wählen Sie, wie Sie Ihren Bildschirm aufnehmen möchten" title = "Aufnahmemodus wählen" [dialog.storage] -stopped_kept = "Die Aufnahme wurde wegen wenig Speicherplatz beendet. Ihre Aufnahmedateien wurden behalten. Geben Sie Speicherplatz frei und stellen Sie die Aufnahme dann unten wieder her." -stopped_saved = "Die Aufnahme wurde wegen wenig Speicherplatz beendet. Ihre Aufnahme wurde gespeichert." +stopped_kept = "Die Aufnahme wurde beendet, weil der Speicherplatz knapp ist. Ihre Aufnahmedateien bleiben erhalten. Geben Sie Speicherplatz frei und stellen Sie die Aufnahme dann unten wieder her." +stopped_saved = "Die Aufnahme wurde beendet, weil der Speicherplatz knapp ist. Ihre Aufnahme wurde gespeichert." stopping = "Wenig Speicherplatz. Ihre Aufnahme wird beendet und gespeichert…" [dialog.teleprompter] cue_markers = "Cue-Marker" hidden = "Dieses Fenster ist in Cap-Aufnahmen ausgeblendet" mirror = "Text spiegeln" -paused = "Scrollen mit der Aufnahme pausiert" +paused = "Scrollen zusammen mit der Aufnahme pausiert" placeholder = "Skript einfügen oder eingeben…" [import] @@ -1520,12 +1520,12 @@ allow = "Kamera erlauben" body = "Zeigen Sie sich in einer Ecke Ihrer Aufnahmen. Überspringen Sie dies, wenn Sie es nicht brauchen." done_body = "Sie können Ihre Kamera zu jeder Aufnahme hinzufügen." done_title = "Kamera ist aktiviert" -title = "Lassen Sie Cap Sie sehen" +title = "Erlauben Sie Cap, Sie zu sehen" [permission.onboarding.checklist] lede = "macOS bittet Sie zuerst, ein paar Dinge zu erlauben. Das dauert nur eine Minute." regained_lede = "In den Systemeinstellungen wurde etwas deaktiviert. Aktivieren Sie es wieder, um weiter aufzunehmen." -regained_title = "Cap benötigt eine Berechtigung zurück" +regained_title = "Cap benötigt erneut eine Berechtigung" title = "Machen wir Cap startklar" [permission.onboarding.hint] @@ -1540,7 +1540,7 @@ allow = "Mikrofon erlauben" body = "Fügen Sie Ihren Aufnahmen Ihre Stimme hinzu. Sie können dies überspringen und später aktivieren." done_body = "Ihre Stimme kann in Ihren Aufnahmen zu hören sein." done_title = "Mikrofon ist aktiviert" -title = "Lassen Sie Cap Sie hören" +title = "Erlauben Sie Cap, Sie zu hören" [permission.onboarding.relaunch] body = "macOS aktiviert die Bildschirmaufnahme, sobald Cap neu startet. Sie machen genau hier weiter." @@ -1599,7 +1599,7 @@ gradient = "Verlauf" image = "Bild" image_files = "Bild" image_title = "Hintergrundbild" -wallpaper = "Wallpaper" +wallpaper = "Hintergrund" [screenshot_editor.corner] rounded = "Abgerundet" @@ -1607,7 +1607,7 @@ squircle = "Squircle" style = "ECKENSTIL" [screenshot_editor.crop] -full = "Ganz" +full = "Voll" loading_frame = "Bild wird geladen…" position = "Position" @@ -1625,7 +1625,7 @@ save_project = "Fehler beim Speichern des Projekts: {error}" serialize = "Die Bildschirmfoto-Konfiguration konnte nicht serialisiert werden: {error}" sign_in_to_share = "Melden Sie sich im Browser bei Cap an und teilen Sie dann erneut" too_large = "Die Bildgröße überschreitet das Maximum: {width}x{height}" -upgrade_required = "Für diese Funktion ist ein höherer Tarif erforderlich" +upgrade_required = "Diese Funktion erfordert einen höheren Tarif" upload_task = "Der Upload ist fehlgeschlagen: {error}" [screenshot_editor.header] @@ -1658,7 +1658,7 @@ border = "Rahmen" color = "Farbe" corner_rounding = "Eckenrundung" opacity = "Deckkraft" -padding = "Innenabstand" +padding = "Rand" rounding = "Rundung" shadow = "Schatten" size_label = "Größe" @@ -1786,10 +1786,10 @@ title = "Zuschneiden" delete = "Löschen" done = "Fertig" duration = "Dauer" -edge_in = "Ein" -edge_out = "Aus" +edge_in = "Eingang" +edge_out = "Ausgang" enabled = "Aktiviert" -fade_duration = "Ausblenddauer" +fade_duration = "Ein-/Ausblenddauer" flip_horizontally = "Horizontal spiegeln" flip_vertically = "Vertikal spiegeln" name = "Name" @@ -1797,7 +1797,7 @@ opacity = "Deckkraft" [editor.panel.audio] change = "Ändern" -change_hint = "Tippen, um den Track zu ändern" +change_hint = "Klicken, um den Track zu ändern" fade_in = "Einblenden" fade_out = "Ausblenden" fallback_name = "Audio" @@ -1815,7 +1815,7 @@ camera = "Kamera" delete_tooltip = "Diese Einstellung löschen" depth_blur = "Tiefenunschärfe" distance = "Abstand" -drag_to_orbit = "Ziehen zum Kreisen" +drag_to_orbit = "Zum Umkreisen ziehen" ease_in = "Anlauf" ease_out = "Auslauf" end_same_as_start = "Ende · wie Start" @@ -1898,7 +1898,7 @@ system_sans = "System-Sans" system_serif = "System-Serif" [editor.panel.font_weight] -black = "Schwarz" +black = "Ultrafett" bold = "Fett" custom = "Benutzerdefiniert ({weight})" extra_bold = "Extrafett" @@ -1909,7 +1909,7 @@ semibold = "Halbfett" [editor.panel.image] arrange = "Auf der Leinwand anordnen" -arrange_help = "Ziehen Sie das Bild, um es zu verschieben. Ziehen Sie an einer Ecke, um die Größe zu ändern, oder drehen Sie es mit der Drehung unten." +arrange_help = "Ziehen Sie das Bild, um es zu verschieben. Ziehen Sie an einer Ecke, um die Größe zu ändern, oder drehen Sie es mit dem Drehregler unten." center = "Auf der Leinwand zentrieren" lock_aspect = "Seitenverhältnis sperren" missing = "Die Bilddatei fehlt. Ersetzen Sie sie, um dieses Segment mit seinem Timing und seinen Transformationen wiederherzustellen." @@ -1968,7 +1968,7 @@ help = "Überschreibungen gelten nur während dieses Segments. Aktivieren Sie ei reset_crop = "Zuschnitt zurücksetzen" [editor.panel.text_animation] -bounce = "Sprung" +bounce = "Hüpfen" fade = "Blende" letters = "Buchstaben" none = "Keine" @@ -1987,7 +1987,7 @@ zoom = "Zoom" box_shape = "Box" highlight = "Marker" none = "Kein" -pill = "Pille" +pill = "Kapsel" [editor.panel.text_layout] fullscreen = "Vollbild" @@ -2164,7 +2164,7 @@ noise = "Rauschen" to = "Bis" [editor.sidebar.image] -prompt = "Klicken zum Auswählen oder Bild hierher ziehen" +prompt = "Klicken Sie zum Auswählen oder ziehen Sie ein Bild hierher" [editor.sidebar.layout] background_blur = "Hintergrundunschärfe" @@ -2222,7 +2222,7 @@ purple = "Lila" [editor.canvas] camera = "Kamera" -camera_follows_zoom = "Die Kameragröße folgt dem Zoom — zum Verschieben ziehen" +camera_follows_zoom = "Die Kameragröße folgt dem Zoom – zum Verschieben ziehen" edit_zoom = "Zoom bearbeiten" image = "Bild" mask = "Maske" @@ -2363,7 +2363,7 @@ larger = "Größere Datei" maximum = "Maximal" potato = "Kartoffel" smaller = "Kleinere Datei" -social = "Social" +social = "Social Media" web = "Web" [editor.export.resources] @@ -2420,7 +2420,7 @@ stereo = "Stereo" advanced_shadow = "Erweiterte Schatteneinstellungen" background = "Hintergrund" corner_style = "Eckenstil" -custom_position = "Eigene Position (auf der Leinwand gezogen)" +custom_position = "Eigene Position (auf der Leinwand verschoben)" hide = "Kamera ausblenden" keep_size_during_zoom = "Originalgröße beim Zoomen beibehalten" mirror = "Kamera spiegeln" @@ -2457,8 +2457,8 @@ delete = "Löschen" deleting = "Wird gelöscht..." download = "Modell {model} herunterladen" download_note = "Einmaliger Download auf Ihr System. Alle Untertitel werden lokal gespeichert." -downloading = "{model} wird heruntergeladen... {percent}%" -fade_duration = "Einblenddauer" +downloading = "{model} wird heruntergeladen... {percent} %" +fade_duration = "Ein-/Ausblenddauer" font_family = "Schriftart" font_settings = "Schrifteinstellungen" font_weight = "Schriftstärke" @@ -2490,7 +2490,7 @@ pop = "Aufpoppen" [editor.tabs.captions.highlight] color = "Farbe" -pill = "Pille" +pill = "Kapsel" [editor.tabs.captions.language] ar = "Arabisch" @@ -2534,7 +2534,7 @@ small_description = "Kleinster Download" classic = "Klassisch" classic_description = "Klarer Text auf einem einfarbigen, abgerundeten Hintergrund." highlight = "Hervorgehoben" -highlight_description = "Fette Großbuchstaben mit einer Pille hinter dem aktiven Wort." +highlight_description = "Fette Großbuchstaben mit einer Kapsel hinter dem aktiven Wort." karaoke = "Karaoke" karaoke_description = "Wörter leuchten synchron zur Sprache auf." minimal = "Minimal" @@ -2554,7 +2554,7 @@ mass = "Masse" movement_style = "Bewegungsstil des Cursors" show = "Cursor anzeigen" size_label = "Größe" -smooth_movement = "Gleichmäßige Bewegung" +smooth_movement = "Geglättete Bewegung" style_label = "Cursorstil" svg_cursors = "Hochwertige SVG-Cursor" tension = "Spannung" @@ -2580,7 +2580,7 @@ beta = "Beta" changed_during_generation = "Die Zeitleiste oder die Tastatureinstellungen haben sich während der Erstellung geändert. Versuchen Sie es erneut." empty_description = "Klicken Sie auf „Tastatursegmente erstellen“, um Segmente aus aufgezeichneten Tastendrücken zu erstellen." empty_title = "Noch keine Tastatursegmente." -fade_duration = "Einblenddauer" +fade_duration = "Ein-/Ausblenddauer" font_family = "Schriftart" font_settings = "Schrifteinstellungen" font_weight = "Schriftstärke" @@ -2592,7 +2592,7 @@ load_failed = "Aufnahmedaten konnten nicht geladen werden: {error}" no_presses = "Es wurden keine aufgezeichneten Tastendrücke gefunden." position = "Position" regenerate = "Tastatursegmente neu erstellen" -show_modifiers = "Sondertasten-Modifikatoren anzeigen" +show_modifiers = "Modifikatortasten anzeigen" show_special_keys = "Spezialtasten anzeigen" size_label = "Größe" text_color = "Textfarbe" @@ -2644,7 +2644,7 @@ caption = "Transkribieren Sie Ihre Aufnahme automatisch in Untertitel auf dem Bi clip = "Ihr aufgenommenes Bildschirmmaterial." image = "Fügen Sie Ihrer Aufnahme ein Bild hinzu." keyboard = "Zeigen Sie beim Tippen Tastendrücke auf dem Bildschirm an." -mask = "Verpixeln oder schwärzen Sie private Bereiche des Bildschirms." +mask = "Machen Sie private Bildschirmbereiche unscharf oder schwärzen Sie sie." scene = "Wechseln Sie zwischen Layouts für Bildschirm und Kamera." scene_unavailable = "Nehmen Sie mit einer Kamera auf, um Szenen zu verwenden." style = "Ändern Sie Hintergrund-, Kamera- und Cursoreinstellungen im Zeitverlauf." @@ -2898,8 +2898,8 @@ already_getting = "Cap bereitet Version {version} bereits vor." available = "Version {version} von Cap ist verfügbar. Cap lädt sie im Hintergrund herunter und informiert Sie, sobald sie installiert werden kann." busy = "Schließen Sie Ihre Aufnahme, Ihren Export, Upload, Import oder Ihre Transkription ab, bevor Sie Cap aktualisieren." busy_title = "Cap ist beschäftigt" -check_failed = "Nach Updates konnte nicht gesucht werden: {error}" -download = "Laden" +check_failed = "Die Suche nach Updates ist fehlgeschlagen: {error}" +download = "Herunterladen" download_failed = "Version {version} konnte nicht geladen werden: {error}" failed = "{message}\n\nSie können die neueste Version jederzeit unter Cap.so/download laden." install_failed = "Version {version} konnte nicht installiert werden: {error}" diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index 2ce337d5cd1..d44d81c9971 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -25,7 +25,7 @@ services = "Servicios" settings = "Ajustes…" [menu.about] -licensed = "Licenciado bajo AGPL-3.0" +licensed = "Con licencia AGPL-3.0" website = "Sitio web" commit = "Commit" @@ -34,7 +34,7 @@ open_prompt = "Abrir grabación" close_window = "Cerrar ventana" title = "Archivo" open_file = "Abrir archivo…" -open_recent = "Abrir reciente" +open_recent = "Abrir recientes" import_media = "Importar contenido…" [menu.edit] @@ -47,7 +47,7 @@ title = "Edición" undo = "Deshacer" [menu.view] -enter_full_screen = "Entrar en pantalla completa" +enter_full_screen = "Usar pantalla completa" title = "Visualización" [menu.window] @@ -58,12 +58,12 @@ zoom = "Zoom" [menu.help] title = "Ayuda" -feedback = "Dar comentarios…" +feedback = "Enviar comentarios…" changelog = "Registro de cambios" dashboard = "Panel" documentation = "Documentación" faq = "Preguntas frecuentes" -self_hosting = "Autoalojamiento" +self_hosting = "Alojamiento propio" help_center = "Centro de ayuda" system_status = "Estado del sistema" discord = "Unirse a la comunidad de Discord" @@ -267,7 +267,7 @@ ok = "Aceptar" [settings.sidebar] account = "Cuenta" -cancel_sign_in = "Cancelar acceso" +cancel_sign_in = "Cancelar inicio de sesión" check_for_updates = "Buscar actualizaciones" checking_for_updates = "Buscando actualizaciones…" click_to_sign_in = "Haz clic para iniciar sesión" @@ -384,7 +384,7 @@ quarter = "Un cuarto" blocked_while_recording = "No se pueden cambiar los atajos mientras esta grabación usa su tecla de detener. Detén la grabación primero." needs_modifier_macos = "Incluye ⌘, ⌃ o ⌥ para que el atajo no interfiera al escribir." needs_modifier_other = "Incluye Ctrl, Alt o Win para que el atajo no interfiera al escribir." -record_shortcut = "Grabar atajo" +record_shortcut = "Asignar atajo" recording_description = "Empieza a grabar sin abrir Cap primero." recording_hint = "Pulsa las teclas que quieras usar. Esc cancela y Supr lo quita." recording_title = "Grabación" @@ -531,8 +531,8 @@ target = "Destino" title = "Línea de comandos" [settings.transcription] -active_title = "Pistas activas" -applied_note = "Estas pistas se aplican cuando generas subtítulos en el editor." +active_title = "Términos activos" +applied_note = "Estos términos se aplican cuando generas subtítulos en el editor." clear = "Borrar" count = { one = "{count} elemento", many = "{count} de elementos", other = "{count} elementos" } description = "Añade nombres, grafías, dominios y preferencias de mayúsculas que la generación de subtítulos debe tener en cuenta." @@ -596,7 +596,7 @@ confirm_title = "¿Ejecutar el diagnóstico?" description = "Graba un breve patrón de prueba con destellos y pitidos, mide cuánto se desfasan la imagen y el sonido, y registra tus pantallas, cámaras, micrófonos, espacio en disco y ajustes de grabación. No se envía nada hasta que tú decidas enviarlo." mic_description = "También graba tu micrófono y comprueba que capta los pitidos a tiempo. Necesita altavoces que el micrófono pueda oír." mic_label = "Probar el micrófono" -mode_description = "Qué proceso probar. Ambos ejecuta primero la parte Studio y luego la Instant." +mode_description = "Qué proceso probar. «Ambos» ejecuta primero la parte Studio y luego la Instant." mode_label = "Modo de grabación" no_cli = "No se encontró la herramienta de línea de comandos de Cap junto a esta compilación, así que el informe solo cubrirá tu entorno." no_cli_sync = "No se encontró la herramienta de línea de comandos de Cap junto a esta compilación, así que no se pudo ejecutar la prueba de sincronización." @@ -732,7 +732,7 @@ working_for = "La conexión de Google Drive funciona para {email}" action_no_effect = "Esta acción no tiene efecto con el disparador seleccionado." add_action = "Añadir acción" add_condition = "Añadir condición" -auto_name_empty = "Automatización de {trigger}" +auto_name_empty = "Automatización: {trigger}" browse = "Examinar" check_compatibility = "Comprobar compatibilidad" compatibility_checked = "Compatibilidad comprobada: esta automatización todavía no se ejecuta en Cap. Las reglas guardadas siguen disponibles para la CLI de Cap. No se ejecutó ninguna acción." @@ -748,7 +748,7 @@ match_all = "Cumplir todas" match_any = "Cumplir alguna" name = "Nombre" new = "Nueva automatización" -no_presets = "Aún no hay ajustes preestablecidos; crea uno primero en el editor." +no_presets = "Aún no hay preajustes; crea uno primero en el editor." only_if = "SOLO SI" open_in_browser = "Abrir en el navegador" retry_save = "Reintentar guardar" @@ -780,7 +780,7 @@ format = "Formato" frame_rate = "Velocidad de fotogramas" method = "Método" organization = "ID de la organización (opcional)" -preset = "Ajuste preestablecido" +preset = "Preajuste" program = "Programa" resolution = "Resolución" source = "Origen" @@ -804,48 +804,48 @@ window = "Ventana" [settings.automations.compression] maximum = "Máxima" potato = "Patata" -social = "Redes sociales" +social = "Redes" web = "Web" [settings.automations.trigger.screenshot_taken] label = "Al hacer una captura de pantalla" -noun = "captura de pantalla" +noun = "Captura de pantalla" phrase = "Captura de pantalla hecha" runs = "Se ejecuta con cada captura de pantalla." [settings.automations.trigger.studio_finished] label = "Al terminar una grabación Studio" -noun = "grabación Studio" +noun = "Grabación Studio" phrase = "Termina una grabación Studio" runs = "Se ejecuta al terminar cada grabación Studio." [settings.automations.trigger.instant_finished] label = "Al terminar una grabación Instant" -noun = "grabación Instant" +noun = "Grabación Instant" phrase = "Termina una grabación Instant" runs = "Se ejecuta al terminar cada grabación Instant." [settings.automations.trigger.recording_started] label = "Al empezar una grabación" -noun = "inicio de grabación" +noun = "Inicio de grabación" phrase = "Empieza una grabación" runs = "Se ejecuta al empezar cada grabación." [settings.automations.trigger.upload_completed] label = "Al completarse una subida" -noun = "subida" +noun = "Subida" phrase = "Se completa una subida" runs = "Se ejecuta al completarse cada subida." [settings.automations.trigger.video_imported] label = "Al importar un vídeo" -noun = "importación" +noun = "Importación" phrase = "Vídeo importado" runs = "Se ejecuta con cada vídeo importado." [settings.automations.trigger.recording_deleted] label = "Al eliminar una grabación" -noun = "eliminación" +noun = "Eliminación" phrase = "Grabación eliminada" runs = "Se ejecuta con cada grabación eliminada." @@ -900,9 +900,9 @@ noun = "Sin editor" short = "Omitir el editor" [settings.automations.action.apply_preset] -label = "Aplicar un ajuste del editor" -noun = "Ajuste" -short = "Aplicar ajuste" +label = "Aplicar un preajuste del editor" +noun = "Preajuste" +short = "Aplicar preajuste" [settings.automations.action.run_command] label = "Ejecutar comando" @@ -1068,11 +1068,11 @@ quality_settings = "Ajustes de calidad" screenshot = "Modo Captura de pantalla" screenshot_description = "Captura y anota imágenes fijas." studio = "Modo Studio" -studio_description = "Guarda en tu ordenador y abre el editor al detener. Elige la calidad de grabación en los ajustes." +studio_description = "Se guarda en tu ordenador y abre el editor al terminar. Elige la calidad de grabación en los ajustes." [main.mode.info] instant = "Comparte al instante con un enlace. Tu grabación se sube mientras grabas, así que puedes compartirla en cuanto termines." -screenshot = "Captura y anota capturas de pantalla al instante. Ideal para capturas rápidas, informes de errores y comunicación visual." +screenshot = "Haz y anota capturas de pantalla al instante. Ideal para capturas rápidas, informes de errores y comunicación visual." studio = "Graba localmente con la máxima calidad para editar después. Perfecto para crear contenido pulido con efectos y transiciones." title = "Modos de grabación" @@ -1105,7 +1105,7 @@ segments_with_duration = { one = "{count} segmento · {duration}", many = "{coun title = "Grabación incompleta" [main.sign_in] -message = "El modo Instant sube mientras grabas, así que tu enlace para compartir está listo en cuanto te detienes." +message = "Con el modo Instant, la grabación se sube mientras grabas, así que tu enlace para compartir está listo en cuanto te detienes." not_now = "Ahora no" open_browser_again = "Volver a abrir el navegador" pending_message = "Termina de iniciar sesión en Cap en tu navegador. Cap volverá aquí y abrirá la grabadora cuando acabes." @@ -1166,9 +1166,9 @@ starting = "Iniciando" waiting_title = "Esperando acceso a la cámara" [camera.effects] -blur_message = "Tu cámara no está desenfocada." +blur_message = "Tu cámara se muestra sin desenfoque de fondo." blur_title = "Desenfoque de fondo no disponible" -both_message = "Tu cámara no está desenfocada ni en espejo." +both_message = "Tu cámara se muestra sin desenfoque de fondo y sin efecto espejo." both_title = "Efectos de cámara no disponibles" mirror_message = "La vista previa de tu cámara no está en espejo." mirror_title = "Espejo no disponible" @@ -1189,7 +1189,7 @@ subtitle = "Personaliza todo, desde los atajos de teclado hasta el almacenamient title = "Haz que Cap sea tuyo" [onboarding.customize.domain] -description = "Usa tu propio dominio para los enlaces compartidos en lugar de cap.so" +description = "Usa tu propio dominio en los enlaces para compartir en lugar de cap.so" title = "Dominio personalizado" [onboarding.customize.preferences] @@ -1242,7 +1242,7 @@ answer = "Cap es gratis para uso personal. Para equipos y uso comercial, consult question = "¿Cap es gratis?" [onboarding.faq.instant_vs_studio] -answer = "El modo Instant sube mientras grabas: detén la grabación y tendrás un enlace para compartir al momento. El modo Studio graba localmente con calidad completa para que puedas editar con fondos, efectos y más antes de compartir." +answer = "Con el modo Instant, la grabación se sube mientras grabas: detén la grabación y tendrás un enlace para compartir al momento. El modo Studio graba localmente con calidad completa para que puedas editar con fondos, efectos y más antes de compartir." question = "¿Qué diferencia hay entre Instant y Studio?" [onboarding.faq.sharing] @@ -1295,7 +1295,7 @@ skip = "Omitir el recorrido" start = "Empezar a usar Cap" [onboarding.overview] -subtitle = "Tanto si buscas rapidez, calidad de estudio o una captura rápida, Cap tiene un modo para ello." +subtitle = "Ya busques rapidez, calidad de estudio o una captura rápida, Cap tiene un modo para ello." title = "Una app para cada forma de trabajar" [onboarding.toggle] @@ -1378,7 +1378,7 @@ camera_notice = "Cap ocultará la vista previa de la cámara y los controles de camera_notice_instant = "Cap ocultará su vista previa y sus controles. La cámara seleccionada aparecerá en la posición de su vista previa con la forma y los efectos elegidos. Mantén toda la vista previa dentro del área de captura. Abrir Cap detendrá esta grabación Instant antes de mostrar sus controles." camera_outside_area = "Mueve toda la vista previa de la cámara dentro del área de captura antes de iniciar la grabación Instant" camera_preview_closed = "Abre la vista previa de la cámara seleccionada antes de grabar en Instant" -cleanup_incomplete = "La limpieza de la grabación no se completó: {error}. Se bloqueó una nueva grabación." +cleanup_incomplete = "La limpieza de la grabación no se completó: {error}. No se puede iniciar una nueva grabación." devices_changed = "Los dispositivos seleccionados cambiaron. Vuelve a empezar para confirmar las nuevas entradas de grabación." devices_changed_before_start = "Los dispositivos seleccionados cambiaron antes de empezar la grabación. Vuelve a empezar para confirmar las entradas." shortcut_conflict = "Ctrl+Shift+F9 está asignado a otra acción de Cap. Cambia ese atajo antes de grabar." @@ -1442,17 +1442,17 @@ placeholder = "Pega o escribe tu guion…" error_title = "Error de importación" image_failed = "No se pudo importar la imagen: {error}" unsupported = "Archivo multimedia no compatible." -video_failed = "No se pudo importar el video: {error}" +video_failed = "No se pudo importar el vídeo: {error}" [import.filter] image = "Archivos de imagen" media = "Archivos multimedia" -video = "Archivos de video" +video = "Archivos de vídeo" [import.progress] -analyzing = "Analizando el archivo de video..." +analyzing = "Analizando el archivo de vídeo..." complete = "¡Importación completada!" -converting = "Convirtiendo el video... {percent} %" +converting = "Convirtiendo el vídeo... {percent} %" image = "Importando la imagen..." metadata = "Creando los metadatos del proyecto..." starting = "Iniciando la conversión..." @@ -1689,7 +1689,7 @@ progress = "Descargando el modelo: {percent} %" intel_mac = "Los modelos Parakeet no están disponibles en Mac con Intel. Usa un modelo Whisper" model_load = "No se pudo cargar el modelo de subtítulos. Intenta descargarlo de nuevo" model_missing = "No se encontró el modelo de subtítulos. Descárgalo primero" -no_audio = "No se encontró audio en el archivo de video" +no_audio = "No se encontró audio en el archivo de vídeo" unsupported_language = "Los modelos Recomendado y Alta precisión solo transcriben inglés y otros 24 idiomas europeos. Descarga el modelo Pequeño o Mediano para subtitular este idioma" [upload.status] @@ -1773,7 +1773,7 @@ editing_style = "Editando solo el estilo {number}" editing_style_override = "Editando solo el estilo {number} · Al guardar se activa su fondo propio" free = "Libre" full = "Completo" -hint = "Arrastra para mover · Flechas para ajustar · Mantén {key} para no ajustar" +hint = "Arrastra para mover · Flechas para desplazar · Mantén {key} para no ajustar" loading_frame = "Cargando fotograma…" position = "Posición" preview = "Vista previa" @@ -1797,7 +1797,7 @@ opacity = "Opacidad" [editor.panel.audio] change = "Cambiar" -change_hint = "Toca para cambiar de pista" +change_hint = "Haz clic para cambiar de pista" fade_in = "Fundido de entrada" fade_out = "Fundido de salida" fallback_name = "Audio" @@ -1847,7 +1847,7 @@ timing_summary = "{easing} · Lente {fov}" [editor.panel.camera3d.blur] angle = "Ángulo" falloff = "Caída" -focus_size = "Área foco" +focus_size = "Tamaño del foco" focus_x = "Foco X" focus_y = "Foco Y" position = "Posición" @@ -1964,7 +1964,7 @@ crop_width = "Ancho" edit_background = "Editar fondo y marco" edit_camera = "Editar cámara" edit_cursor = "Editar cursor" -help = "Los cambios solo se aplican durante este segmento. Activa un grupo para copiar sus ajustes generales." +help = "Los cambios solo se aplican durante este segmento. Activa un grupo para copiar sus ajustes globales." reset_crop = "Restablecer recorte" [editor.panel.text_animation] @@ -1984,7 +1984,7 @@ words = "Palabras" zoom = "Zoom" [editor.panel.text_background] -box_shape = "Caja" +box_shape = "Recuadro" highlight = "Resaltado" none = "Ninguno" pill = "Píldora" @@ -2011,7 +2011,7 @@ highlight = "Resaltado" kicker = "Antetítulo" label = "Etiqueta" lower_third = "Rótulo" -name_tag = "Etiqueta" +name_tag = "Nombre y cargo" neon = "Neón" quote = "Cita" stat = "Cifra grande" @@ -2086,7 +2086,7 @@ limit_reached = { one = "Puedes guardar hasta {count} degradado. Elimina uno par motion = "Movimiento" name_placeholder = "Ponle nombre a este degradado" name_required = "Escribe un nombre para este degradado." -presets = "Ajustes predefinidos" +presets = "Preajustes" randomize = "Aleatorio" remember_failed = "No se pudieron recordar tus ajustes de degradado animado." remove_colour = "Quitar color" @@ -2202,7 +2202,7 @@ back = "Volver al estilo" background = "Fondo y marco" camera = "Cámara" cursor = "Cursor" -editing_note = "Solo se edita este segmento. Los ajustes generales no cambian." +editing_note = "Solo se edita este segmento. Los ajustes globales no cambian." title = "Estilo {number} · {group}" [editor.sidebar.tab] @@ -2289,7 +2289,7 @@ sign_in_required = "Tienes que iniciar sesión para compartir grabaciones" task_failed = "Falló la tarea de exportación" title = "Exportar" upgrade_message = "Esta función requiere un plan superior" -upgrade_title = "Se requiere una mejora" +upgrade_title = "Se requiere un plan superior" upload_failed = "No se pudo subir la grabación" [editor.export.advanced] @@ -2457,7 +2457,7 @@ delete = "Eliminar" deleting = "Eliminando..." download = "Descargar modelo {model}" download_note = "Descarga única en tu sistema. Todos los subtítulos se guardan localmente." -downloading = "Descargando {model}... {percent}%" +downloading = "Descargando {model}... {percent} %" fade_duration = "Duración del fundido" font_family = "Fuente" font_settings = "Ajustes de fuente" @@ -2626,8 +2626,8 @@ medium = "Media" normal = "Normal" [editor.timeline] -add_shot = "Añadir plano" -add_shot_with_look = "Añadir plano · {look} · {seconds} s" +add_shot = "Añadir toma" +add_shot_with_look = "Añadir toma · {look} · {seconds} s" paused = "En pausa" [editor.timeline.empty] @@ -2678,7 +2678,7 @@ automatic_zoom = "Zoom automático" caption = "Subtítulo" clip = "Clip" cursor_hidden = "Cursor oculto" -highlight = "Destacar" +highlight = "Resaltar" label = "Etiqueta" manual_zoom = "Zoom manual" muted = "Silenciado" @@ -2703,7 +2703,7 @@ audio_filter = "Audio" audio_segment_changed = "Este segmento de audio cambió. Selecciónalo y vuelve a elegir un reemplazo." crop = "Recortar" crop_tooltip = "Recortar vídeo" -no_room_for_3d_shot = "No hay espacio para otro plano 3D" +no_room_for_3d_shot = "No hay espacio para otra toma 3D" redo = "Rehacer" save_failed = "No se pudo guardar {path}. Mantén el editor abierto e inténtalo de nuevo: {error}" undo = "Deshacer" @@ -2741,10 +2741,10 @@ opening = "Espera a que la grabación termine de abrirse antes de eliminarla." save_before_delete_failed = "No se pudo guardar la grabación antes de eliminarla: {error}" [editor.window.camera3d_lane] -add_shot = "+ Añadir plano" +add_shot = "+ Añadir toma" auto_scene = "Escena automática" -how_many = "¿Cuántos planos?" -prompt = "Añade planos 3D cinematográficos a tu grabación" +how_many = "¿Cuántas tomas?" +prompt = "Añade tomas 3D cinematográficas a tu grabación" [editor.window.clip_menu] audio = "AUDIO" @@ -2807,7 +2807,7 @@ media_failed = "No se pudieron leer los archivos de esta grabación: {error}" meta_failed = "No se pudieron cargar los metadatos de la grabación: {error}" no_segments = "La grabación no tiene segmentos. Puede que primero haya que recuperarla." not_found = "¡No se encontró la ruta de vídeo {path}!" -not_studio = "Solo se pueden editar grabaciones de Studio" +not_studio = "Solo se pueden editar grabaciones Studio" [editor.window.presets] apply = "Aplicar" @@ -2830,7 +2830,7 @@ set_default = "Establecer como predeterminado" full = "Completa" half = "Mitad" label = "Vista previa" -quarter = "Cuarto" +quarter = "Un cuarto" [editor.window.text_menu] copy_style = "Copiar estilo" diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index dcf4be016e3..560ef406e0a 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -2,7 +2,7 @@ add = "Ajouter" back = "Retour" cancel = "Annuler" -choose_allow_prompt = "Choisissez Autoriser dans la fenêtre de macOS." +choose_allow_prompt = "Choisissez « Autoriser » dans la demande de macOS." close = "Fermer" copied = "Copié" dismiss = "Fermer" @@ -22,7 +22,7 @@ hide = "Masquer Cap" hide_others = "Masquer les autres" quit = "Quitter Cap" services = "Services" -settings = "Paramètres…" +settings = "Réglages…" [menu.about] licensed = "Sous licence AGPL-3.0" @@ -30,11 +30,11 @@ website = "Site web" commit = "Commit" [menu.file] -open_prompt = "Ouvrir l’enregistrement" +open_prompt = "Ouvrir l'enregistrement" close_window = "Fermer la fenêtre" title = "Fichier" open_file = "Ouvrir un fichier…" -open_recent = "Ouvrir récent" +open_recent = "Ouvrir l'élément récent" import_media = "Importer des médias…" [menu.edit] @@ -59,7 +59,7 @@ zoom = "Réduire/agrandir" [menu.help] title = "Aide" feedback = "Donner son avis…" -changelog = "Journal des modifications" +changelog = "Nouveautés" dashboard = "Tableau de bord" documentation = "Documentation" faq = "FAQ" @@ -104,7 +104,7 @@ experimental = "Expérimental" feedback = "Commentaires" general = "Général" integrations = "Intégrations" -license = "Forfait et licence" +license = "Offre et licence" quality = "Qualité d'enregistrement" recordings = "Enregistrements" screenshots = "Captures d'écran" @@ -153,7 +153,7 @@ description = "Demander une confirmation si aucun micro n'est sélectionné ou s label = "Confirmer avant d'enregistrer sans micro" [settings.general.recording.studio_sound] -description = "Nettoie automatiquement l'audio du micro. Vous pouvez toujours le désactiver pour un enregistrement dans l'éditeur." +description = "Nettoie automatiquement l'audio du micro. Vous pouvez toujours le désactiver pour n'importe quel enregistrement dans l'éditeur." label = "Studio Sound sur les nouveaux enregistrements" [settings.general.recording.main_window_start] @@ -161,7 +161,7 @@ description = "Ce que devient la fenêtre principale lorsqu'un enregistrement co label = "Fenêtre principale au début de l'enregistrement" [settings.general.recording.window_capture] -description = "La façon dont un enregistrement de fenêtre capture la fenêtre choisie. Fenêtre ciblée enregistre uniquement cette fenêtre, centrée et suivie si elle bouge ; Zone de l’écran enregistre la zone de l’écran qu’elle couvre." +description = "La façon dont un enregistrement de fenêtre capture la fenêtre choisie. « Fenêtre ciblée » enregistre uniquement cette fenêtre, centrée et suivie si elle bouge ; « Zone de l'écran » enregistre la zone de l'écran qu'elle couvre." label = "Capture de fenêtre" [settings.general.recording.post_deletion] @@ -199,11 +199,11 @@ label = "Afficher l'encoche du MacBook dans les enregistrements d'écran" [settings.general.recording.max_fps] description = "Fréquence d'images maximale de la capture d'écran." description_high = "Fréquence d'images maximale de la capture d'écran. Des valeurs plus élevées peuvent provoquer des pertes d'images ou une hausse de l'utilisation du processeur sur certains systèmes." -fps_120 = "120 FPS" -fps_24 = "24 FPS" -fps_25 = "25 FPS" -fps_30 = "30 FPS" -fps_60 = "60 FPS (recommandé)" +fps_120 = "120 i/s" +fps_24 = "24 i/s" +fps_25 = "25 i/s" +fps_30 = "30 i/s" +fps_60 = "60 i/s (recommandé)" label = "Fréquence d'images maximale" [settings.general.storage] @@ -319,14 +319,14 @@ save_as = "Enregistrer sous" title = "Captures d'écran" [settings.quality] -save_failed = "Impossible d'enregistrer vos paramètres d'enregistrement. Veuillez réessayer." +save_failed = "Impossible de sauvegarder vos paramètres d'enregistrement. Veuillez réessayer." [settings.quality.studio] balanced = "Équilibré" balanced_description = "Des détails nets pour une taille de fichier raisonnable. Idéal pour la plupart des enregistrements." compatibility = "Fichiers plus légers" compatibility_description = "Économise de l'espace disque. Les détails fins peuvent s'adoucir, surtout sur la caméra." -description = "Enregistrés sur votre ordinateur pour être modifiés. Vous choisirez la taille d'export plus tard, dans l'éditeur." +description = "Enregistrés sur votre ordinateur pour être modifiés. Vous choisirez la taille d'exportation plus tard, dans l'éditeur." recommended = "Recommandé" title = "Enregistrements Studio" ultra = "Détail maximal" @@ -358,7 +358,7 @@ minimise = "Réduire" [settings.options.window_capture] focused_window = "Fenêtre ciblée" -screen_region = "Zone de l’écran" +screen_region = "Zone de l'écran" [settings.options.post_studio] do_nothing = "Ne rien faire" @@ -376,7 +376,7 @@ do_nothing = "Ne rien faire" reopen = "Rouvrir l'enregistreur" [settings.options.preview_quality] -full = "Complète" +full = "Pleine" half = "Moitié" quarter = "Quart" @@ -434,7 +434,7 @@ studio_show_cap_hint = "Retour à l'enregistreur" cycle_mode = "Changer de mode d'enregistrement" fallback = "Raccourci" open_picker = "Ouvrir le sélecteur d'enregistrement" -pause = "Pause ou reprise" +pause = "Mettre en pause ou reprendre" record_area = "Enregistrer une zone" record_display = "Enregistrer un écran" record_window = "Enregistrer une fenêtre" @@ -553,7 +553,7 @@ native_app_description = "Désactivez pour fermer cette app et rouvrir l'app Tau reliability = "Fiabilité" [settings.experimental.switch] -busy = "Terminez votre enregistrement, export, mise en ligne, import ou transcription avant de passer à l'app classique." +busy = "Terminez votre enregistrement, exportation, mise en ligne, importation ou transcription avant de passer à l'app classique." check_failed = "Impossible de vérifier si l'app Cap classique s'est ouverte : {error}" data_stays = "Vos enregistrements et vos paramètres restent exactement où ils sont." failed = "Impossible d'ouvrir l'app Cap classique." @@ -596,7 +596,7 @@ confirm_title = "Lancer le diagnostic ?" description = "Enregistre une courte mire clignotante et sonore, mesure le décalage entre l'image et le son, et relève vos écrans, caméras, micros, espace disque et paramètres d'enregistrement. Rien n'est envoyé tant que vous ne le décidez pas." mic_description = "Enregistre aussi votre micro et vérifie qu'il capte les bips à temps. Nécessite des haut-parleurs que le micro peut réellement entendre." mic_label = "Tester le micro" -mode_description = "La chaîne de traitement à tester. Les deux exécute d'abord la partie Studio, puis la partie Instant." +mode_description = "La chaîne de traitement à tester. « Les deux » exécute d'abord la partie Studio, puis la partie Instant." mode_label = "Mode d'enregistrement" no_cli = "L'outil en ligne de commande Cap est introuvable à côté de cette version, le rapport ne couvrira donc que votre environnement." no_cli_sync = "L'outil en ligne de commande Cap est introuvable à côté de cette version, le test de synchronisation n'a donc pas pu s'exécuter." @@ -790,7 +790,7 @@ url = "URL" [settings.automations.placeholder] organization_id = "ID de l'organisation" project_folder = "Dossier du projet" -window_title = "ex. Slack" +window_title = "ex. : Slack" [settings.automations.clipboard] original = "Capture d'origine" @@ -880,7 +880,7 @@ noun = "Ouverture" short = "Ouvrir le fichier" [settings.automations.action.ocr] -label = "Reconnaître le texte (OCR) vers le presse-papiers" +label = "Copier le texte reconnu (OCR) dans le presse-papiers" noun = "Texte" short = "Copier le texte (OCR)" @@ -1160,7 +1160,7 @@ paused = "En pause" pausing = "Pause…" resuming = "Reprise…" saving = "Arrêt…" -starting = "Début…" +starting = "Début" [camera] waiting_title = "En attente de l'accès à la caméra" @@ -1232,13 +1232,13 @@ save_progress = "Impossible d'enregistrer votre progression. Veuillez réessayer save_sound = "Impossible d'enregistrer la préférence de son." [onboarding.faq] -pricing = "Voir les forfaits" -pricing_link = "Voir les forfaits ↗" +pricing = "Voir les offres" +pricing_link = "Voir les offres ↗" subtitle = "Tout ce qu'il faut savoir pour bien démarrer." title = "Questions fréquentes" [onboarding.faq.free] -answer = "Cap est gratuit pour un usage personnel. Pour les équipes et un usage commercial, consultez nos forfaits." +answer = "Cap est gratuit pour un usage personnel. Pour les équipes et un usage commercial, consultez nos offres." question = "Cap est-il gratuit ?" [onboarding.faq.instant_vs_studio] @@ -1349,9 +1349,9 @@ analyzing_studio = "Analyse de l'enregistrement Studio..." cancelling = "Annulation..." collecting = "Collecte des informations système..." done = "Finalisation du test de synchronisation..." -exporting = "Export de l'enregistrement..." -exporting_instant = "Export de l'enregistrement Instant..." -exporting_studio = "Export de l'enregistrement Studio..." +exporting = "Exportation de l'enregistrement..." +exporting_instant = "Exportation de l'enregistrement Instant..." +exporting_studio = "Exportation de l'enregistrement Studio..." finalizing = "Finalisation de l'enregistrement..." finalizing_instant = "Finalisation de l'enregistrement Instant..." finalizing_studio = "Finalisation de l'enregistrement Studio..." @@ -1475,9 +1475,9 @@ select_camera = "Veuillez choisir une caméra" [overlay.device] camera_allow = "Autoriser l'accès à la caméra…" -camera_turn_on = "Activer l'accès à la caméra dans les réglages…" +camera_turn_on = "Activer l'accès à la caméra dans Réglages Système…" microphone_allow = "Autoriser l'accès au micro…" -microphone_turn_on = "Activer l'accès au micro dans les réglages…" +microphone_turn_on = "Activer l'accès au micro dans Réglages Système…" [overlay.start] instant = "Mode Instant : enregistrer un lien de partage" @@ -1489,7 +1489,7 @@ take_screenshot = "Prendre une capture d'écran" [permission.guide] accessibility_body = "Déposez-le dans la liste « Accessibilité » ci-dessus, puis vérifiez que son interrupteur est activé." -screen_body = "Déposez-le dans la liste « Enregistrement de l'écran et de l'audio du système » ci-dessus, puis vérifiez que son interrupteur est activé." +screen_body = "Déposez-le dans la liste « Enregistrement de l'écran et des sons du système » ci-dessus, puis vérifiez que son interrupteur est activé." title = "Faites glisser Cap dans la liste" [permission.name] @@ -1586,7 +1586,7 @@ transparent = "Transparent" auto = "Auto" classic = "Classique ⋅4:3" square = "Carré ⋅1:1" -tall = "Haut ⋅3:4" +tall = "Portrait ⋅3:4" vertical = "Vertical ⋅9:16" wide = "Large ⋅16:9" @@ -1620,7 +1620,7 @@ not_screenshot = "Ce n'est pas un dossier de capture d'écran" open_image = "Impossible d'ouvrir l'image : {error}" renderer_failed = "Le moteur de rendu GPU n'a pas pu démarrer : {error}" renderer_not_running = "Le moteur de rendu des captures d'écran n'est pas lancé" -renderer_stopped = "Le moteur de rendu s'est arrêté avant la fin de l'export" +renderer_stopped = "Le moteur de rendu s'est arrêté avant la fin de l'exportation" save_project = "Erreur lors de l'enregistrement du projet : {error}" serialize = "Impossible de sérialiser la configuration de la capture d'écran : {error}" sign_in_to_share = "Connectez-vous à Cap dans votre navigateur, puis partagez à nouveau" @@ -1629,7 +1629,7 @@ upgrade_required = "Cette fonctionnalité nécessite une offre supérieure" upload_task = "La tâche d'envoi a échoué : {error}" [screenshot_editor.header] -aspect_ratio = "Format" +aspect_ratio = "Format d'image" copy = "Copier dans le presse-papiers" crop_image = "Recadrer l'image" more_actions = "Plus d'actions" @@ -1766,14 +1766,14 @@ tint = "Teinte" vignette = "Vignettage" [editor.crop] -aspect_ratio = "Proportions" +aspect_ratio = "Format d'image" center = "Centrer" dimensions = "Taille" editing_style = "Modification du style {number} uniquement" editing_style_override = "Modification du style {number} uniquement · L'enregistrement active son arrière-plan personnalisé" free = "Libre" full = "Entier" -hint = "Glisser pour déplacer · Flèches pour ajuster · Maintenez {key} pour ne pas aligner" +hint = "Faites glisser pour déplacer · Flèches pour ajuster · Maintenez {key} pour désactiver l'aimantation" loading_frame = "Chargement de l'image…" position = "Position" preview = "Aperçu" @@ -1797,7 +1797,7 @@ opacity = "Opacité" [editor.panel.audio] change = "Changer" -change_hint = "Touchez pour changer de piste" +change_hint = "Cliquez pour changer de piste" fade_in = "Fondu d'entrée" fade_out = "Fondu de sortie" fallback_name = "Audio" @@ -1836,8 +1836,8 @@ reset_camera = "Réinitialiser la caméra" roll = "Roulis" rotate_x = "Rotation X" rotate_y = "Rotation Y" -shift_x = "Pan X" -shift_y = "Pan Y" +shift_x = "Décalage X" +shift_y = "Décalage Y" shots = { one = "{count} plan", other = "{count} plans" } still_shot = "Plan fixe" swap = "Inverser le début et la fin" @@ -1847,7 +1847,7 @@ timing_summary = "{easing} · Objectif {fov}" [editor.panel.camera3d.blur] angle = "Angle" falloff = "Fondu" -focus_size = "Netteté" +focus_size = "Zone nette" focus_x = "Point X" focus_y = "Point Y" position = "Position" @@ -1878,7 +1878,7 @@ slide_by = "Passage" spotlight = "Projecteur" tilt_away = "Bascule" top_down = "Plongée" -unfold = "Dépli" +unfold = "Déploiement" [editor.panel.camera3d.sequence] product_tour = "Visite produit" @@ -1894,7 +1894,7 @@ title = "Sous-titre {number}" [editor.panel.font] system_mono = "Mono système" -system_sans = "Sans système" +system_sans = "Sans-serif système" system_serif = "Serif système" [editor.panel.font_weight] @@ -1923,7 +1923,7 @@ title = "Clavier {number}" [editor.panel.mask] blur = "Flou" effect = "Effet" -highlight = "Mise en avant" +highlight = "Mise en valeur" outside_darkness = "Assombrissement extérieur" pixel_size = "Taille des pixels" pixelate = "Pixeliser" @@ -1987,7 +1987,7 @@ zoom = "Zoom" box_shape = "Boîte" highlight = "Surligné" none = "Aucun" -pill = "Pilule" +pill = "Pastille" [editor.panel.text_layout] fullscreen = "Plein écran" @@ -2067,7 +2067,7 @@ auto_locked = "Le mode auto nécessite la capture du curseur. Activez « Capture how_it_works = "Comment ça marche ?" manual = "Manuel" manual_help = "Le zoom manuel reste sur le point fixe que vous choisissez ci-dessous." -mixed = "Variable" +mixed = "Mixte" mode = "Mode de zoom" positions_mixed = "Les segments zooment sur des points différents. Faites glisser pour tous les placer au même endroit." title = "Zoom {number}" @@ -2247,7 +2247,7 @@ importing = "Importation…" mp4_filter = "Vidéo MP4" prepare_controls_failed = "Impossible de préparer les commandes d'enregistrement : {error}" record_new = "Enregistrer un nouveau clip" -save_before_recording_failed = "Impossible d'enregistrer le projet actuel avant l'enregistrement : {error}" +save_before_recording_failed = "Impossible de sauvegarder le projet actuel avant l'enregistrement : {error}" split = "Partie {number}" title = "Clips" @@ -2288,7 +2288,7 @@ reupload_transparent = "Les liens de partage nécessitent un arrière-plan sans sign_in_required = "Vous devez vous connecter pour partager des enregistrements" task_failed = "Échec de la tâche d'exportation" title = "Exporter" -upgrade_message = "Cette fonctionnalité nécessite une formule supérieure" +upgrade_message = "Cette fonctionnalité nécessite une offre supérieure" upgrade_title = "Mise à niveau requise" upload_failed = "Impossible de mettre en ligne l'enregistrement" @@ -2417,7 +2417,7 @@ mono_right = "Mono D" stereo = "Stéréo" [editor.tabs.camera] -advanced_shadow = "Paramètres d'ombre avancés" +advanced_shadow = "Réglages avancés de l'ombre" background = "Arrière-plan" corner_style = "Style des coins" custom_position = "Position personnalisée (déplacée sur le canevas)" @@ -2668,7 +2668,7 @@ zoom = "Supprimer la piste Zoom." [editor.timeline.scene] camera_only = "Caméra seule" default = "Par défaut" -floating = "Flottante" +floating = "Flottant" hide_camera = "Masquer la caméra" split_screen = "Écran partagé" @@ -2719,8 +2719,8 @@ classic = "Classique" classic_ratio = "Classique ⋅4:3" square = "Carré" square_ratio = "Carré ⋅1:1" -tall = "Haut" -tall_ratio = "Haut ⋅3:4" +tall = "Portrait" +tall_ratio = "Portrait ⋅3:4" tooltip = "Format d'image" vertical = "Vertical" vertical_ratio = "Vertical ⋅9:16" @@ -2738,7 +2738,7 @@ unsupported = "Format audio non pris en charge : .{extension}" clip_import = "Attendez la fin de l'importation du clip avant de fermer ou de supprimer cet enregistrement." export = "Attendez la fin de l'exportation ou de son annulation avant de fermer ou de supprimer cet enregistrement." opening = "Attendez que l'enregistrement soit ouvert avant de le supprimer." -save_before_delete_failed = "Impossible d'enregistrer l'enregistrement avant de le supprimer : {error}" +save_before_delete_failed = "Impossible de sauvegarder l'enregistrement avant de le supprimer : {error}" [editor.window.camera3d_lane] add_shot = "+ Ajouter un plan" @@ -2753,7 +2753,7 @@ delete = "Supprimer le clip" delete_only_clip = "Impossible de supprimer le seul clip" hide_cursor = "Masquer le curseur" hide_cursor_description = "Disparaît pendant la lecture de ce clip" -keep_pitch = "Garder la hauteur" +keep_pitch = "Conserver la tonalité" match_speed = "Suivre la vitesse" merge_next = "Fusionner avec le clip suivant" merge_previous = "Fusionner avec le clip précédent" @@ -2884,14 +2884,14 @@ style = { one = "{count} segment de style sélectionné", other = "{count} segme text_segments = { one = "{count} segment de texte sélectionné", other = "{count} segments de texte sélectionnés" } three_d = { one = "{count} segment 3D sélectionné", other = "{count} segments 3D sélectionnés" } zoom = { one = "{count} sélectionné", other = "{count} sélectionnés" } -zoom_all = { one = "{count} sélectionné", other = "Les {count} sélectionnés" } +zoom_all = { one = "{count} sélectionné", other = "Tous sélectionnés ({count})" } zoom_some = { one = "{count} sur {total} sélectionné", other = "{count} sur {total} sélectionnés" } [dialog.quit] -canceled = "Fermeture annulée : {error}. Cap reste ouvert pour que vous puissiez terminer ou récupérer l’enregistrement." -pending = "Fermeture annulée, car l’arrêt de l’enregistrement est toujours en cours. Cap reste ouvert. Utilisez Arrêter dès que possible, puis quittez à nouveau." +canceled = "Fermeture annulée : {error}. Cap reste ouvert pour que vous puissiez terminer ou récupérer l'enregistrement." +pending = "Fermeture annulée, car l'arrêt de l'enregistrement est toujours en cours. Cap reste ouvert. Utilisez Arrêter dès que possible, puis quittez à nouveau." still_open = "Cap est toujours ouvert" -unconfirmed = "Fermeture annulée, car l’arrêt de l’enregistrement n’a pas pu être confirmé. Cap reste ouvert. Utilisez Arrêter, puis quittez à nouveau." +unconfirmed = "Fermeture annulée, car l'arrêt de l'enregistrement n'a pas pu être confirmé. Cap reste ouvert. Utilisez Arrêter, puis quittez à nouveau." [dialog.update] already_getting = "Cap prépare déjà la version {version}." @@ -2902,12 +2902,12 @@ check_failed = "Impossible de rechercher des mises à jour : {error}" download = "Télécharger" download_failed = "Impossible de télécharger la version {version} : {error}" failed = "{message}\n\nVous pouvez toujours télécharger la dernière version sur Cap.so/download." -install_failed = "Impossible d’installer la version {version} : {error}" +install_failed = "Impossible d'installer la version {version} : {error}" later = "Plus tard" no_update = "Vous utilisez déjà la dernière version de Cap." no_update_title = "Aucune mise à jour disponible" not_now = "Plus tard" -package_not_installed = "Le paquet n’a pas été installé. Acceptez la demande du système, ou installez-le depuis Cap.so/download." +package_not_installed = "Le paquet n'a pas été installé. Acceptez la demande du système, ou installez-le depuis Cap.so/download." ready = "Cap {version} est prêt. Redémarrez Cap pour terminer la mise à jour." restart_now = "Redémarrer maintenant" title = "Mettre à jour Cap" diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index 2f7dcc7be19..de0f312c6d6 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -2,7 +2,7 @@ add = "जोड़ें" back = "वापस" cancel = "रद्द करें" -choose_allow_prompt = "macOS के संकेत में “अनुमति दें” चुनें।" +choose_allow_prompt = "macOS प्रॉम्प्ट में “अनुमति दें” चुनें।" close = "बंद करें" copied = "कॉपी हो गया" dismiss = "बंद करें" @@ -17,10 +17,10 @@ search = "खोजें" yes = "हाँ" [menu.app] -about = "Cap के बारे में" +about = "Cap परिचय" hide = "Cap छिपाएँ" hide_others = "अन्य छिपाएँ" -quit = "Cap से बाहर निकलें" +quit = "Cap बंद करें" services = "सेवाएँ" settings = "सेटिंग्स…" @@ -34,36 +34,36 @@ open_prompt = "रिकॉर्डिंग खोलें" close_window = "विंडो बंद करें" title = "फ़ाइल" open_file = "फ़ाइल खोलें…" -open_recent = "हाल ही में खोलें" +open_recent = "हालिया खोलें" import_media = "मीडिया इम्पोर्ट करें…" [menu.edit] -copy = "कॉपी करें" -cut = "कट करें" -paste = "पेस्ट करें" -redo = "फिर से करें" +copy = "कॉपी" +cut = "कट" +paste = "पेस्ट" +redo = "फिर करें" select_all = "सभी चुनें" -title = "संपादित करें" +title = "संपादन" undo = "पहले जैसा करें" [menu.view] -enter_full_screen = "पूर्ण स्क्रीन में जाएँ" -title = "देखें" +enter_full_screen = "फ़ुल स्क्रीन में प्रवेश करें" +title = "दृश्य" [menu.window] close_window = "विंडो बंद करें" -minimize = "छोटा करें" +minimize = "मिनिमाइज़ करें" title = "विंडो" zoom = "ज़ूम करें" [menu.help] title = "सहायता" -feedback = "प्रतिक्रिया दें…" +feedback = "फ़ीडबैक दें…" changelog = "चेंजलॉग" dashboard = "डैशबोर्ड" documentation = "दस्तावेज़ीकरण" faq = "सामान्य प्रश्न" -self_hosting = "सेल्फ-होस्टिंग" +self_hosting = "सेल्फ़-होस्टिंग" help_center = "सहायता केंद्र" system_status = "सिस्टम स्थिति" discord = "Discord समुदाय से जुड़ें" @@ -79,9 +79,9 @@ record_area = "क्षेत्र रिकॉर्ड करें" record_display = "डिस्प्ले रिकॉर्ड करें" record_window = "विंडो रिकॉर्ड करें" request_permissions = "अनुमतियाँ माँगें" -screenshot_area = "क्षेत्र का स्क्रीनशॉट" -screenshot_display = "डिस्प्ले का स्क्रीनशॉट" -screenshot_window = "विंडो का स्क्रीनशॉट" +screenshot_area = "क्षेत्र का स्क्रीनशॉट लें" +screenshot_display = "डिस्प्ले का स्क्रीनशॉट लें" +screenshot_window = "विंडो का स्क्रीनशॉट लें" select_mode = "मोड चुनें" settings = "सेटिंग्स" take_screenshot = "स्क्रीनशॉट लें" @@ -98,7 +98,7 @@ studio = "Studio" [settings.nav] automations = "ऑटोमेशन" -changelog = "बदलावों की सूची" +changelog = "चेंजलॉग" cli = "CLI" experimental = "प्रायोगिक" feedback = "फ़ीडबैक" @@ -173,11 +173,11 @@ description = "सफलतापूर्वक अपलोड होने label = "अपलोड के बाद Instant रिकॉर्डिंग हटाएँ" [settings.general.recording.crash_recovery] -description = "ऐसे हिस्सों में रिकॉर्ड करें जिन्हें क्रैश या बिजली जाने के बाद वापस पाया जा सके। कैप्चर के दौरान फ़ाइलें थोड़ी बड़ी होती हैं।" -label = "क्रैश के बाद वापस पाई जा सकने वाली रिकॉर्डिंग" +description = "ऐसे हिस्सों में रिकॉर्ड करें जिन्हें क्रैश या बिजली जाने के बाद रिकवर किया जा सके। कैप्चर के दौरान फ़ाइलें थोड़ी बड़ी होती हैं।" +label = "क्रैश के बाद रिकवर हो सकने वाली रिकॉर्डिंग" [settings.general.recording.custom_cursor] -description = "कर्सर की स्थिति अलग से कैप्चर करें, ताकि आप एडिटर में उसका आकार और स्मूदिंग बदल सकें।" +description = "कर्सर की स्थिति अलग से कैप्चर करें, ताकि आप एडिटर में उसका आकार और स्मूथिंग बदल सकें।" label = "कर्सर को अलग से कैप्चर करें (Studio)" [settings.general.recording.auto_zoom] @@ -268,13 +268,13 @@ ok = "ठीक है" [settings.sidebar] account = "खाता" cancel_sign_in = "साइन इन रद्द करें" -check_for_updates = "अपडेट देखें" +check_for_updates = "अपडेट जाँचें" checking_for_updates = "अपडेट देखे जा रहे हैं…" click_to_sign_in = "साइन इन करने के लिए क्लिक करें" downloading = "v{version} डाउनलोड हो रहा है…" downloading_progress = "v{version} डाउनलोड हो रहा है… {percent}%" installing = "v{version} इंस्टॉल हो रहा है…" -previous_versions = "पिछले वर्शन देखें" +previous_versions = "पिछले वर्ज़न देखें" restart_to_update = "v{version} पर अपडेट करने के लिए रीस्टार्ट करें" sign_in = "साइन इन करें" sign_out = "साइन आउट करें" @@ -283,7 +283,7 @@ signed_in = "साइन इन है" [settings.recordings] corrupted_body = "रिकॉर्डिंग विफल रही, इसलिए एडिटर में इस फ़ाइल के साथ समस्याएँ हो सकती हैं! अगर फ़ाइल रिकवर करने में दिक्कत हो, तो कृपया सपोर्ट से संपर्क करें!" corrupted_title = "रिकॉर्डिंग शायद खराब हो गई है" -delete = "डिलीट करें" +delete = "हटाएँ" description = "अपनी रिकॉर्डिंग मैनेज करें और उन पर कार्रवाई करें।" edit = "एडिट करें" empty_library = "कोई रिकॉर्डिंग नहीं मिली" @@ -293,7 +293,7 @@ in_progress = "रिकॉर्डिंग जारी है" load_more = "और लोड करें" open_bundle = "रिकॉर्डिंग बंडल खोलें" open_link = "लिंक खोलें" -retry_upload = "अपलोड फिर से करें" +retry_upload = "फिर से अपलोड करें" show_all = "सभी दिखाएँ" title = "रिकॉर्डिंग" @@ -307,7 +307,7 @@ studio_matching = "कोई मिलती-जुलती Studio रिकॉ [settings.screenshots] copy = "क्लिपबोर्ड पर कॉपी करें" -delete = "डिलीट करें" +delete = "हटाएँ" description = "अपने स्क्रीनशॉट मैनेज करें और उन पर कार्रवाई करें।" empty = "कोई स्क्रीनशॉट नहीं" empty_library = "कोई स्क्रीनशॉट नहीं मिला" @@ -381,7 +381,7 @@ half = "आधा" quarter = "एक-चौथाई" [settings.shortcuts] -blocked_while_recording = "जब तक यह रिकॉर्डिंग अपनी स्टॉप की इस्तेमाल कर रही है, शॉर्टकट नहीं बदले जा सकते। पहले रिकॉर्डिंग रोकें।" +blocked_while_recording = "जब तक यह रिकॉर्डिंग अपनी स्टॉप कुंजी इस्तेमाल कर रही है, शॉर्टकट नहीं बदले जा सकते। पहले रिकॉर्डिंग रोकें।" needs_modifier_macos = "⌘, ⌃ या ⌥ शामिल करें ताकि शॉर्टकट टाइपिंग में बाधा न बने।" needs_modifier_other = "Ctrl, Alt या Win शामिल करें ताकि शॉर्टकट टाइपिंग में बाधा न बने।" record_shortcut = "शॉर्टकट सेट करें" @@ -394,7 +394,7 @@ screenshots_description = "शॉर्टकट किसी भी ऐप म screenshots_title = "स्क्रीनशॉट" type_shortcut = "शॉर्टकट दबाएँ…" unavailable = "कोई दूसरा ऐप या सिस्टम पहले से यह शॉर्टकट इस्तेमाल कर रहा है। कोई दूसरा चुनने के लिए क्लिक करें।" -unsupported_key = "यह की शॉर्टकट में इस्तेमाल नहीं हो सकती। कोई अक्षर, संख्या या F-की आज़माएँ।" +unsupported_key = "यह कुंजी शॉर्टकट में इस्तेमाल नहीं हो सकती। कोई अक्षर, संख्या या F-कुंजी आज़माएँ।" while_recording_title = "रिकॉर्डिंग के दौरान" [settings.shortcuts.after_screenshot] @@ -438,10 +438,10 @@ pause = "पॉज़ या फिर से शुरू करें" record_area = "क्षेत्र रिकॉर्ड करें" record_display = "डिस्प्ले रिकॉर्ड करें" record_window = "विंडो रिकॉर्ड करें" -restart = "रिकॉर्डिंग फिर से शुरू करें" -screenshot_area = "क्षेत्र का स्क्रीनशॉट" -screenshot_display = "डिस्प्ले का स्क्रीनशॉट" -screenshot_window = "विंडो का स्क्रीनशॉट" +restart = "रिकॉर्डिंग रीस्टार्ट करें" +screenshot_area = "क्षेत्र का स्क्रीनशॉट लें" +screenshot_display = "डिस्प्ले का स्क्रीनशॉट लें" +screenshot_window = "विंडो का स्क्रीनशॉट लें" start_instant = "Instant रिकॉर्डिंग शुरू करें" start_studio = "Studio रिकॉर्डिंग शुरू करें" stop = "रिकॉर्डिंग रोकें" @@ -563,10 +563,10 @@ opening = "क्लासिक Cap ऐप खुल रहा है" opening_detail = "क्लासिक Cap ऐप तैयार होने पर यह विंडो बंद हो जाएगी।" save_failed = "आपकी ऐप पसंद सेव नहीं हो सकी। Cap अभी भी खुला है।" switching = "क्लासिक Cap ऐप पर वापस जा रहे हैं।" -timed_out = "क्लासिक ऐप नहीं खुला। डेव टर्मिनल में बिल्ड की गड़बड़ियाँ देखें, फिर से कोशिश करें।" +timed_out = "क्लासिक ऐप नहीं खुला। डेव टर्मिनल में बिल्ड की गड़बड़ियाँ देखें, फिर दोबारा कोशिश करें।" [settings.feedback] -app_version = "ऐप वर्शन" +app_version = "ऐप वर्ज़न" capture_support = "कैप्चर सपोर्ट" capture_supported = "स्क्रीन कैप्चर: सपोर्टेड" capture_unsupported = "स्क्रीन कैप्चर: सपोर्टेड नहीं" @@ -608,8 +608,8 @@ show_file = "फ़ाइल दिखाएँ" stop_recording_first = "डायग्नोस्टिक चलाने से पहले मौजूदा रिकॉर्डिंग रोकें।" title = "डायग्नोस्टिक रिपोर्ट" try_again = "फिर से कोशिश करें" -warning_body = "एक फ़ुलस्क्रीन विंडो काले और सफ़ेद रंग में चमकती है जबकि आपके स्पीकर से 1kHz की बीप बजती है, और Cap इसे रिकॉर्ड करके मापता है कि तस्वीर और आवाज़ कितनी अलग हो गई हैं। चलते समय कंप्यूटर को न छुएँ। दोनों मोड मिलाकर कुछ मिनट लगते हैं।" -warning_title = "टेस्ट आपकी पूरी स्क्रीन लेता है और आवाज़ करता है।" +warning_body = "एक फ़ुल स्क्रीन विंडो काले और सफ़ेद रंग में चमकती है और साथ ही आपके स्पीकर से 1kHz की बीप बजती है; Cap इसे रिकॉर्ड करके मापता है कि तस्वीर और आवाज़ कितनी अलग हो गई हैं। चलते समय कंप्यूटर को न छुएँ। दोनों मोड मिलाकर कुछ मिनट लगते हैं।" +warning_title = "टेस्ट आपकी पूरी स्क्रीन घेर लेता है और आवाज़ करता है।" [settings.feedback.diagnostic.verdict] environment_only = "सिर्फ़ एनवायरनमेंट" @@ -626,7 +626,7 @@ title = "साइन इन ज़रूरी है" fetch_failed = "चेंजलॉग नहीं मिल सका" loading = "चेंजलॉग लोड हो रहा है..." new = "नया" -version = "वर्शन {version} - {date}" +version = "वर्ज़न {version} - {date}" [settings.license] activate = "लाइसेंस सक्रिय करें" @@ -638,16 +638,16 @@ description = "आपका Cap प्लान और डेस्कटॉप desktop_description = "आपका डेस्कटॉप लाइसेंस कमर्शियल रिकॉर्डिंग और एडिटिंग को कवर करता है। Cap Pro इसमें क्लाउड शेयरिंग और कोलैबोरेशन फ़ीचर जोड़ता है।" desktop_name = "डेस्कटॉप लाइसेंस" expires = "समाप्ति" -explore_description = "हमारी वेबसाइट पर मौजूदा कीमतें और उनमें शामिल सब कुछ तुलना करें।" +explore_description = "हमारी वेबसाइट पर मौजूदा कीमतों और उनमें शामिल हर चीज़ की तुलना करें।" explore_desktop = "डेस्कटॉप लाइसेंस · डेस्कटॉप रिकॉर्डर और एडिटर का कमर्शियल इस्तेमाल।" explore_pro = "Cap Pro · डेस्कटॉप लाइसेंस, साथ में क्लाउड शेयरिंग, AI फ़ीचर और कोलैबोरेशन।" explore_title = "प्लान देखें" free_description = "निजी इस्तेमाल के लिए लोकल रूप से रिकॉर्ड और एडिट करें। कमर्शियल इस्तेमाल या ज़्यादा क्लाउड फ़ीचर के लिए पेड प्लान चुनें।" -have_key = "क्या आपके पास लाइसेंस की है?" -key = "लाइसेंस की" +have_key = "क्या आपके पास लाइसेंस कुंजी है?" +key = "लाइसेंस कुंजी" no_instance = "कोई इंस्टेंस ID नहीं मिली" opens_pricing = "आपके ब्राउज़र में cap.so/pricing खोलता है।" -placeholder = "लाइसेंस की" +placeholder = "लाइसेंस कुंजी" plan_title = "आपका प्लान" pro_description = "आपके खाते में क्लाउड शेयरिंग, Pro फ़ीचर और कमर्शियल इस्तेमाल के लिए डेस्कटॉप लाइसेंस शामिल है।" refresh = "प्लान रीफ़्रेश करें" @@ -670,7 +670,7 @@ title = "इंटीग्रेशन" upgrade = "Pro में अपग्रेड करें" [settings.integrations.s3] -access_key = "एक्सेस की ID" +access_key = "एक्सेस कुंजी ID" bucket = "बकेट का नाम" configuration = "कॉन्फ़िगरेशन" delete_failed = "S3 कॉन्फ़िग डिलीट नहीं हो सका" @@ -687,7 +687,7 @@ removing = "हटाया जा रहा है..." save_failed = "S3 कॉन्फ़िग सेव नहीं हो सका" saved = "S3 कॉन्फ़िगरेशन सेव हो गया" saving = "सेव हो रहा है..." -secret_key = "सीक्रेट एक्सेस की" +secret_key = "सीक्रेट एक्सेस कुंजी" test = "कनेक्शन टेस्ट करें" test_failed = "S3 कनेक्शन टेस्ट विफल रहा। अपना कॉन्फ़िग और नेटवर्क कनेक्शन जाँचें।" test_ok = "S3 कॉन्फ़िगरेशन टेस्ट सफल रहा! कनेक्शन काम कर रहा है।" @@ -697,7 +697,7 @@ testing = "टेस्ट हो रहा है..." [settings.integrations.gdrive] active = "सक्रिय" active_for_uploads = "नए अपलोड के लिए सक्रिय" -cached = "{time} का कैश" +cached = "{time} को कैश किया गया" connect = "Google Drive कनेक्ट करें" connect_failed = "Google Drive कनेक्शन शुरू नहीं हो सका" connected_inactive = "कनेक्टेड है पर सक्रिय नहीं" @@ -739,13 +739,13 @@ compatibility_checked = "कम्पैटिबिलिटी जाँची condition_never_matches = "चुने गए ट्रिगर के लिए यह शर्त कभी पूरी नहीं होती।" copy_link = "लिंक क्लिपबोर्ड पर कॉपी करें" dangerous = "Cap CLI में यह ऑटोमेशन आपकी अनुमतियों से कमांड चलाता है या नेटवर्क अनुरोध भेजता है। सिर्फ़ भरोसेमंद वैल्यू इस्तेमाल करें।" -delete = "डिलीट करें" +delete = "हटाएँ" description = "नियम Cap CLI के साथ साझा होते हैं। Cap इन नियमों को सेव करता है, लेकिन अभी ऑटोमेशन नहीं चलाता।" empty_description = "एक क्लिक में शुरू करने के लिए नीचे कोई टेम्पलेट चुनें, या शुरुआत से अपना बनाएँ।" empty_title = "अभी कोई ऑटोमेशन नहीं" enabled = "चालू" -match_all = "सभी मिलें" -match_any = "कोई भी मिले" +match_all = "सभी शर्तें पूरी हों" +match_any = "कोई भी शर्त पूरी हो" name = "नाम" new = "नया ऑटोमेशन" no_presets = "अभी कोई प्रीसेट नहीं है, पहले एडिटर में एक बनाएँ।" @@ -770,7 +770,7 @@ enabled_unsaved = "चालू · सेव नहीं है" [settings.automations.field] arguments = "आर्ग्युमेंट (स्पेस से अलग)" -body = "मुख्य भाग" +body = "बॉडी" body_template = "बॉडी टेम्पलेट (वैकल्पिक)" compression = "कम्प्रेशन" destination = "डेस्टिनेशन फ़ोल्डर (वैकल्पिक, खाली = प्रोजेक्ट फ़ोल्डर)" @@ -803,7 +803,7 @@ window = "विंडो" [settings.automations.compression] maximum = "अधिकतम" -potato = "सबसे कम" +potato = "न्यूनतम" social = "सोशल" web = "वेब" @@ -880,12 +880,12 @@ noun = "खोलें" short = "फ़ाइल खोलें" [settings.automations.action.ocr] -label = "टेक्स्ट पहचानकर (OCR) क्लिपबोर्ड पर" +label = "टेक्स्ट पहचानकर (OCR) क्लिपबोर्ड पर कॉपी करें" noun = "टेक्स्ट" short = "टेक्स्ट कॉपी (OCR)" [settings.automations.action.notify] -label = "नोटिफ़िकेशन दिखाएँ" +label = "सूचना दिखाएँ" noun = "सूचना" short = "सूचित करें" @@ -1024,7 +1024,7 @@ devices_not_ready = "रिकॉर्डिंग डिवाइस तैय title = "कुछ गड़बड़ हो गई" [main.header] -changelog = "बदलावों की सूची" +changelog = "चेंजलॉग" help = "सहायता और टूर" recordings = "रिकॉर्डिंग" screenshots = "स्क्रीनशॉट" @@ -1066,13 +1066,13 @@ instant = "Instant मोड" instant_description = "रेंडरिंग की ज़रूरत नहीं: रिकॉर्ड करते-करते अपलोड होता है, ताकि रोकते ही आप लिंक शेयर कर सकें।" quality_settings = "क्वालिटी सेटिंग्स" screenshot = "स्क्रीनशॉट मोड" -screenshot_description = "स्टिल इमेज कैप्चर करें और उन पर नोट्स जोड़ें।" +screenshot_description = "स्टिल इमेज कैप्चर करें और उन पर एनोटेशन जोड़ें।" studio = "Studio मोड" studio_description = "आपके कंप्यूटर पर सहेजता है और रोकने पर एडिटर खोलता है। रिकॉर्डिंग क्वालिटी सेटिंग्स में चुनें।" [main.mode.info] instant = "लिंक के साथ तुरंत शेयर करें। आपकी रिकॉर्डिंग रिकॉर्ड करते-करते अपलोड होती है, ताकि पूरा होते ही आप उसे शेयर कर सकें।" -screenshot = "तुरंत स्क्रीनशॉट लें और उन पर नोट्स जोड़ें। झटपट कैप्चर, बग रिपोर्ट और विज़ुअल बातचीत के लिए बढ़िया।" +screenshot = "तुरंत स्क्रीनशॉट लें और उन पर एनोटेशन जोड़ें। झटपट कैप्चर, बग रिपोर्ट और विज़ुअल बातचीत के लिए बढ़िया।" studio = "बाद में एडिट करने के लिए सबसे अच्छी क्वालिटी में लोकल रिकॉर्ड करें। इफ़ेक्ट और ट्रांज़िशन के साथ बढ़िया कंटेंट बनाने के लिए एकदम सही।" title = "रिकॉर्डिंग मोड" @@ -1143,9 +1143,9 @@ low_storage = "स्टोरेज कम है। आपकी रिकॉ delete = "हटाएँ" delete_message = "क्या आप वाकई रिकॉर्डिंग हटाना चाहते हैं?" delete_title = "हटाने की पुष्टि करें" -restart = "फिर से शुरू करें" -restart_message = "क्या आप वाकई रिकॉर्डिंग फिर से शुरू करना चाहते हैं? मौजूदा रिकॉर्डिंग हटा दी जाएगी।" -restart_title = "फिर से शुरू करने की पुष्टि करें" +restart = "रीस्टार्ट करें" +restart_message = "क्या आप वाकई रिकॉर्डिंग रीस्टार्ट करना चाहते हैं? मौजूदा रिकॉर्डिंग हटा दी जाएगी।" +restart_title = "रीस्टार्ट की पुष्टि करें" [controls.pause] pause = "रिकॉर्डिंग पॉज़ करें" @@ -1216,7 +1216,7 @@ exporting = "एक्सपोर्ट हो रहा है..." link_ready = "लिंक शेयर करने के लिए तैयार है!" save = "सहेजें" start_recording = "रिकॉर्डिंग शुरू करें" -stopped = "रुका" +stopped = "रुक गया" style = "स्टाइल" [onboarding.demo.steps] @@ -1267,9 +1267,9 @@ tagline = "सेकंडों में रिकॉर्ड करें title = "Instant मोड" [onboarding.modes.screenshot] -description = "एक ही हॉटकी से स्क्रीनशॉट लें, नोट्स और सुंदर बैकग्राउंड जोड़ें, फिर तुरंत शेयर या कॉपी करें।" +description = "एक ही हॉटकी से स्क्रीनशॉट लें, एनोटेशन और सुंदर बैकग्राउंड जोड़ें, फिर तुरंत शेयर या कॉपी करें।" feature_1 = "हॉटकी से तुरंत कैप्चर" -feature_2 = "नोट्स और ड्रॉइंग टूल" +feature_2 = "एनोटेशन और ड्रॉइंग टूल" feature_3 = "सुंदर बैकग्राउंड" feature_4 = "कॉपी करें, सहेजें या शेयर करें" tagline = "तुरंत कैप्चर करें और सजाएँ" @@ -1287,7 +1287,7 @@ title = "Studio मोड" [onboarding.nav] back = "← वापस" continue_to_cap = "Cap पर जारी रखें" -keyboard_hint = "Enter ↵ दबाएँ या ← → ऐरो की इस्तेमाल करें" +keyboard_hint = "Enter ↵ दबाएँ या ← → तीर कुंजियों का इस्तेमाल करें" next = "जारी रखें" permissions_needed = "जारी रखने के लिए ज़रूरी अनुमतियाँ दें" relaunch = "Cap फिर से खोलें" @@ -1315,7 +1315,7 @@ check = "अपडेट जाँचें" download = "अपडेट डाउनलोड करें…" checking = "अपडेट जाँच रहे हैं…" downloading = "अपडेट डाउनलोड हो रहा है…" -restart = "अपडेट करने के लिए फिर से शुरू करें…" +restart = "अपडेट करने के लिए रीस्टार्ट करें…" [settings.general.updates.download_policy] auto = "स्वचालित" @@ -1328,11 +1328,11 @@ never = "कभी नहीं" sign_in_failed = "साइन इन विफल रहा" [diagnostics] -already_running = "एक डायग्नॉस्टिक पहले से चल रहा है।" +already_running = "एक डायग्नोस्टिक पहले से चल रहा है।" mode_both = "दोनों" [diagnostics.error] -create_folder = "डायग्नॉस्टिक फ़ोल्डर नहीं बन सका: {error}" +create_folder = "डायग्नोस्टिक फ़ोल्डर नहीं बन सका: {error}" exited = "सेल्फ़-टेस्ट {status} के साथ बंद हुआ" no_output = "सेल्फ़-टेस्ट से कोई आउटपुट स्ट्रीम नहीं मिली" serialize = "रिपोर्ट सीरियलाइज़ नहीं हो सकी: {error}" @@ -1364,7 +1364,7 @@ preparing_studio = "Studio टेस्ट की तैयारी हो र recording = "टेस्ट रिकॉर्ड हो रहा है..." recording_instant = "Instant टेस्ट रिकॉर्ड हो रहा है..." recording_studio = "Studio टेस्ट रिकॉर्ड हो रहा है..." -starting = "डायग्नॉस्टिक शुरू हो रहा है..." +starting = "डायग्नोस्टिक शुरू हो रहा है..." [dialog] no_microphone_body = "इस रिकॉर्डिंग में आपकी आवाज़ शामिल नहीं होगी।" @@ -1382,7 +1382,7 @@ cleanup_incomplete = "रिकॉर्डिंग की सफ़ाई अ devices_changed = "चुने गए डिवाइस बदल गए। नए रिकॉर्डिंग इनपुट की पुष्टि के लिए फिर से शुरू करें।" devices_changed_before_start = "रिकॉर्डिंग शुरू होने से पहले चुने गए डिवाइस बदल गए। इनपुट की पुष्टि के लिए फिर से शुरू करें।" shortcut_conflict = "Ctrl+Shift+F9 किसी दूसरे Cap एक्शन को दिया गया है। रिकॉर्ड करने से पहले वह शॉर्टकट बदलें।" -shortcut_description = "यह Cap रिकॉर्डिंग शुरू या बंद करें" +shortcut_description = "यह Cap रिकॉर्डिंग शुरू करें या रोकें" shortcut_hint = "शुरू करने के लिए {shortcut} दबाकर छोड़ें। रोकने के लिए वही शॉर्टकट इस्तेमाल करें। यह अस्थायी शॉर्टकट आपकी सेटिंग्स नहीं बदलता।" tray_stop_hint = "शुरू करने के लिए Cap Stop ट्रे आइकन सक्रिय करें। रोकने के लिए वही आइकन फिर से सक्रिय करें। जब तक आप उस कंट्रोल का इस्तेमाल नहीं करते, रिकॉर्डिंग शुरू नहीं होगी।" window_unavailable = "चुनी गई विंडो अब उपलब्ध नहीं है। इसे फिर से चुनें।" @@ -1439,10 +1439,10 @@ paused = "रिकॉर्डिंग के साथ स्क्रॉल placeholder = "अपनी स्क्रिप्ट पेस्ट करें या लिखें…" [import] -error_title = "इंपोर्ट त्रुटि" -image_failed = "इमेज इंपोर्ट नहीं हो सकी: {error}" +error_title = "इम्पोर्ट त्रुटि" +image_failed = "इमेज इम्पोर्ट नहीं हो सकी: {error}" unsupported = "यह मीडिया फ़ाइल समर्थित नहीं है।" -video_failed = "वीडियो इंपोर्ट नहीं हो सका: {error}" +video_failed = "वीडियो इम्पोर्ट नहीं हो सका: {error}" [import.filter] image = "इमेज फ़ाइलें" @@ -1451,9 +1451,9 @@ video = "वीडियो फ़ाइलें" [import.progress] analyzing = "वीडियो फ़ाइल का विश्लेषण हो रहा है..." -complete = "इंपोर्ट पूरा हुआ!" +complete = "इम्पोर्ट पूरा हुआ!" converting = "वीडियो कन्वर्ट हो रहा है... {percent}%" -image = "इमेज इंपोर्ट हो रही है..." +image = "इमेज इम्पोर्ट हो रही है..." metadata = "प्रोजेक्ट मेटाडेटा बन रहा है..." starting = "कन्वर्ज़न शुरू हो रहा है..." @@ -1530,7 +1530,7 @@ title = "चलिए Cap को तैयार करें" [permission.onboarding.hint] drag_into_list = "सिस्टम सेटिंग्स के नीचे वाले पैनल से Cap को लिस्ट में खींचें। पहले से लिस्ट में है? बस उसे चालू कर दें।" -find_in_list = "लिस्ट में Cap ढूँढें और उसे चालू करें। अगर macOS ने पहले पूछा था, तो ‘सिस्टम सेटिंग्स खोलें’ चुनें।" +find_in_list = "लिस्ट में Cap ढूँढें और उसे चालू करें। अगर macOS ने पहले पूछा था, तो “सिस्टम सेटिंग्स खोलें” चुनें।" no_prompt = "कोई प्रॉम्प्ट नहीं दिखा? सिस्टम सेटिंग्स में Cap चालू करें।" open_settings = "सिस्टम सेटिंग्स खोलें, लिस्ट में Cap ढूँढें और उसे चालू करें।" turned_off = "इसे पहले बंद कर दिया गया था, इसलिए सिस्टम सेटिंग्स में Cap चालू करें। या अभी के लिए इसे छोड़ दें।" @@ -1564,7 +1564,7 @@ required = "ज़रूरी" skipped = "अभी के लिए छोड़ा गया" [permission.onboarding.waiting] -prompt = "macOS प्रॉम्प्ट में ‘अनुमति दें’ चुनें" +prompt = "macOS प्रॉम्प्ट में “अनुमति दें” चुनें" system_settings = "सिस्टम सेटिंग्स का इंतज़ार है" [screenshot_editor] @@ -1578,14 +1578,14 @@ done = "हो गया" fill = "फ़िल" intensity = "तीव्रता" mask_style = "स्टाइल" -pixelate = "पिक्सेलेट" +pixelate = "पिक्सलेट" stroke = "स्ट्रोक" transparent = "पारदर्शी" [screenshot_editor.aspect] auto = "ऑटो" classic = "क्लासिक ⋅4:3" -square = "स्क्वेयर ⋅1:1" +square = "वर्ग ⋅1:1" tall = "लंबा ⋅3:4" vertical = "वर्टिकल ⋅9:16" wide = "वाइड ⋅16:9" @@ -1641,7 +1641,7 @@ reorder_hint = "क्रम बदलने के लिए खींचें title = "लेयर" [screenshot_editor.menu] -delete = "डिलीट करें" +delete = "हटाएँ" open_folder = "फ़ोल्डर खोलें" [screenshot_editor.share] @@ -1651,7 +1651,7 @@ rendering = "स्क्रीनशॉट रेंडर हो रहा ह uploading = "स्क्रीनशॉट अपलोड हो रहा है" [screenshot_editor.style] -advanced_shadow = "एडवांस्ड शैडो सेटिंग्स" +advanced_shadow = "छाया की एडवांस्ड सेटिंग्स" background = "बैकग्राउंड" blur = "ब्लर" border = "बॉर्डर" @@ -1660,7 +1660,7 @@ corner_rounding = "कोनों की गोलाई" opacity = "अपारदर्शिता" padding = "पैडिंग" rounding = "गोलाई" -shadow = "शैडो" +shadow = "छाया" size_label = "साइज़" width = "चौड़ाई" @@ -1690,14 +1690,14 @@ intel_mac = "Parakeet मॉडल Intel Mac पर उपलब्ध नही model_load = "कैप्शन मॉडल लोड नहीं हो सका। इसे फिर से डाउनलोड करके देखें" model_missing = "कैप्शन मॉडल नहीं मिला। कृपया पहले इसे डाउनलोड करें" no_audio = "वीडियो फ़ाइल में कोई ऑडियो नहीं मिला" -unsupported_language = "सुझाया गया और हाई एक्यूरेसी मॉडल सिर्फ़ अंग्रेज़ी और 24 दूसरी यूरोपीय भाषाएँ ट्रांसक्राइब करते हैं। इस भाषा के कैप्शन के लिए Small या Medium मॉडल डाउनलोड करें" +unsupported_language = "“सुझाया गया” और “उच्च सटीकता” मॉडल सिर्फ़ अंग्रेज़ी और 24 दूसरी यूरोपीय भाषाएँ ट्रांसक्राइब करते हैं। इस भाषा के कैप्शन के लिए “छोटा” या “मध्यम” मॉडल डाउनलोड करें" [upload.status] failed = "अपलोड पर ध्यान देने की ज़रूरत है" paused = "अपलोड रुका हुआ है" prepared = "अपलोड तैयार है" processing = "अपलोड प्रोसेस हो रहा है" -retrying = "अपलोड फिर से कोशिश करेगा" +retrying = "अपलोड फिर से किया जाएगा" sign_in = "अपलोड दोबारा करने के लिए साइन इन करें" uploaded = "अपलोड हो गया" uploading = "अपलोड हो रहा है" @@ -1710,7 +1710,7 @@ add_title = "ऑडियो जोड़ें" change_hint = "इस सेगमेंट के लिए कोई दूसरा ट्रैक चुनें" change_title = "ऑडियो बदलें" done = "हो गया" -import = "फ़ाइल इंपोर्ट करें" +import = "फ़ाइल इम्पोर्ट करें" [editor.color] apply_to_cursor = "कर्सर पर लागू करें" @@ -1831,7 +1831,7 @@ nothing_to_swap = "स्थिर शॉट में बदलने को play_shot = "चलाएँ" play_shot_tooltip = "यह शॉट चलाएँ" pose_end = "अंत · {time}" -pose_start = "शुरू · {time}" +pose_start = "शुरुआत · {time}" reset_camera = "कैमरा रीसेट करें" roll = "रोल" rotate_x = "घुमाव X" @@ -1858,7 +1858,7 @@ strength = "तीव्रता" ease_in = "ईज़ इन" ease_out = "ईज़ आउट" linear = "लीनियर" -smooth = "स्मूद" +smooth = "स्मूथ" [editor.panel.camera3d.focus_mode] directional = "दिशात्मक" @@ -2055,7 +2055,7 @@ title = "टेक्स्ट {number}" [editor.panel.timing] end = "अंत" -start = "शुरू" +start = "शुरुआत" title = "टाइमिंग" to = "से" @@ -2073,8 +2073,8 @@ positions_mixed = "सेगमेंट अलग-अलग जगहों प title = "ज़ूम {number}" [editor.sidebar] -desktop_import_failed = "आपका डेस्कटॉप वॉलपेपर इंपोर्ट नहीं हो सका" -importing_image = "इमेज इंपोर्ट हो रही है…" +desktop_import_failed = "आपका डेस्कटॉप वॉलपेपर इम्पोर्ट नहीं हो सका" +importing_image = "इमेज इम्पोर्ट हो रही है…" [editor.sidebar.animated_gradient] add_colour = "रंग जोड़ें" @@ -2087,7 +2087,7 @@ motion = "मोशन" name_placeholder = "इस ग्रेडिएंट का नाम रखें" name_required = "इस ग्रेडिएंट का नाम लिखें।" presets = "प्रीसेट" -randomize = "रैंडम" +randomize = "रैंडम करें" remember_failed = "आपकी एनिमेटेड ग्रेडिएंट सेटिंग्स याद नहीं रखी जा सकीं।" remove_colour = "रंग हटाएँ" reset_tooltip = "बारीक सेटिंग्स रीसेट करें" @@ -2151,10 +2151,10 @@ ripple_size = "आकार" strength = "तीव्रता" [editor.sidebar.desktop] -import = "डेस्कटॉप बैकग्राउंड इंपोर्ट करें" -importing = "इंपोर्ट हो रहा है..." +import = "डेस्कटॉप बैकग्राउंड इम्पोर्ट करें" +importing = "इम्पोर्ट हो रहा है..." prompt = "अपने डेस्कटॉप का वॉलपेपर इस्तेमाल करें" -reimport = "फिर से इंपोर्ट करें" +reimport = "फिर से इम्पोर्ट करें" [editor.sidebar.gradient] angle = "कोण" @@ -2223,7 +2223,7 @@ purple = "बैंगनी" [editor.canvas] camera = "कैमरा" camera_follows_zoom = "कैमरे का आकार ज़ूम के साथ बदलता है; हिलाने के लिए खींचें" -edit_zoom = "ज़ूम संपादित करें" +edit_zoom = "ज़ूम एडिट करें" image = "इमेज" mask = "मास्क" screen = "स्क्रीन" @@ -2256,7 +2256,7 @@ cancelled = "इम्पोर्ट रद्द किया गया" cannot_decode = "वीडियो {path} डिकोड नहीं हो सका: {error}" in_use = "यह रिकॉर्डिंग उपयोग में होने पर इम्पोर्ट नहीं हो सकता: {error}" instant_source = "Instant रिकॉर्डिंग अभी किसी प्रोजेक्ट में इम्पोर्ट नहीं की जा सकतीं" -instant_target = "Instant मोड की रिकॉर्डिंग संपादित नहीं की जा सकतीं" +instant_target = "Instant मोड की रिकॉर्डिंग एडिट नहीं की जा सकतीं" into_itself = "किसी रिकॉर्डिंग को उसी में इम्पोर्ट नहीं किया जा सकता" missing_video = "वीडियो फ़ाइल नहीं मिली: {path}" no_duration = "इम्पोर्ट किए गए वीडियो की कोई मान्य अवधि नहीं है" @@ -2276,8 +2276,8 @@ failed = "एक्सपोर्ट विफल रहा" fps = "{fps} FPS" frames = { one = "{rendered} / {count} फ़्रेम", other = "{rendered} / {count} फ़्रेम" } generating_preview = "प्रीव्यू बनाया जा रहा है…" -link_unavailable_cursor = "केवल कर्सर वाले एक्सपोर्ट सिर्फ़ फ़ाइल या क्लिपबोर्ड में सेव किए जा सकते हैं।" -link_unavailable_transparent = "पारदर्शी एक्सपोर्ट सिर्फ़ फ़ाइल या क्लिपबोर्ड में सेव किए जा सकते हैं।" +link_unavailable_cursor = "सिर्फ़ कर्सर वाले एक्सपोर्ट को फ़ाइल या क्लिपबोर्ड में ही सेव किया जा सकता है।" +link_unavailable_transparent = "पारदर्शी एक्सपोर्ट को फ़ाइल या क्लिपबोर्ड में ही सेव किया जा सकता है।" organization = "संगठन" preview = "प्रीव्यू" preview_info = "यह आपके वीडियो का रेंडर किया गया फ़्रेम है। फ़ाइनल एक्सपोर्ट की क्वालिटी देखने के लिए सेटिंग्स बदलें।" @@ -2288,12 +2288,12 @@ reupload_transparent = "शेयर लिंक के लिए बिना sign_in_required = "रिकॉर्डिंग शेयर करने के लिए आपको साइन इन करना होगा" task_failed = "एक्सपोर्ट कार्य विफल रहा" title = "एक्सपोर्ट" -upgrade_message = "इस सुविधा के लिए अपग्रेड किया गया प्लान चाहिए" +upgrade_message = "इस फ़ीचर के लिए अपग्रेड किया गया प्लान चाहिए" upgrade_title = "अपग्रेड ज़रूरी है" upload_failed = "रिकॉर्डिंग अपलोड नहीं हो सकी" [editor.export.advanced] -bits_per_pixel = "बिट्स प्रति पिक्सेल" +bits_per_pixel = "बिट्स प्रति पिक्सल" cursor_only = "केवल कर्सर एक्सपोर्ट करें" cursor_only_description = "पारदर्शी बैकग्राउंड पर वही कर्सर मूवमेंट और क्लिक रखता है" custom_bitrate = "कस्टम बिटरेट इस्तेमाल हो रहा है" @@ -2304,7 +2304,7 @@ transparent_mov = "पारदर्शी MOV के रूप में ए [editor.export.cta] cancel_sign_in = "साइन इन रद्द करें" -clipboard = "क्लिपबोर्ड में एक्सपोर्ट करें" +clipboard = "क्लिपबोर्ड पर एक्सपोर्ट करें" create_link = "शेयर करने लायक लिंक बनाएँ" file = "फ़ाइल में एक्सपोर्ट करें" reupload = "उसी लिंक पर दोबारा अपलोड करें" @@ -2344,10 +2344,10 @@ description = "बहुत छोटी फ़ाइलों के लिए title = "फ़ाइल साइज़ ऑप्टिमाइज़ करें" [editor.export.phase] -clipboard_failed = "एक्सपोर्ट पूरा हुआ; क्लिपबोर्ड में कॉपी विफल रही" +clipboard_failed = "एक्सपोर्ट पूरा हुआ; क्लिपबोर्ड पर कॉपी विफल रही" complete = "एक्सपोर्ट पूरा हुआ" -copied = "क्लिपबोर्ड में कॉपी हो गया" -copying_to_clipboard = "क्लिपबोर्ड में कॉपी हो रहा है" +copied = "क्लिपबोर्ड पर कॉपी हो गया" +copying_to_clipboard = "क्लिपबोर्ड पर कॉपी हो रहा है" rendering_cursor = "कर्सर ट्रैक रेंडर हो रहा है" rendering_gif = "GIF रेंडर हो रहा है" rendering_video = "वीडियो रेंडर हो रहा है" @@ -2372,7 +2372,7 @@ title = "एक्सपोर्ट संसाधन जाँचें" [editor.export.section] destination = "गंतव्य" -format = "फ़ॉर्मेट" +format = "फ़ॉर्मैट" frame_rate = "फ़्रेम रेट" quality = "क्वालिटी" resolution = "रेज़ोल्यूशन" @@ -2397,10 +2397,10 @@ recording_retained = "रिकॉर्डिंग रखी गई" [editor.tabs.audio] isolation_balanced = "संतुलित" -isolation_balanced_description = "आवाज़ के स्वाभाविक विवरण के साथ ज़्यादा साफ़ अलगाव।" +isolation_balanced_description = "आवाज़ की स्वाभाविक बारीकियों के साथ ज़्यादा साफ़ अलगाव।" isolation_light = "हल्का" isolation_light_description = "आपकी मूल आवाज़ और कमरे की ध्वनि ज़्यादा बनी रहती है।" -isolation_strong = "मज़बूत" +isolation_strong = "तेज़" isolation_strong_description = "शोर वाली जगहों के लिए ज़्यादा अलगाव। आवाज़ का टेक्सचर बदल सकता है।" mic_volume = "माइक्रोफ़ोन वॉल्यूम" mute = "ऑडियो म्यूट करें" @@ -2417,18 +2417,18 @@ mono_right = "मोनो R" stereo = "स्टीरियो" [editor.tabs.camera] -advanced_shadow = "एडवांस्ड शैडो सेटिंग्स" +advanced_shadow = "छाया की एडवांस्ड सेटिंग्स" background = "बैकग्राउंड" corner_style = "कोने की स्टाइल" -custom_position = "कस्टम स्थिति (कैनवस पर खींची गई)" +custom_position = "कस्टम पोज़िशन (कैनवास पर ड्रैग की गई)" hide = "कैमरा छिपाएँ" keep_size_during_zoom = "ज़ूम के दौरान मूल आकार बनाए रखें" mirror = "कैमरा मिरर करें" -position = "स्थिति" +position = "पोज़िशन" reset = "रीसेट करें" rounded_corners = "गोल कोने" -shadow = "शैडो" -shadow_blur = "धुंधलापन" +shadow = "छाया" +shadow_blur = "ब्लर" shadow_opacity = "अपारदर्शिता" shape_label = "आकृति" size_during_zoom = "ज़ूम के दौरान आकार" @@ -2461,27 +2461,27 @@ downloading = "{model} डाउनलोड हो रहा है... {percent fade_duration = "फ़ेड अवधि" font_family = "फ़ॉन्ट" font_settings = "फ़ॉन्ट सेटिंग्स" -font_weight = "फ़ॉन्ट वज़न" +font_weight = "फ़ॉन्ट वेट" generate = "कैप्शन बनाएँ" generate_failed = "कैप्शन नहीं बन सके: {reason}" generating = "बनाया जा रहा है..." highlight_color = "हाइलाइट रंग" highlight_note = "यह Cap में कैप्शन का पहला वर्ज़न है। कुछ स्थितियों में सक्रिय शब्द की हाइलाइटिंग गलत हो सकती है। हम इसे ठीक करने पर काम कर रहे हैं और यह आने वाले वर्ज़न में जारी होगा।" highlight_style = "हाइलाइट स्टाइल" -horizontal_position = "क्षैतिज स्थिति" +horizontal_position = "हॉरिज़ॉन्टल पोज़िशन" intel_unavailable = "Parakeet कैप्शन मॉडल Intel Mac पर उपलब्ध नहीं हैं। Whisper मॉडल उपलब्ध रहेंगे।" keep_open = "मॉडल डाउनलोड होने तक Cap खुला रखें। एडिटर दोबारा लोड होने पर अपने आप फिर से कनेक्ट हो जाएगा।" language_label = "भाषा" model_downloaded = "{model} मॉडल डाउनलोड हो गया" model_label = "मॉडल" -position = "स्थिति" +position = "पोज़िशन" regenerate = "कैप्शन दोबारा बनाएँ" size_label = "आकार" style = "स्टाइल" text_color = "टेक्स्ट रंग" title = "कैप्शन" uppercase = "बड़े अक्षर" -vertical_position = "ऊर्ध्वाधर स्थिति" +vertical_position = "वर्टिकल पोज़िशन" [editor.tabs.captions.animation] bounce = "बाउंस" @@ -2544,7 +2544,7 @@ pop_description = "चटक रंग के साथ उभरते मज [editor.tabs.corner] rounded = "गोल" -squircle = "स्क्विर्कल" +squircle = "स्क्वर्कल" [editor.tabs.cursor] friction = "घर्षण" @@ -2583,14 +2583,14 @@ empty_title = "अभी कोई कीबोर्ड सेगमेंट fade_duration = "फ़ेड अवधि" font_family = "फ़ॉन्ट" font_settings = "फ़ॉन्ट सेटिंग्स" -font_weight = "फ़ॉन्ट वज़न" +font_weight = "फ़ॉन्ट वेट" generate = "कीबोर्ड सेगमेंट बनाएँ" generating = "कीबोर्ड सेगमेंट बनाए जा रहे हैं..." grouping_threshold = "ग्रुपिंग सीमा" linger_duration = "दिखने की अवधि" load_failed = "रिकॉर्डिंग डेटा लोड नहीं हो सका: {error}" no_presses = "कोई रिकॉर्ड किया गया की-प्रेस नहीं मिला।" -position = "स्थिति" +position = "पोज़िशन" regenerate = "कीबोर्ड सेगमेंट दोबारा बनाएँ" show_modifiers = "मॉडिफ़ायर दिखाएँ" show_special_keys = "विशेष कुंजियाँ दिखाएँ" @@ -2605,7 +2605,7 @@ uppercase = "बड़े अक्षर" bottom_center = "नीचे बीच में" bottom_left = "नीचे बाएँ" bottom_right = "नीचे दाएँ" -manual = "मैन्युअल" +manual = "मैनुअल" top_center = "ऊपर बीच में" top_left = "ऊपर बाएँ" top_right = "ऊपर दाएँ" @@ -2622,7 +2622,7 @@ title = "सिंक" [editor.tabs.weight] bold = "बोल्ड" -medium = "मध्यम" +medium = "मीडियम" normal = "सामान्य" [editor.timeline] @@ -2648,8 +2648,8 @@ mask = "स्क्रीन के निजी हिस्सों को scene = "अपनी स्क्रीन और कैमरे के बीच लेआउट बदलें।" scene_unavailable = "सीन इस्तेमाल करने के लिए कैमरे के साथ रिकॉर्ड करें।" style = "समय के साथ बैकग्राउंड, कैमरा और कर्सर सेटिंग्स बदलें।" -text_overlay = "कैनवस पर कस्टम टेक्स्ट ओवरले और टाइटल जोड़ें।" -three_d = "सीन को 3D परिप्रेक्ष्य में झुकाएँ।" +text_overlay = "कैनवास पर कस्टम टेक्स्ट ओवरले और टाइटल जोड़ें।" +three_d = "सीन को 3D पर्सपेक्टिव में झुकाएँ।" zoom = "स्मूथ ज़ूम-इन जो एक्शन के साथ चलते हैं।" [editor.timeline.remove] @@ -2666,7 +2666,7 @@ three_d = "3D ट्रैक हटाएँ।" zoom = "ज़ूम ट्रैक हटाएँ।" [editor.timeline.scene] -camera_only = "केवल कैमरा" +camera_only = "सिर्फ़ कैमरा" default = "डिफ़ॉल्ट" floating = "फ़्लोटिंग" hide_camera = "कैमरा छिपाएँ" @@ -2680,7 +2680,7 @@ clip = "क्लिप" cursor_hidden = "कर्सर छिपा है" highlight = "हाइलाइट" label = "लेबल" -manual_zoom = "मैन्युअल ज़ूम" +manual_zoom = "मैनुअल ज़ूम" muted = "म्यूट" numbered_clip = "क्लिप {number}" sensitive = "संवेदनशील" @@ -2704,9 +2704,9 @@ audio_segment_changed = "यह ऑडियो सेगमेंट बदल crop = "क्रॉप" crop_tooltip = "वीडियो क्रॉप करें" no_room_for_3d_shot = "एक और 3D शॉट के लिए जगह नहीं है" -redo = "फिर से करें" +redo = "फिर करें" save_failed = "{path} सेव नहीं हो सका। एडिटर खुला रखें और फिर से कोशिश करें: {error}" -undo = "पूर्ववत करें" +undo = "पहले जैसा करें" [editor.window.add_track] button = "ट्रैक जोड़ें" @@ -2732,7 +2732,7 @@ copy_failed = "ऑडियो फ़ाइल कॉपी नहीं हो directory_failed = "ऑडियो फ़ोल्डर नहीं बन सका: {error}" no_extension = "ऑडियो फ़ाइल का कोई एक्सटेंशन नहीं है" not_found = "ऑडियो फ़ाइल नहीं मिली: {path}" -unsupported = "असमर्थित ऑडियो फ़ॉर्मेट: .{extension}" +unsupported = "असमर्थित ऑडियो फ़ॉर्मैट: .{extension}" [editor.window.busy] clip_import = "इस रिकॉर्डिंग को बंद करने या हटाने से पहले क्लिप इम्पोर्ट पूरा होने दें।" @@ -2807,7 +2807,7 @@ media_failed = "इस रिकॉर्डिंग का मीडिया meta_failed = "रिकॉर्डिंग मेटाडेटा लोड नहीं हो सका: {error}" no_segments = "रिकॉर्डिंग में कोई सेगमेंट नहीं है। हो सकता है पहले इसे रिकवर करना पड़े।" not_found = "वीडियो पाथ {path} नहीं मिला!" -not_studio = "Studio के अलावा की रिकॉर्डिंग संपादित नहीं की जा सकतीं" +not_studio = "Studio के अलावा दूसरी रिकॉर्डिंग एडिट नहीं की जा सकतीं" [editor.window.presets] apply = "लागू करें" @@ -2830,7 +2830,7 @@ set_default = "डिफ़ॉल्ट बनाएँ" full = "पूरा" half = "आधा" label = "प्रीव्यू" -quarter = "चौथाई" +quarter = "एक-चौथाई" [editor.window.text_menu] copy_style = "स्टाइल कॉपी करें" @@ -2896,15 +2896,15 @@ unconfirmed = "रिकॉर्डिंग बंद होने की प [dialog.update] already_getting = "Cap पहले से ही वर्ज़न {version} तैयार कर रहा है।" available = "Cap का वर्ज़न {version} उपलब्ध है। Cap इसे बैकग्राउंड में डाउनलोड करेगा और इंस्टॉल के लिए तैयार होने पर आपको बताएगा।" -busy = "Cap अपडेट करने से पहले अपनी रिकॉर्डिंग, एक्सपोर्ट, अपलोड, इंपोर्ट या ट्रांसक्रिप्शन पूरा करें।" +busy = "Cap अपडेट करने से पहले अपनी रिकॉर्डिंग, एक्सपोर्ट, अपलोड, इम्पोर्ट या ट्रांसक्रिप्शन पूरा करें।" busy_title = "Cap व्यस्त है" -check_failed = "अपडेट नहीं देखे जा सके: {error}" +check_failed = "अपडेट की जाँच नहीं हो सकी: {error}" download = "डाउनलोड करें" download_failed = "वर्ज़न {version} डाउनलोड नहीं हो सका: {error}" -failed = "{message}\n\nआप नया वर्ज़न कभी भी Cap.so/download से डाउनलोड कर सकते हैं।" +failed = "{message}\n\nआप सबसे नया वर्ज़न कभी भी Cap.so/download से डाउनलोड कर सकते हैं।" install_failed = "वर्ज़न {version} इंस्टॉल नहीं हो सका: {error}" later = "बाद में" -no_update = "आप पहले से ही Cap का नया वर्ज़न इस्तेमाल कर रहे हैं।" +no_update = "आप पहले से ही Cap का सबसे नया वर्ज़न इस्तेमाल कर रहे हैं।" no_update_title = "कोई अपडेट उपलब्ध नहीं है" not_now = "अभी नहीं" package_not_installed = "पैकेज इंस्टॉल नहीं हुआ। सिस्टम प्रॉम्प्ट को मंज़ूरी दें, या इसे Cap.so/download से इंस्टॉल करें।" diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 8b1e4173177..5ae44a227f3 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -31,7 +31,7 @@ commit = "コミット" [menu.file] open_prompt = "録画を開く" -close_window = "ウィンドウを閉じる" +close_window = "ウインドウを閉じる" title = "ファイル" open_file = "ファイルを開く…" open_recent = "最近使った項目を開く" @@ -51,9 +51,9 @@ enter_full_screen = "フルスクリーンにする" title = "表示" [menu.window] -close_window = "ウィンドウを閉じる" +close_window = "ウインドウを閉じる" minimize = "しまう" -title = "ウィンドウ" +title = "ウインドウ" zoom = "拡大/縮小" [menu.help] @@ -78,7 +78,7 @@ quit = "Capを終了" record_area = "範囲を録画" record_display = "ディスプレイを録画" record_window = "ウィンドウを録画" -request_permissions = "アクセス権を要求" +request_permissions = "権限を要求" screenshot_area = "範囲のスクリーンショット" screenshot_display = "ディスプレイのスクリーンショット" screenshot_window = "ウィンドウのスクリーンショット" @@ -161,8 +161,8 @@ description = "録画が始まったときのメインウィンドウの動作 label = "録画開始時のメインウィンドウ" [settings.general.recording.window_capture] -description = "ウインドウ録画で選んだウインドウをどのようにキャプチャするか。「フォーカスしたウインドウ」はそのウインドウだけを中央に配置し、移動しても追従して録画します。「画面の範囲」はウインドウが覆う画面の範囲を録画します。" -label = "ウインドウのキャプチャ方法" +description = "ウィンドウ録画で選んだウィンドウをどのようにキャプチャするか。「フォーカスしたウィンドウ」はそのウィンドウだけを中央に配置し、移動しても追従して録画します。「画面の範囲」はウィンドウが覆う画面の範囲を録画します。" +label = "ウィンドウのキャプチャ方法" [settings.general.recording.post_deletion] description = "録画ウィンドウをもう一度開くかどうか。" @@ -220,20 +220,20 @@ title = "デフォルトのプロジェクト名" [settings.general.project_name.placeholders] date_time = "日付と時刻" date_values = "録画の日付" -mode_values = "\"studio\"、\"instant\"、\"screenshot\" のいずれか" +mode_values = "\"studio\"、\"instant\"、\"screenshot\"のいずれか" random = "ランダム" -random_values = "ファイル名に使える 16 進数 10 文字。1–32 文字にするには {{random:N}} を使用します。" +random_values = "ファイル名に使える16進数10文字。1–32文字にするには{{random:N}}を使用します。" recording_mode = "録画モード" -recording_mode_values = "\"Studio\"、\"Instant\"、\"Screenshot\" のいずれか" +recording_mode_values = "\"Studio\"、\"Instant\"、\"Screenshot\"のいずれか" target = "対象" -target_kind_values = "\"Display\"、\"Window\"、\"Area\" のいずれか" +target_kind_values = "\"Display\"、\"Window\"、\"Area\"のいずれか" target_name_values = "モニター名またはウィンドウのタイトル。" time_values = "録画の時刻" [settings.general.excluded_windows] description = "録画に映さないウィンドウ。" empty = "現在、除外されているウィンドウはありません。" -missing_body = "カメラ、設定、録画のウィンドウが画面録画に黒い枠として映ることがあります。除外されていないウィンドウ: {names}。" +missing_body = "カメラ、設定、録画のウィンドウが画面録画に黒い四角形として映ることがあります。除外されていないウィンドウ:{names}。" missing_title = "推奨されるCapのウィンドウが除外されていません" restore = "元に戻す" title = "除外するウィンドウ" @@ -357,7 +357,7 @@ close = "閉じる" minimise = "最小化" [settings.options.window_capture] -focused_window = "フォーカスしたウインドウ" +focused_window = "フォーカスしたウィンドウ" screen_region = "画面の範囲" [settings.options.post_studio] @@ -518,9 +518,9 @@ install = "CLIをインストール" installed = "インストール済み" installing = "インストール中..." installs_command = "デスクトップアプリは、同梱のCLIを指すローカルの\u00A0{command}\u00A0コマンドをインストールします。" -load_failed = "CLIの状態を読み込めませんでした: {error}" +load_failed = "CLIの状態を読み込めませんでした:{error}" not_installed = "未インストール" -path_configured = "capをPATHに追加しました。使うにはターミナルを再起動するか、今すぐ次を実行してください:" +path_configured = "capをPATHに追加しました。使うにはターミナルを再起動するか、今すぐ次を実行してください:" path_missing = "新しいターミナルでcapを使うには、{path}をPATHに追加してください。" remove = "削除" removing = "削除中..." @@ -554,11 +554,11 @@ reliability = "信頼性" [settings.experimental.switch] busy = "従来のアプリに切り替える前に、録画、書き出し、アップロード、読み込み、文字起こしの作業を完了してください。" -check_failed = "従来のCapアプリが開いたかどうか確認できませんでした: {error}" +check_failed = "従来のCapアプリが開いたかどうか確認できませんでした:{error}" data_stays = "録画と設定はそのまま残ります。" failed = "従来のCapアプリを開けませんでした。" not_found = "切り替え先の従来のCapアプリが見つかりませんでした。" -open_failed = "従来のCapアプリを開けませんでした: {error}" +open_failed = "従来のCapアプリを開けませんでした:{error}" opening = "従来のCapアプリを開いています" opening_detail = "従来のCapアプリの準備ができると、このウィンドウは閉じます。" save_failed = "アプリの設定を保存できませんでした。Capは開いたままです。" @@ -568,8 +568,8 @@ timed_out = "従来のアプリが開きません。開発用ターミナルで [settings.feedback] app_version = "アプリのバージョン" capture_support = "キャプチャ対応" -capture_supported = "画面キャプチャ: 対応" -capture_unsupported = "画面キャプチャ: 非対応" +capture_supported = "画面キャプチャ:対応" +capture_unsupported = "画面キャプチャ:非対応" community_description = "質問やアイデアの共有、ちょっとした雑談に。CapのDiscordコミュニティに参加しましょう。" community_title = "コミュニティに参加" debug_description = "ログをアップロードして、Capの問題の診断にご協力ください。個人情報は含まれません。" @@ -596,7 +596,7 @@ confirm_title = "診断を実行しますか?" description = "点滅とビープ音の短いテストパターンを録画し、映像と音声のずれを測定して、ディスプレイ、カメラ、マイク、ディスク容量、録画設定の状態を記録します。送信を選ぶまで、何も送信されません。" mic_description = "マイクも録音し、ビープ音を正しいタイミングで拾えているか確認します。マイクに実際に聞こえるスピーカーが必要です。" mic_label = "マイクをテスト" -mode_description = "テストする処理を選びます。両方を選ぶとStudioを実行してからInstantを実行します。" +mode_description = "テストする処理を選びます。「両方」を選ぶと、Studioモードの後にInstantモードを実行します。" mode_label = "録画モード" no_cli = "このビルドの横にCapコマンドラインツールが見つからないため、レポートには環境情報のみが含まれます。" no_cli_sync = "このビルドの横にCapコマンドラインツールが見つからないため、同期テストを実行できませんでした。" @@ -650,8 +650,8 @@ opens_pricing = "ブラウザでcap.so/pricingを開きます。" placeholder = "ライセンスキー" plan_title = "ご利用中のプラン" pro_description = "お使いのアカウントには、クラウド共有、Pro機能、商用利用できるデスクトップライセンスが含まれます。" -refresh = "プランを更新" -refresh_failed = "プランを更新できませんでした。もう一度お試しください。" +refresh = "プラン情報を更新" +refresh_failed = "プラン情報を更新できませんでした。もう一度お試しください。" save_failed = "ライセンスを保存できませんでした" sign_in_hint = "すでにCap Proをお持ちですか?サイドバーからアカウントでサインインしてください。" title = "プランとライセンス" @@ -660,9 +660,9 @@ view_pricing = "プランと料金を見る ↗" [settings.integrations] configure = "設定" description = "連携を設定して、Capの機能を拡張し、外部サービスと接続します。" -google_drive_description = "Google Driveを接続すると、新しい共有リンクの動画がアップロードされます。Capは新しい動画をDrive内の非公開のCapフォルダに保存し、通常のアクセス確認の後にCap経由で配信し続けます。" +google_drive_description = "Google Driveを接続して、新しい共有リンクのアップロード先として使用します。Capは新しい動画をDrive内の非公開のCapフォルダに保存し、通常のアクセス確認を行ったうえでCap経由で配信します。" managed = "組織が管理しています" -managed_by = "組織が管理しています: {organization}" +managed_by = "組織が管理しています:{organization}" request_timed_out = "リクエストがタイムアウトしました" s3_description = "独自のS3バケットを接続して、データの保存を完全に管理できます。新しい共有リンクのアップロードはすべて設定したS3バケットに自動でアップロードされ、コンテンツの所有権と管理を完全に保てます。データ主権や独自の保存ポリシーが必要な組織に最適です。" s3_name = "S3設定" @@ -735,7 +735,7 @@ add_condition = "条件を追加" auto_name_empty = "{trigger}のオートメーション" browse = "参照" check_compatibility = "互換性を確認" -compatibility_checked = "互換性を確認しました: このオートメーションはまだCapでは実行されません。保存したルールはCap CLIで引き続き使えます。アクションは実行されていません。" +compatibility_checked = "互換性を確認しました:このオートメーションはまだCapでは実行されません。保存したルールはCap CLIで引き続き使えます。アクションは実行されていません。" condition_never_matches = "この条件は選択したトリガーでは一致しません。" copy_link = "リンクをクリップボードにコピー" dangerous = "Cap CLIでは、このオートメーションはあなたの権限でコマンドを実行したりネットワークリクエストを送信したりします。信頼できる値だけを使ってください。" @@ -760,7 +760,7 @@ templates_description = "ワンクリックで既製のオートメーション templates_title = "テンプレート" then = "実行する内容" title = "オートメーション" -when = "このとき" +when = "実行するタイミング" [settings.automations.status] disabled_saved = "無効 · 保存済み" @@ -790,7 +790,7 @@ url = "URL" [settings.automations.placeholder] organization_id = "組織ID" project_folder = "プロジェクトフォルダ" -window_title = "例: Slack" +window_title = "例:Slack" [settings.automations.clipboard] original = "元のキャプチャ" @@ -802,9 +802,9 @@ display = "ディスプレイ" window = "ウィンドウ" [settings.automations.compression] -maximum = "最大" -potato = "最小" -social = "ソーシャル" +maximum = "最高" +potato = "最低" +social = "SNS" web = "Web" [settings.automations.trigger.screenshot_taken] @@ -996,12 +996,12 @@ open_system_settings = "システム設定を開く" turn_on_in_settings = "設定でオンにする" [main.device.format] -apply_failed = "デバイスのフォーマットを適用できませんでした: {error}" +apply_failed = "デバイスのフォーマットを適用できませんでした:{error}" applying = "フォーマットを適用中..." -available = "利用可能: {format}" +available = "利用可能:{format}" camera_changed = "カメラが変更されたか、接続が切れました。フォーマット設定を開き直してください。" channels = { other = "{count}チャンネル" } -current = "現在: {format}" +current = "現在:{format}" default = "デフォルト" loading = "フォーマットを読み込み中..." mono = "モノラル" @@ -1118,7 +1118,7 @@ waiting = "ブラウザを待っています" [main.storage] blocked = "録画用ドライブの空き容量は{available} GBしかありません。録画する前に、512 MB以上の空きを確保してください。" can_start = "録画用ドライブの空き容量は{available} GBしかありません。容量が足りなくなるとCapは録画を保存して自動的に停止します。" -check_failed = "録画用ストレージを確認できませんでした: {error}" +check_failed = "録画用ストレージを確認できませんでした:{error}" record_anyway = "このまま録画" title = "空き容量が不足しています" @@ -1177,7 +1177,7 @@ mirror_title = "反転を使用できません" access_message = "クリックして、システム設定でCapにカメラの使用を許可してください。" access_title = "カメラへのアクセスが必要です" generic = "選択したカメラを起動できませんでした。別のカメラを選ぶか、このカメラを接続し直してください。" -invalid_format = "このカメラは使用可能なキャプチャフォーマットを報告しませんでした。" +invalid_format = "このカメラから使用可能なキャプチャフォーマットを取得できませんでした。" not_found = "このカメラは使用できなくなりました。接続されていて、システムの権限で許可されているか確認してください。" start_failed = "システムがこのカメラを起動できませんでした。使用できないか、ほかのアプリで使用中の可能性があります。" timeout = "このカメラから映像が届きませんでした。閉じている、接続が切れている、覆われている、またはほかのアプリで使用中の可能性があります。" @@ -1250,7 +1250,7 @@ answer = "Instantモードでは、録画を停止すると自動的に共有リ question = "共有はどのように行いますか?" [onboarding.faq.shortcuts] -answer = "いつでも 設定 → ショートカット から、すべてのキーボードショートカットをカスタマイズできます。" +answer = "いつでも「設定」→「ショートカット」から、すべてのキーボードショートカットをカスタマイズできます。" question = "ショートカットはあとで変更できますか?" [onboarding.faq.storage] @@ -1303,7 +1303,7 @@ subtitle = "Capのメインウィンドウから、ワンクリックでモー title = "モードはいつでも切り替え可能" [onboarding.welcome] -click_or_press = "ここをクリックするか、次のキーを押す:" +click_or_press = "ここをクリックするか、次のキーを押す:" get_started = "はじめる" space_key = "スペース" tagline = "誰もが使えるオープンソースの画面共有アプリ。" @@ -1315,13 +1315,13 @@ check = "アップデートを確認" download = "アップデートをダウンロード…" checking = "アップデートを確認中…" downloading = "アップデートをダウンロード中…" -restart = "アップデートのために再起動…" +restart = "再起動してアップデート…" [settings.general.updates.download_policy] auto = "自動" -description = "Cap がアップデートを自動でダウンロードするか選択します。" +description = "Capがアップデートを自動でダウンロードするか選択します。" label = "アップデートのダウンロード" -manual = "確認してから" +manual = "事前に確認" never = "しない" [auth] @@ -1336,11 +1336,11 @@ create_folder = "診断フォルダを作成できませんでした:{error}" exited = "セルフテストが終了しました:{status}" no_output = "セルフテストから出力が得られませんでした" serialize = "レポートをシリアライズできませんでした:{error}" -start_failed = "{program} を起動できませんでした:{error}" +start_failed = "{program}を起動できませんでした:{error}" upload_logs = "ログをアップロードできませんでした:{error}" upload_status = "アップロードに失敗しました。ステータス:{status}" wait_failed = "セルフテストの終了を待てませんでした:{error}" -write_failed = "{path} に書き込めませんでした:{error}" +write_failed = "{path}に書き込めませんでした:{error}" [diagnostics.stage] analyzing = "録画を分析中..." @@ -1371,20 +1371,20 @@ no_microphone_body = "この録画にはあなたの声は入りません。" open_recording_failed = "この録画を開けませんでした:{error}" [dialog.capture] -approve_shortcut = "録画用のショートカットを承認するか、Cap の停止トレイアイコンが表示されたらそれをクリックしてください。録画はまだ始まっていません。" +approve_shortcut = "録画用のショートカットを承認するか、Capの停止トレイアイコンが表示されたらそれをクリックしてください。録画はまだ始まっていません。" area_unavailable = "選択した範囲が変更されたか、使用できなくなりました。もう一度選択してください。" -camera_needs_x11 = "Linux で Instant のカメラオーバーレイを使うには X11 デスクトップが必要です" -camera_notice = "Cap はカメラのプレビューと録画コントロールを非表示にします。選択したカメラは、個別に編集できるトラックとして録画され続けます。" -camera_notice_instant = "Cap はプレビューとコントロールを非表示にします。選択したカメラは、選んだ形とエフェクトでプレビューの位置に表示されます。プレビュー全体をキャプチャ範囲内に収めてください。Cap を開くと、コントロールを表示する前にこの Instant 録画が停止します。" -camera_outside_area = "Instant 録画を始める前に、カメラのプレビュー全体をキャプチャ範囲内に移動してください" -camera_preview_closed = "Instant 録画の前に、選択したカメラのプレビューを開いてください" +camera_needs_x11 = "LinuxでInstantのカメラオーバーレイを使うにはX11デスクトップが必要です" +camera_notice = "Capはカメラのプレビューと録画コントロールを非表示にします。選択したカメラは、個別に編集できるトラックとして録画され続けます。" +camera_notice_instant = "Capはプレビューとコントロールを非表示にします。選択したカメラは、選んだ形とエフェクトでプレビューの位置に表示されます。プレビュー全体をキャプチャ範囲内に収めてください。Capを開くと、コントロールを表示する前にこのInstant録画が停止します。" +camera_outside_area = "Instant録画を始める前に、カメラのプレビュー全体をキャプチャ範囲内に移動してください" +camera_preview_closed = "Instant録画の前に、選択したカメラのプレビューを開いてください" cleanup_incomplete = "録画のクリーンアップが完了していません:{error}。新しい録画は開始できません。" devices_changed = "選択したデバイスが変更されました。新しい録画の入力を確認するため、もう一度開始してください。" devices_changed_before_start = "録画の開始前に選択したデバイスが変更されました。入力を確認するため、もう一度開始してください。" -shortcut_conflict = "Ctrl+Shift+F9 は Cap の別の操作に割り当てられています。録画の前にそのショートカットを変更してください。" -shortcut_description = "この Cap 録画を開始または停止" -shortcut_hint = "{shortcut} を押して離すと開始します。同じショートカットで停止できます。この一時的なショートカットは設定を変更しません。" -tray_stop_hint = "Cap の停止トレイアイコンをクリックすると開始します。同じアイコンをもう一度クリックすると停止します。このコントロールを使うまで録画は始まりません。" +shortcut_conflict = "Ctrl+Shift+F9はCapの別の操作に割り当てられています。録画の前にそのショートカットを変更してください。" +shortcut_description = "このCap録画を開始または停止" +shortcut_hint = "{shortcut}を押して離すと開始します。同じショートカットで停止できます。この一時的なショートカットは設定を変更しません。" +tray_stop_hint = "Capの停止トレイアイコンをクリックすると開始します。同じアイコンをもう一度クリックすると停止します。このコントロールを使うまで録画は始まりません。" window_unavailable = "選択したウィンドウは使用できなくなりました。もう一度選択してください。" [dialog.clipboard] @@ -1403,7 +1403,7 @@ editor_changed = "削除の確認中にエディタが変更されました。 editor_closed = "エディタはすでに閉じられています。録画は削除されていません。" failed = "録画を削除できませんでした:{error}" failed_title = "録画を削除できませんでした" -finishing_body = "Cap は録画のクリーンアップが終わるのを待ってから、この削除を完了します。" +finishing_body = "Capは録画のクリーンアップが終わるのを待ってから、この削除を完了します。" finishing_title = "削除はまだ完了していません" in_use = "この録画はまだ使用中です。アップロードを完了またはキャンセルしてから、もう一度お試しください:{error}" outside_library = "この録画は録画ライブラリの外にあります。ファイルは変更されていません。" @@ -1411,13 +1411,13 @@ recording_active = "このプロジェクトを削除する前に、録画を終 retained_title = "録画は残されています" [dialog.file] -audio_filter = "オーディオ" +audio_filter = "音声" busy = "ファイルダイアログがすでに開いています。完了するかキャンセルしてから、もう一度お試しください。" export_filter = "書き出し" failed = "ファイルダイアログを完了できませんでした:{error}" images_filter = "画像" unavailable_title = "ファイルダイアログを使用できません" -unconfirmed = "Cap はファイルダイアログが閉じたことを確認できませんでした。残っているファイルダイアログを閉じ、Cap を再起動してからもう一度お試しください。" +unconfirmed = "Capはファイルダイアログが閉じたことを確認できませんでした。残っているファイルダイアログを閉じ、Capを再起動してからもう一度お試しください。" [dialog.mode_select] instant = "リンクですぐに共有。録画しながらアップロードします。" @@ -1433,9 +1433,9 @@ stopping = "ストレージが不足しています。録画を停止して保 [dialog.teleprompter] cue_markers = "キューマーカー" -hidden = "このウィンドウは Cap の録画に映りません" +hidden = "このウィンドウはCapの録画に映りません" mirror = "テキストを反転" -paused = "録画とともにスクロールを一時停止中" +paused = "録画に合わせてスクロールを一時停止中" placeholder = "台本を貼り付けるか入力してください…" [import] @@ -1460,14 +1460,14 @@ starting = "変換を開始しています..." [overlay] choose_mode = "録画モードを選択" countdown_tooltip = "録画のカウントダウン" -display_fallback = "ディスプレイ {id}" +display_fallback = "ディスプレイ{id}" [overlay.area] draw = "範囲を描く" free_ratio = "自由" hint = "クリックしてドラッグし、範囲を選択" -minimum_size = "最小サイズは {width} x {height} です" -too_small = "{width} x {height} は小さすぎます" +minimum_size = "最小サイズは{width} x {height}です" +too_small = "{width} x {height}は小さすぎます" [overlay.camera_only] description = "カメラとマイクだけで録画します" @@ -1490,7 +1490,7 @@ take_screenshot = "スクリーンショットを撮る" [permission.guide] accessibility_body = "上の「アクセシビリティ」のリストにドロップし、スイッチがオンになっていることを確認してください。" screen_body = "上の「画面収録とシステムオーディオ録音」のリストにドロップし、スイッチがオンになっていることを確認してください。" -title = "Cap をリストにドラッグ" +title = "Capをリストにドラッグ" [permission.name] accessibility = "アクセシビリティ" @@ -1501,18 +1501,18 @@ screen_recording = "画面収録" [permission.onboarding] open_settings_again = "システム設定をもう一度開く" skip = "今はスキップ" -switch_on_title = "システム設定で Cap をオンにする" +switch_on_title = "システム設定でCapをオンにする" [permission.onboarding.accessibility] allow = "アクセシビリティを許可" -body = "アクセシビリティを許可すると、Cap がこの Mac 上だけでクリックとカーソルを把握し、自動ズームがどこを映すべきかわかるようになります。" +body = "アクセシビリティを許可すると、CapがこのMac上だけでクリックとカーソルを把握し、自動ズームがどこを映すべきかわかるようになります。" done_body = "自動ズームがクリックを追えるようになりました。" done_title = "アクセシビリティはオンです" -title = "Cap がカーソルを追えるようにする" +title = "Capがカーソルを追えるようにする" [permission.onboarding.all_set] -body = "Cap に必要なものはすべてそろいました。次は、できることをざっと見てみましょう。" -regained = "ありがとうございます。Cap で再び録画できます。" +body = "Capに必要なものはすべてそろいました。次は、できることをざっと見てみましょう。" +regained = "ありがとうございます。Capで再び録画できます。" title = "準備完了です" [permission.onboarding.camera] @@ -1520,41 +1520,41 @@ allow = "カメラを許可" body = "録画の隅に自分を映せます。必要なければスキップしてください。" done_body = "どの録画にもカメラを追加できます。" done_title = "カメラはオンです" -title = "Cap にあなたを映させる" +title = "Capがあなたを映せるようにする" [permission.onboarding.checklist] -lede = "最初に macOS がいくつかの許可を求めます。1分ほどで終わります。" +lede = "最初にmacOSがいくつかの許可を求めます。1分ほどで終わります。" regained_lede = "システム設定でオフにされた項目があります。録画を続けるにはオンに戻してください。" -regained_title = "Cap に権限を戻してください" -title = "Cap の準備をしましょう" +regained_title = "Capの権限をもう一度オンにしてください" +title = "Capの準備をしましょう" [permission.onboarding.hint] -drag_into_list = "システム設定の下にあるパネルから Cap をリストにドラッグしてください。すでにリストにある場合は、オンにするだけです。" -find_in_list = "リストで Cap を見つけてオンにしてください。macOS から先に確認された場合は、「システム設定を開く」を選んでください。" -no_prompt = "確認ダイアログが表示されませんか?システム設定で Cap をオンにしてください。" -open_settings = "システム設定を開き、リストで Cap を見つけてオンにしてください。" -turned_off = "以前オフにされているため、システム設定で Cap をオンにしてください。今はスキップすることもできます。" +drag_into_list = "システム設定の下にあるパネルからCapをリストにドラッグしてください。すでにリストにある場合は、オンにするだけです。" +find_in_list = "リストでCapを見つけてオンにしてください。macOSから先に確認された場合は、「システム設定を開く」を選んでください。" +no_prompt = "確認ダイアログが表示されませんか?システム設定でCapをオンにしてください。" +open_settings = "システム設定を開き、リストでCapを見つけてオンにしてください。" +turned_off = "以前オフにしたため、システム設定でCapをオンにしてください。今はスキップすることもできます。" [permission.onboarding.microphone] allow = "マイクを許可" body = "録画に声を入れられます。スキップして後でオンにすることもできます。" done_body = "録画に声を入れられるようになりました。" done_title = "マイクはオンです" -title = "Cap に声を届ける" +title = "Capがあなたの声を聞けるようにする" [permission.onboarding.relaunch] -body = "Cap を再起動すると macOS が画面収録をオンにします。この画面から続けられます。" +body = "Capを再起動するとmacOSが画面収録をオンにします。この画面から続けられます。" button = "再起動" -hint = "オンにしたのにまだ待機中ですか?Cap を再起動すると反映されます。" -later = "macOS は Cap の再起動後にこれを反映します。ここでの設定が終わってから再起動できます。" +hint = "オンにしたのにまだ待機中ですか?Capを再起動すると反映されます。" +later = "macOSはCapの再起動後にこれを反映します。ここでの設定が終わってから再起動できます。" title = "再起動すれば準備完了です" [permission.onboarding.screen] allow = "画面収録を許可" -body = "画面収録を許可すると、Cap がディスプレイ、ウィンドウ、システムオーディオをキャプチャできます。" -done_body = "Cap で画面をキャプチャできるようになりました。" +body = "画面収録を許可すると、Capがディスプレイ、ウィンドウ、システムオーディオをキャプチャできます。" +done_body = "Capで画面をキャプチャできるようになりました。" done_title = "画面収録はオンです" -title = "Cap に画面を見せる" +title = "Capが画面を見られるようにする" [permission.onboarding.status] allowed = "許可済み" @@ -1564,7 +1564,7 @@ required = "必須" skipped = "今はスキップ" [permission.onboarding.waiting] -prompt = "macOS の確認ダイアログで「許可」を選択" +prompt = "macOSの確認ダイアログで「許可」を選択" system_settings = "システム設定を待っています" [screenshot_editor] @@ -1578,7 +1578,7 @@ done = "完了" fill = "塗りつぶし" intensity = "強さ" mask_style = "スタイル" -pixelate = "ピクセル化" +pixelate = "モザイク" stroke = "線" transparent = "透明" @@ -1615,15 +1615,15 @@ position = "位置" encode = "スクリーンショットの書き出しをエンコードできませんでした:{error}" load_meta = "スクリーンショットのメタデータを読み込めませんでした:{error}" load_metadata = "スクリーンショットのメタデータを読み込めませんでした:{error}" -no_image = "{path} に PNG が見つかりません" +no_image = "{path}にPNGが見つかりません" not_screenshot = "スクリーンショットのバンドルではありません" open_image = "画像を開けませんでした:{error}" -renderer_failed = "GPU レンダラーを起動できませんでした:{error}" +renderer_failed = "GPUレンダラーを起動できませんでした:{error}" renderer_not_running = "スクリーンショットのレンダラーが動作していません" renderer_stopped = "書き出しが終わる前にレンダラーが停止しました" save_project = "プロジェクトの保存中にエラーが発生しました:{error}" serialize = "スクリーンショットの設定をシリアライズできませんでした:{error}" -sign_in_to_share = "ブラウザで Cap にサインインしてから、もう一度共有してください" +sign_in_to_share = "ブラウザでCapにサインインしてから、もう一度共有してください" too_large = "画像サイズが上限を超えています:{width}x{height}" upgrade_required = "この機能を使うにはプランのアップグレードが必要です" upload_task = "アップロード処理に失敗しました:{error}" @@ -1651,7 +1651,7 @@ rendering = "スクリーンショットを描画中" uploading = "スクリーンショットをアップロード中" [screenshot_editor.style] -advanced_shadow = "シャドウの詳細設定" +advanced_shadow = "影の詳細設定" background = "背景" blur = "ぼかし" border = "枠線" @@ -1660,7 +1660,7 @@ corner_rounding = "角の丸み" opacity = "不透明度" padding = "余白" rounding = "角の丸み" -shadow = "シャドウ" +shadow = "影" size_label = "サイズ" width = "幅" @@ -1681,12 +1681,12 @@ text_label = "テキスト" [transcription.download] complete = "ダウンロード完了" failed = "ダウンロードに失敗しました:{error}" -file_progress = "{file} をダウンロード中:{percent}%" +file_progress = "{file}をダウンロード中:{percent}%" preparing = "モデルのダウンロードを準備中" progress = "モデルをダウンロード中:{percent}%" [transcription.error] -intel_mac = "Parakeet モデルは Intel Mac では使えません。代わりに Whisper モデルを使ってください" +intel_mac = "ParakeetモデルはIntel搭載のMacでは使えません。代わりにWhisperモデルを使ってください" model_load = "字幕モデルを読み込めませんでした。もう一度ダウンロードしてください" model_missing = "字幕モデルが見つかりません。先にダウンロードしてください" no_audio = "動画ファイルに音声がありません" @@ -1705,10 +1705,10 @@ uploading = "アップロード中" # Editor sidebar, segment panels, colour correction, audio library and crop dialog. [editor.audio.library] -add_hint = "動画にオーディオや音楽などのサウンドを追加" -add_title = "オーディオを追加" +add_hint = "動画に音声や音楽などのサウンドを追加" +add_title = "音声を追加" change_hint = "このセグメントに別のトラックを選ぶ" -change_title = "オーディオを変更" +change_title = "音声を変更" done = "完了" import = "ファイルを読み込む" @@ -1762,18 +1762,18 @@ saturation = "彩度" split_tone = "スプリットトーン" strength = "強さ" temperature = "色温度" -tint = "色かぶり" +tint = "色合い" vignette = "周辺減光" [editor.crop] -aspect_ratio = "縦横比" +aspect_ratio = "アスペクト比" center = "中央" dimensions = "サイズ" editing_style = "スタイル{number}のみを編集中" editing_style_override = "スタイル{number}のみを編集中 · 保存すると背景の上書きがオンになります" free = "自由" full = "全体" -hint = "ドラッグで移動 · 矢印キーで微調整 · {key}を押したままでスナップしない" +hint = "ドラッグで移動 · 矢印キーで微調整 · {key}を押したままでスナップを無効化" loading_frame = "フレームを読み込み中…" position = "位置" preview = "プレビュー" @@ -1797,11 +1797,11 @@ opacity = "不透明度" [editor.panel.audio] change = "変更" -change_hint = "タップしてトラックを変更" +change_hint = "クリックしてトラックを変更" fade_in = "フェードイン" fade_out = "フェードアウト" -fallback_name = "オーディオ" -title = "オーディオ{number}" +fallback_name = "音声" +title = "音声{number}" volume = "音量" [editor.panel.camera3d] @@ -1876,7 +1876,7 @@ pull_back = "引き" rising_sweep = "上昇スイープ" slide_by = "スライド" spotlight = "スポットライト" -tilt_away = "傾けて離す" +tilt_away = "傾けながら離れる" top_down = "真上から" unfold = "展開" @@ -1911,7 +1911,7 @@ semibold = "やや太字" arrange = "キャンバス上で配置" arrange_help = "画像をドラッグして移動します。角を引っ張るとサイズを変更でき、下の回転コントロールで回転できます。" center = "キャンバスの中央に配置" -lock_aspect = "縦横比を固定" +lock_aspect = "アスペクト比を固定" missing = "画像ファイルが見つかりません。置き換えると、タイミングと変形を保ったままこのセグメントを復元できます。" replace = "画像を置き換え" rotation = "回転" @@ -1949,7 +1949,7 @@ split_screen = "画面とカメラを横に並べて表示(縦向きでは自 camera_only = "カメラのみ" default = "デフォルト" floating = "フローティング" -hide_camera = "カメラを隠す" +hide_camera = "カメラを非表示" split_screen = "分割画面" [editor.panel.style] @@ -2146,7 +2146,7 @@ description_tahoe = "macOS Tahoeの見た目です。カスタムカーソルは description_windows = "Windowsの見た目です。カスタムカーソルは録画時の形のままです。" duration = "長さ" recorded = "録画したカーソル" -recorded_tooltip = "このカーソルで録画" +recorded_tooltip = "このカーソルで録画されました" ripple_size = "サイズ" strength = "強さ" @@ -2164,7 +2164,7 @@ noise = "ノイズ" to = "終了" [editor.sidebar.image] -prompt = "クリックして選択するか画像をドラッグ&ドロップ" +prompt = "クリックして選択するか、画像をドラッグ&ドロップ" [editor.sidebar.layout] background_blur = "背景のぼかし" @@ -2206,7 +2206,7 @@ editing_note = "このセグメントのみを編集しています。全体設 title = "スタイル{number} · {group}" [editor.sidebar.tab] -audio = "オーディオ" +audio = "音声" background = "背景" camera = "カメラ" captions = "字幕" @@ -2242,9 +2242,9 @@ import = "読み込む" import_error = "クリップを読み込めませんでした:{message}" import_error_title = "読み込みエラー" import_existing = "既存の録画" -import_mp4 = "MP4ビデオ…" +import_mp4 = "MP4動画…" importing = "読み込み中…" -mp4_filter = "MP4ビデオ" +mp4_filter = "MP4動画" prepare_controls_failed = "録画コントロールを準備できませんでした:{error}" record_new = "新しいクリップを録画" save_before_recording_failed = "録画前に現在のプロジェクトを保存できませんでした:{error}" @@ -2253,24 +2253,24 @@ title = "クリップ" [editor.clips.error] cancelled = "読み込みをキャンセルしました" -cannot_decode = "ビデオ{path}をデコードできません:{error}" +cannot_decode = "動画{path}をデコードできません:{error}" in_use = "この録画の使用中は読み込めません:{error}" instant_source = "Instant録画はまだプロジェクトに読み込めません" instant_target = "Instantモードの録画は編集できません" into_itself = "録画をそれ自体に読み込むことはできません" -missing_video = "ビデオファイルがありません:{path}" -no_duration = "読み込んだビデオに有効な長さがありません" +missing_video = "動画ファイルがありません:{path}" +no_duration = "読み込んだ動画に有効な長さがありません" not_a_project = "Capのプロジェクトフォルダを選択してください" source_empty = "読み込み元のCapプロジェクトに録画セグメントがありません" source_failed = "読み込み元のCapプロジェクトでエラーが発生しました:{error}" source_recording = "読み込み元のCapプロジェクトはまだ録画中です" source_unimportable = "読み込み元のCapプロジェクトに読み込める録画セグメントがありません" unfinished_target = "クリップを読み込む前に、この録画を完了するか復元してください" -unsupported_video = "対応していないビデオファイルです:{path}" +unsupported_video = "対応していない動画ファイルです:{path}" [editor.export] back = "エディタに戻る" -existing_link_description = "再アップロードすると、このリンクのビデオが最新の編集内容に置き換わります。リンクを知っている全員に更新版が表示されます。" +existing_link_description = "再アップロードすると、このリンクの動画が最新の編集内容に置き換わります。リンクを知っている全員に更新版が表示されます。" existing_link_title = "既存のリンクを更新" failed = "書き出しに失敗しました" fps = "{fps} FPS" @@ -2280,7 +2280,7 @@ link_unavailable_cursor = "カーソルのみの書き出しは、ファイル link_unavailable_transparent = "透過の書き出しは、ファイルかクリップボードにのみ保存できます。" organization = "組織" preview = "プレビュー" -preview_info = "ビデオからレンダリングしたフレームです。設定を調整すると、最終的な書き出しの品質を確認できます。" +preview_info = "動画からレンダリングしたフレームです。設定を調整すると、最終的な書き出しの品質を確認できます。" preview_unavailable = "プレビューを表示できません" reupload = "再アップロード" reupload_tooltip = "最新の編集内容を同じリンクにアップロード" @@ -2295,12 +2295,12 @@ upload_failed = "録画をアップロードできませんでした" [editor.export.advanced] bits_per_pixel = "ピクセルあたりのビット数" cursor_only = "カーソルのみを書き出す" -cursor_only_description = "同じカーソルの動きとクリックを透過背景の上に保持します" +cursor_only_description = "カーソルの動きとクリックはそのままに、透過背景で書き出します" custom_bitrate = "カスタムビットレートを使用中" force_ffmpeg = "FFmpegデコーダを強制" force_ffmpeg_description = "ハードウェアデコーダを使わない(自動フォールバック有効)" title = "詳細設定" -transparent_mov = "透過MOVとして書き出します。ファイルが大きいため、合成や編集に適しています。" +transparent_mov = "透過MOVとして書き出します。ファイルサイズは大きくなりますが、合成や編集に最適です。" [editor.export.cta] cancel_sign_in = "サインインをキャンセル" @@ -2323,7 +2323,7 @@ open_file = "ファイルを開く" open_link = "リンクを開く" retry_copy = "コピーを再試行" reuploaded = "最新の編集内容が同じリンクで利用できます" -uploaded = "Capのアップロードが完了しました" +uploaded = "録画のアップロードが完了しました" [editor.export.estimate] gigabytes = "約{value} GB" @@ -2350,7 +2350,7 @@ copied = "クリップボードにコピーしました" copying_to_clipboard = "クリップボードにコピー中" rendering_cursor = "カーソルトラックをレンダリング中" rendering_gif = "GIFをレンダリング中" -rendering_video = "ビデオをレンダリング中" +rendering_video = "動画をレンダリング中" reupload_complete = "再アップロードが完了しました" reuploading = "リンクに再アップロード中" saving_to_file = "ファイルに保存中" @@ -2412,8 +2412,8 @@ title = "音声コントロール" use_for_new = "新しい録画で使用" [editor.tabs.audio.stereo] -mono_left = "モノラル L" -mono_right = "モノラル R" +mono_left = "モノラル(左)" +mono_right = "モノラル(右)" stereo = "ステレオ" [editor.tabs.camera] @@ -2469,7 +2469,7 @@ highlight_color = "ハイライトの色" highlight_note = "これはCapの字幕機能の最初のバージョンです。話している単語のハイライトは、状況によって不正確になる場合があります。修正に取り組んでおり、今後のバージョンで公開する予定です。" highlight_style = "ハイライトのスタイル" horizontal_position = "水平位置" -intel_unavailable = "IntelのMacではParakeetの字幕モデルを利用できません。Whisperモデルは引き続き利用できます。" +intel_unavailable = "Intel搭載のMacではParakeetの字幕モデルを利用できません。Whisperモデルは引き続き利用できます。" keep_open = "モデルのダウンロード中はCapを開いたままにしてください。エディタを再読み込みしても自動的に再接続されます。" language_label = "言語" model_downloaded = "{model}モデルをダウンロード済み" @@ -2528,7 +2528,7 @@ medium_description = "低速だがより正確" recommended = "推奨" recommended_description = "ほとんどの録画に最適なバランス" small = "小" -small_description = "最小のダウンロード" +small_description = "ダウンロードサイズが最小" [editor.tabs.captions.preset] classic = "クラシック" @@ -2611,7 +2611,7 @@ top_left = "左上" top_right = "右上" [editor.tabs.sync] -auto_description = "Capは音声をビデオと同期させるため、これらのオフセットを自動で計算しました。まだずれて聞こえる場合は調整してください。" +auto_description = "Capは音声を動画と同期させるため、これらのオフセットを自動で計算しました。まだずれて聞こえる場合は調整してください。" auto_synced = "自動同期済み" camera = "カメラのオフセット" clip = "クリップ{number}" @@ -2695,14 +2695,14 @@ mask = "マスク" scene = "シーン" style = "スタイル" text_track = "テキスト" -video = "ビデオ" +video = "動画" zoom = "ズーム" [editor.window] audio_filter = "音声" audio_segment_changed = "この音声セグメントは変更されました。選択して、もう一度置き換えるファイルを選んでください。" crop = "切り抜き" -crop_tooltip = "ビデオを切り抜く" +crop_tooltip = "動画を切り抜く" no_room_for_3d_shot = "別の3Dショットを入れる余地がありません" redo = "やり直す" save_failed = "{path}を保存できませんでした。エディタを開いたまま、もう一度お試しください:{error}" @@ -2775,13 +2775,13 @@ dark = "ダーク" global = "グローバルフレーム · スタイルが背景を継承する箇所すべてに適用されます。" heading = "フレーム" light = "ライト" -macbook_description = "録画の周りにノートパソコンのベゼル" +macbook_description = "録画を囲むノートパソコンのベゼル" macos_description = "信号ボタン付きのウィンドウ枠" none = "なし" none_description = "録画をそのまま表示" style_changed = "このスタイルは変更されました。フレームを閉じて開き直してください。" -style_only = "スタイル{index}のみ · グローバル設定は変わりません。" -style_only_override = "スタイル{index}のみ · 編集すると独自の背景設定が有効になります。" +style_only = "スタイル{index}のみ · 全体設定は変わりません。" +style_only_override = "スタイル{index}のみ · 編集すると背景の上書きが有効になります。" theme = "テーマ" title = "タイトル" url = "URL" @@ -2801,12 +2801,12 @@ filter = "画像" recording_unavailable = "この画像の録画は利用できなくなりました。プロジェクトを開き直してください。" [editor.window.open] -damaged = "この録画のビデオトラックを開けませんでした。バンドルが破損しているようです。" +damaged = "この録画の動画トラックを開けませんでした。バンドルが破損しているようです。" failed = "この録画を開けませんでした" media_failed = "この録画のメディアを読み込めませんでした:{error}" meta_failed = "録画のメタデータを読み込めませんでした:{error}" no_segments = "録画にセグメントがありません。先に復元が必要な可能性があります。" -not_found = "ビデオのパス{path}が見つかりません!" +not_found = "動画のパス{path}が見つかりません!" not_studio = "Studio以外の録画は編集できません" [editor.window.presets] @@ -2854,7 +2854,7 @@ delete = "削除" [editor.window.transport] done_splitting = "分割を終了" -play_pause = "ビデオを再生/一時停止" +play_pause = "動画を再生/一時停止" split = "分割" split_tooltip = "セグメントをクリックして分割します。Escで終了します。" zoom_in = "ズームイン" diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index 599fb98a013..d7cfd598d91 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -4,7 +4,7 @@ back = "返回" cancel = "取消" choose_allow_prompt = "请在 macOS 提示中选择“允许”。" close = "关闭" -copied = "已拷贝" +copied = "已复制" dismiss = "关闭" go_back = "返回" loading = "正在加载..." @@ -109,7 +109,7 @@ quality = "录制质量" recordings = "录制" screenshots = "截图" shortcuts = "快捷键" -transcription = "转录" +transcription = "转写" [settings.general.language] description = "选择 Cap 的显示语言。" @@ -133,7 +133,7 @@ description = "即使没有打开的窗口,也在程序坞中保留 Cap。" label = "始终显示程序坞图标" [settings.general.app.notifications] -description = "在拷贝到剪贴板、保存文件等操作后显示通知。你可能需要在系统的通知设置中允许 Cap。" +description = "在复制到剪贴板、保存文件等操作后显示通知。你可能需要在系统的通知设置中允许 Cap。" label = "系统通知" [settings.general.recording] @@ -220,13 +220,13 @@ title = "默认项目名称" [settings.general.project_name.placeholders] date_time = "日期和时间" date_values = "录制日期" -mode_values = "\"studio\"、\"instant\" 或 \"screenshot\"" +mode_values = "“studio”、“instant” 或 “screenshot”" random = "随机" random_values = "10 个可用于文件名的十六进制字符;使用 {{random:N}} 指定 1–32 个。" recording_mode = "录制模式" -recording_mode_values = "\"Studio\"、\"Instant\" 或 \"Screenshot\"" +recording_mode_values = "“Studio”、“Instant” 或 “Screenshot”" target = "目标" -target_kind_values = "\"Display\"、\"Window\" 或 \"Area\"" +target_kind_values = "“Display”、“Window” 或 “Area”" target_name_values = "显示器名称或窗口标题。" time_values = "录制时间" @@ -234,7 +234,7 @@ time_values = "录制时间" description = "在录制中隐藏窗口。" empty = "当前没有排除任何窗口。" missing_body = "摄像头、设置或录制窗口可能会在屏幕录制中显示为黑框。未排除:{names}。" -missing_title = "推荐排除的 Cap 窗口未被排除" +missing_title = "部分推荐排除的 Cap 窗口尚未排除" restore = "恢复" title = "排除的窗口" @@ -263,7 +263,7 @@ title = "隐私" description = "Cap 使用匿名遥测数据来提升稳定性并修复问题。我们绝不会收集录制内容、窗口标题、文件路径或任何个人信息。" label = "共享匿名遥测数据" [settings.prompt] -ok = "好" +ok = "确定" [settings.sidebar] account = "账户" @@ -291,7 +291,7 @@ failed = "录制失败" import = "导入" in_progress = "正在录制" load_more = "加载更多" -open_bundle = "打开录制文件包" +open_bundle = "打开录制包" open_link = "打开链接" retry_upload = "重新上传" show_all = "全部显示" @@ -376,9 +376,9 @@ do_nothing = "不执行任何操作" reopen = "重新打开录制窗口" [settings.options.preview_quality] -full = "完整" -half = "一半" -quarter = "四分之一" +full = "全尺寸" +half = "1/2" +quarter = "1/4" [settings.shortcuts] blocked_while_recording = "当前录制占用了停止键,无法更改快捷键。请先停止录制。" @@ -406,7 +406,7 @@ hint = "停止录制时" title = "Studio 录制之后" [settings.shortcuts.after_instant] -hint = "分享链接总会被复制" +hint = "始终会复制分享链接" title = "Instant 录制之后" [settings.shortcuts.outcome] @@ -416,7 +416,7 @@ instant_open_link = "打开链接" instant_open_link_hint = "在浏览器中" instant_show_cap = "显示 Cap" instant_show_cap_hint = "回到录制器" -saved_to_library = "保存到资料库" +saved_to_library = "仅保存到资料库" screenshot_copy = "复制" screenshot_copy_and_edit = "复制并编辑" screenshot_copy_and_edit_hint = "两者兼顾" @@ -461,7 +461,7 @@ picker_display_studio = "选择一个屏幕,然后以 Studio 模式录制" picker_window_instant = "选择一个窗口,然后以 Instant 模式录制" picker_window_screenshot = "选择一个窗口,然后截图" picker_window_studio = "选择一个窗口,然后以 Studio 模式录制" -restart = "丢弃当前这段并重新开始" +restart = "丢弃当前录制并重新开始" screenshot_area_copy = "选择一个区域并复制到剪贴板" screenshot_area_copy_and_open_editor = "选择一个区域,复制后打开编辑器" screenshot_area_open_editor = "选择一个区域并在编辑器中打开" @@ -470,10 +470,10 @@ screenshot_display_copy = "截取指针所在的屏幕并复制到剪贴板" screenshot_display_copy_and_open_editor = "截取指针所在的屏幕,复制后打开编辑器" screenshot_display_open_editor = "截取指针所在的屏幕并在编辑器中打开" screenshot_display_save = "截取指针所在的屏幕并保存到你的资料库,不打开任何内容" -screenshot_window_copy = "截取指针下方的窗口并复制到剪贴板" -screenshot_window_copy_and_open_editor = "截取指针下方的窗口,复制后打开编辑器" -screenshot_window_open_editor = "截取指针下方的窗口并在编辑器中打开" -screenshot_window_save = "截取指针下方的窗口并保存到你的资料库,不打开任何内容" +screenshot_window_copy = "截取指针所在的窗口并复制到剪贴板" +screenshot_window_copy_and_open_editor = "截取指针所在的窗口,复制后打开编辑器" +screenshot_window_open_editor = "截取指针所在的窗口并在编辑器中打开" +screenshot_window_save = "截取指针所在的窗口并保存到你的资料库,不打开任何内容" stop = "停止当前录制" studio = "以 Studio 模式录制所选屏幕或窗口" studio_open_editor = "以 Studio 模式录制所选屏幕或窗口,停止后打开编辑器" @@ -493,7 +493,7 @@ picker_display_studio = "选择一个屏幕,然后以 Studio 模式录制。" picker_window_instant = "选择一个窗口,然后以 Instant 模式录制。" picker_window_screenshot = "选择一个窗口,然后截图。" picker_window_studio = "选择一个窗口,然后以 Studio 模式录制。" -restart = "丢弃当前这段并重新开始。" +restart = "丢弃当前录制并重新开始。" screenshot_area_copy = "选择一个区域并复制到剪贴板。" screenshot_area_copy_and_open_editor = "选择一个区域,复制后打开编辑器。" screenshot_area_open_editor = "选择一个区域并在编辑器中打开。" @@ -502,10 +502,10 @@ screenshot_display_copy = "截取指针所在的屏幕并复制到剪贴板。" screenshot_display_copy_and_open_editor = "截取指针所在的屏幕,复制后打开编辑器。" screenshot_display_open_editor = "截取指针所在的屏幕并在编辑器中打开。" screenshot_display_save = "截取指针所在的屏幕并保存到你的资料库,不打开任何内容。" -screenshot_window_copy = "截取指针下方的窗口并复制到剪贴板。" -screenshot_window_copy_and_open_editor = "截取指针下方的窗口,复制后打开编辑器。" -screenshot_window_open_editor = "截取指针下方的窗口并在编辑器中打开。" -screenshot_window_save = "截取指针下方的窗口并保存到你的资料库,不打开任何内容。" +screenshot_window_copy = "截取指针所在的窗口并复制到剪贴板。" +screenshot_window_copy_and_open_editor = "截取指针所在的窗口,复制后打开编辑器。" +screenshot_window_open_editor = "截取指针所在的窗口并在编辑器中打开。" +screenshot_window_save = "截取指针所在的窗口并保存到你的资料库,不打开任何内容。" stop = "停止当前录制。" studio = "以 Studio 模式录制所选屏幕或窗口。" studio_open_editor = "以 Studio 模式录制所选屏幕或窗口,停止后打开编辑器。" @@ -591,10 +591,10 @@ upload_sent = "已发送。谢谢,这能帮助我们找到问题。" uploading = "正在上传..." [settings.feedback.diagnostic] -confirm_body = "Cap 将以闪烁图样占据你的屏幕,并在每条处理流程中播放约 {seconds} 秒的响亮提示音。请摘下耳机,保持音量可闻,并在完成前不要操作电脑。" +confirm_body = "Cap 将以闪烁图案占据你的屏幕并播放响亮的提示音,每条处理流程约 {seconds} 秒。请摘下耳机,不要静音,并在完成前不要操作电脑。" confirm_title = "运行诊断?" -description = "录制一段闪烁并发出提示音的简短测试图样,测量画面与声音的偏移程度,并记录你的显示器、摄像头、麦克风、磁盘空间和录制设置。在你选择发送之前,不会发送任何内容。" -mic_description = "同时录制你的麦克风,检查它是否及时听到提示音。需要麦克风能实际听到的扬声器。" +description = "录制一段闪烁并发出提示音的简短测试图案,测量画面与声音的偏移程度,并记录你的显示器、摄像头、麦克风、磁盘空间和录制设置。在你选择发送之前,不会发送任何内容。" +mic_description = "同时录制你的麦克风,检查它是否及时听到提示音。扬声器的声音需要能被麦克风实际收到。" mic_label = "测试麦克风" mode_description = "要测试的处理流程。“两者”会先运行 Studio 部分,再运行 Instant 部分。" mode_label = "录制模式" @@ -803,7 +803,7 @@ window = "窗口" [settings.automations.compression] maximum = "最高" -potato = "极低画质" +potato = "最低" social = "社交" web = "网页" @@ -1019,7 +1019,7 @@ on = "开" [main.error] camera_only_needs_camera = "仅摄像头录制需要先选择摄像头。" -copy = "拷贝错误信息" +copy = "复制错误信息" devices_not_ready = "录制设备尚未就绪。请打开录制器后重试。" title = "出了点问题" @@ -1033,7 +1033,7 @@ teleprompter = "提词器" [main.library] clips = { other = "{count} 个片段" } -copy_screenshot_failed = "无法拷贝截图" +copy_screenshot_failed = "无法复制截图" import_recordings = "导入" import_screenshots = "导入图片" importing = "正在导入" @@ -1206,9 +1206,9 @@ title = "键盘快捷键" [onboarding.demo] background = "背景" -copied = "已拷贝!" -copied_to_clipboard = "已拷贝到剪贴板" -copy = "拷贝" +copied = "已复制!" +copied_to_clipboard = "已复制到剪贴板" +copy = "复制" editor = "Cap 编辑器" export = "导出" export_complete = "导出完成!" @@ -1232,13 +1232,13 @@ save_progress = "无法保存引导进度。请重试。" save_sound = "无法保存声音偏好设置。" [onboarding.faq] -pricing = "查看价格方案" -pricing_link = "查看价格方案 ↗" +pricing = "查看套餐价格" +pricing_link = "查看套餐价格 ↗" subtitle = "开始使用前需要了解的一切。" title = "常见问题" [onboarding.faq.free] -answer = "Cap 个人使用免费。团队和商业用途请查看我们的价格方案。" +answer = "Cap 个人使用免费。团队和商业用途请查看我们的付费套餐。" question = "Cap 可以免费使用吗?" [onboarding.faq.instant_vs_studio] @@ -1250,7 +1250,7 @@ answer = "在 Instant 模式下,停止录制时会自动生成分享链接。 question = "如何分享?" [onboarding.faq.shortcuts] -answer = "随时前往 设置 → 快捷键,自定义所有键盘快捷键。" +answer = "随时前往“设置 → 快捷键”,自定义所有键盘快捷键。" question = "以后可以更改快捷键吗?" [onboarding.faq.storage] @@ -1261,17 +1261,17 @@ question = "我的录制保存在哪里?" description = "边录制边上传。停止录制即可立刻获得分享链接,无需等待。" feature_1 = "即时分享链接" feature_2 = "后台上传" -feature_3 = "AI 转录与摘要" +feature_3 = "AI 转写与摘要" feature_4 = "浏览器播放" tagline = "几秒内录制并分享" title = "Instant 模式" [onboarding.modes.screenshot] -description = "一个快捷键即可截图,添加标注和漂亮的背景,然后立即分享或拷贝。" +description = "一个快捷键即可截图,添加标注和漂亮的背景,然后立即分享或复制。" feature_1 = "快捷键一键截图" feature_2 = "标注与绘图工具" feature_3 = "漂亮的背景" -feature_4 = "拷贝、保存或分享" +feature_4 = "复制、保存或分享" tagline = "即时截图与美化" title = "截图模式" @@ -1371,7 +1371,7 @@ no_microphone_body = "此录制将不包含你的声音。" open_recording_failed = "无法打开此录制:{error}" [dialog.capture] -approve_shortcut = "请批准一个录制快捷键,或在 Cap 停止托盘图标出现时点按它。录制尚未开始。" +approve_shortcut = "请批准一个录制快捷键,或在 Cap 停止托盘图标出现时点击它。录制尚未开始。" area_unavailable = "所选区域已变化或不再可用。请重新选择。" camera_needs_x11 = "在 Linux 上,Instant 摄像头叠加层需要 X11 桌面" camera_notice = "Cap 将隐藏摄像头预览和录制控件。所选摄像头会继续作为单独的可编辑轨道录制。" @@ -1384,7 +1384,7 @@ devices_changed_before_start = "录制开始前所选设备已更改。请重新 shortcut_conflict = "Ctrl+Shift+F9 已分配给另一个 Cap 操作。请在录制前更改该快捷键。" shortcut_description = "开始或停止此 Cap 录制" shortcut_hint = "按下并松开 {shortcut} 开始录制。再次使用同一快捷键即可停止。此临时快捷键不会更改你的设置。" -tray_stop_hint = "点按 Cap 停止托盘图标即可开始。再次点按同一图标即可停止。在你使用该控件之前,录制不会开始。" +tray_stop_hint = "点击 Cap 的“停止”托盘图标即可开始。再次点击同一图标即可停止。在你使用该控件之前,录制不会开始。" window_unavailable = "所选窗口已不可用。请重新选择。" [dialog.clipboard] @@ -1524,9 +1524,9 @@ title = "让 Cap 看到你" [permission.onboarding.checklist] lede = "macOS 会先请你允许几项权限,只需一分钟。" -regained_lede = "系统设置中有项权限被关闭了。请重新开启以继续录制。" +regained_lede = "系统设置中有一项权限被关闭了。请重新开启以继续录制。" regained_title = "Cap 需要重新获得一项权限" -title = "让我们准备好 Cap" +title = "先来设置 Cap" [permission.onboarding.hint] drag_into_list = "将 Cap 从系统设置下方的面板拖到列表中。已经在列表里了?直接打开即可。" @@ -1658,7 +1658,7 @@ border = "边框" color = "颜色" corner_rounding = "圆角" opacity = "不透明度" -padding = "内边距" +padding = "边距" rounding = "圆角" shadow = "阴影" size_label = "大小" @@ -1695,7 +1695,7 @@ unsupported_language = "“推荐”和“高精度”模型只能转写英语 [upload.status] failed = "上传需要处理" paused = "上传已暂停" -prepared = "上传已准备" +prepared = "已准备上传" processing = "正在处理上传" retrying = "将重试上传" sign_in = "登录以重试上传" @@ -1707,7 +1707,7 @@ uploading = "正在上传" [editor.audio.library] add_hint = "为视频添加音频、音乐或其他声音" add_title = "添加音频" -change_hint = "为此片段选择其他音轨" +change_hint = "为此分段选择其他音轨" change_title = "更换音频" done = "完成" import = "导入文件" @@ -1789,7 +1789,7 @@ duration = "时长" edge_in = "入场" edge_out = "出场" enabled = "已启用" -fade_duration = "淡化时长" +fade_duration = "淡入淡出时长" flip_horizontally = "水平翻转" flip_vertically = "垂直翻转" name = "名称" @@ -1797,7 +1797,7 @@ opacity = "不透明度" [editor.panel.audio] change = "更换" -change_hint = "点按以更换音轨" +change_hint = "点击以更换音轨" fade_in = "淡入" fade_out = "淡出" fallback_name = "音频" @@ -1823,7 +1823,7 @@ fine_tune = "微调" focus = "焦点" header = "3D 镜头 · {look} · {duration} 秒" hold_opening = "整个镜头保持起始姿态" -lens = "焦距" +lens = "视角" look_title = "取景" motion_style = "运动方式" moves = "运镜" @@ -1842,7 +1842,7 @@ shots = { other = "{count} 个镜头" } still_shot = "静止镜头" swap = "交换开始和结束" timing_advanced = "时间与高级" -timing_summary = "{easing} · 焦距 {fov}" +timing_summary = "{easing} · 视角 {fov}" [editor.panel.camera3d.blur] angle = "角度" @@ -1912,7 +1912,7 @@ arrange = "在画布上排列" arrange_help = "拖动图片即可移动。拖动角可调整大小,或使用下方的旋转控件进行旋转。" center = "在画布上居中" lock_aspect = "锁定宽高比" -missing = "图片文件缺失。替换图片即可恢复此片段,并保留其时间和变换。" +missing = "图片文件缺失。替换图片即可恢复此分段,并保留其时间和变换。" replace = "替换图片" rotation = "旋转" rounding = "圆角" @@ -1948,7 +1948,7 @@ split_screen = "屏幕和摄像头并排显示(竖屏时自动上下排列)" [editor.panel.scene.mode] camera_only = "仅摄像头" default = "默认" -floating = "浮动" +floating = "悬浮" hide_camera = "隐藏摄像头" split_screen = "分屏" @@ -1964,7 +1964,7 @@ crop_width = "宽度" edit_background = "编辑背景和边框" edit_camera = "编辑摄像头" edit_cursor = "编辑光标" -help = "覆盖设置仅在此片段期间生效。启用一个分组即可复制其全局设置。" +help = "覆盖设置仅在此分段期间生效。启用一个分组即可复制其全局设置。" reset_crop = "重置裁剪" [editor.panel.text_animation] @@ -2064,7 +2064,7 @@ amount = "缩放倍数" auto = "自动" auto_help = "自动缩放会跟随光标在屏幕上移动。" auto_locked = "自动模式需要捕获光标。请在“设置 → 通用”中开启“单独捕获光标(Studio)”。" -how_it_works = "这是如何工作的?" +how_it_works = "它是如何运作的?" manual = "手动" manual_help = "手动缩放会固定在你在下方选择的位置。" mixed = "不一致" @@ -2110,7 +2110,7 @@ highlights = "高光" motion_speed = "运动速度" relief = "浮雕" ripple_size = "波纹大小" -shading = "阴影" +shading = "明暗" vibrance = "自然饱和度" [editor.sidebar.animated_gradient.group] @@ -2202,7 +2202,7 @@ back = "返回样式" background = "背景和边框" camera = "摄像头" cursor = "光标" -editing_note = "仅编辑此片段。全局设置不变。" +editing_note = "仅编辑此分段。全局设置不变。" title = "样式 {number} · {group}" [editor.sidebar.tab] @@ -2276,7 +2276,7 @@ failed = "导出失败" fps = "{fps} FPS" frames = { other = "{rendered} / {count} 帧" } generating_preview = "正在生成预览…" -link_unavailable_cursor = "仅光标的导出只能保存到文件或剪贴板。" +link_unavailable_cursor = "仅导出光标时,只能保存到文件或剪贴板。" link_unavailable_transparent = "透明导出只能保存到文件或剪贴板。" organization = "组织" preview = "预览" @@ -2323,7 +2323,7 @@ open_file = "打开文件" open_link = "打开链接" retry_copy = "重新复制" reuploaded = "你最新的编辑已在同一链接上就绪" -uploaded = "你的 Cap 已成功上传" +uploaded = "你的录制已成功上传" [editor.export.estimate] gigabytes = "约 {value} GB" @@ -2384,8 +2384,8 @@ estimated_size = "预计大小" export_time = "导出用时" output = "输出" output_value = "{width}×{height} · {fps} fps" -refining_size = "正在细化大小…" -refining_time = "正在细化用时…" +refining_size = "正在精确估算大小…" +refining_time = "正在精确估算用时…" unavailable = "不可用" [editor.modal] @@ -2455,9 +2455,9 @@ background_settings = "背景设置" beta = "Beta" delete = "删除" deleting = "正在删除..." -download = "下载{model}模型" +download = "下载“{model}”模型" download_note = "只需下载到你的系统一次。所有字幕都保存在本地。" -downloading = "正在下载{model}... {percent}%" +downloading = "正在下载“{model}”模型... {percent}%" fade_duration = "淡入淡出时长" font_family = "字体" font_settings = "字体设置" @@ -2472,7 +2472,7 @@ horizontal_position = "水平位置" intel_unavailable = "Parakeet 字幕模型在 Intel Mac 上不可用。Whisper 模型仍可使用。" keep_open = "下载模型时请保持 Cap 打开。编辑器重新加载后会自动重新连接。" language_label = "语言" -model_downloaded = "已下载{model}模型" +model_downloaded = "已下载“{model}”模型" model_label = "模型" position = "位置" regenerate = "重新生成字幕" @@ -2643,7 +2643,7 @@ audio = "添加背景音乐或导入你自己的音频。" caption = "自动转写你的录制,生成屏幕字幕。" clip = "你录制的屏幕画面。" image = "为你的录制添加图片。" -keyboard = "在你输入时在屏幕上显示按键。" +keyboard = "输入时在屏幕上显示按键。" mask = "模糊或涂黑屏幕上的隐私区域。" scene = "在屏幕和摄像头之间切换布局。" scene_unavailable = "使用摄像头录制后才能使用场景。" @@ -2683,7 +2683,7 @@ label = "标签" manual_zoom = "手动缩放" muted = "已静音" numbered_clip = "片段 {number}" -sensitive = "敏感" +sensitive = "敏感内容" [editor.timeline.track] audio = "音频" @@ -2719,8 +2719,8 @@ classic = "经典" classic_ratio = "经典 ⋅4:3" square = "方形" square_ratio = "方形 ⋅1:1" -tall = "竖长" -tall_ratio = "竖长 ⋅3:4" +tall = "竖版" +tall_ratio = "竖版 ⋅3:4" tooltip = "宽高比" vertical = "竖屏" vertical_ratio = "竖屏 ⋅9:16" @@ -2889,9 +2889,9 @@ zoom_some = { other = "已选择 {count}/{total} 个" } [dialog.quit] canceled = "已取消退出:{error}。Cap 将保持打开,以便你完成或恢复录制。" -pending = "录制仍在停止中,已取消退出。Cap 将保持打开。请在可用时点按“停止”,然后再次尝试退出。" +pending = "录制仍在停止中,已取消退出。Cap 将保持打开。请在可用时点击“停止”,然后再次尝试退出。" still_open = "Cap 仍在运行" -unconfirmed = "无法确认录制已停止,已取消退出。Cap 将保持打开。请点按“停止”,然后再次尝试退出。" +unconfirmed = "无法确认录制已停止,已取消退出。Cap 将保持打开。请点击“停止”,然后再次尝试退出。" [dialog.update] already_getting = "Cap 已在准备版本 {version}。" From c7955e8fb5e187467caede3d8b7e219f44f7fb04 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:29:46 +0000 Subject: [PATCH 19/25] feat(desktop-gpui): localize the remaining picker hints, Turbo export, clip transitions, camera blur labels, recording errors and decimals --- apps/desktop-gpui/locales/de.toml | 102 ++++++- apps/desktop-gpui/locales/en.toml | 102 ++++++- apps/desktop-gpui/locales/es.toml | 102 ++++++- apps/desktop-gpui/locales/fr.toml | 102 ++++++- apps/desktop-gpui/locales/hi.toml | 102 ++++++- apps/desktop-gpui/locales/ja.toml | 102 ++++++- apps/desktop-gpui/locales/zh-CN.toml | 102 ++++++- apps/desktop-gpui/src/controls_window.rs | 2 +- apps/desktop-gpui/src/editor_export.rs | 16 +- apps/desktop-gpui/src/editor_panels.rs | 2 +- apps/desktop-gpui/src/editor_timeline.rs | 6 +- apps/desktop-gpui/src/editor_window.rs | 27 +- .../src/i18n/dialogs_surface_tests.rs | 61 +++- .../src/i18n/editor_shell_surface_tests.rs | 24 ++ apps/desktop-gpui/src/i18n/messages.rs | 267 ++++++++++++++++++ apps/desktop-gpui/src/i18n/messages/tests.rs | 144 ++++++++++ apps/desktop-gpui/src/i18n/mod.rs | 12 + apps/desktop-gpui/src/i18n/surface_tests.rs | 3 + apps/desktop-gpui/src/i18n/tests.rs | 2 + apps/desktop-gpui/src/main_window.rs | 22 +- apps/desktop-gpui/src/permissions.rs | 16 +- apps/desktop-gpui/src/settings_window.rs | 4 +- apps/desktop-gpui/src/store.rs | 6 +- apps/desktop-gpui/src/target_overlay.rs | 48 ++-- 24 files changed, 1294 insertions(+), 82 deletions(-) create mode 100644 apps/desktop-gpui/src/i18n/messages.rs create mode 100644 apps/desktop-gpui/src/i18n/messages/tests.rs diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index ab2ee96077a..fc9c378503e 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -1183,6 +1183,11 @@ start_failed = "Das System konnte diese Kamera nicht starten. Sie ist möglicher timeout = "Von dieser Kamera kamen keine Bilder. Sie ist möglicherweise geschlossen, getrennt, abgedeckt oder wird von einer anderen App verwendet." unavailable_title = "Kamera nicht verfügbar" +[camera.blur] +light = "Leicht" +heavy = "Stark" +cutout = "Freistellen" + [onboarding.customize] footer = "Alles davon können Sie jederzeit in den Einstellungen ändern" subtitle = "Passen Sie alles an, von Tastenkürzeln bis zum Speicherort. Cap richtet sich nach Ihrer Arbeitsweise." @@ -1458,9 +1463,11 @@ metadata = "Projektmetadaten werden erstellt..." starting = "Konvertierung wird gestartet..." [overlay] -choose_mode = "Aufnahmemodus wählen" -countdown_tooltip = "Countdown vor der Aufnahme" display_fallback = "Bildschirm {id}" +countdown_title = "Countdown vor der Aufnahme" +countdown_body = "Nach dem Klick auf „Aufnahme starten“ einige Sekunden warten, bevor die Aufnahme beginnt." +mode_title = "Aufnahmemodus ändern" +mode_body = "Mit Instant erhalten Sie einen Link zum Teilen, sobald Sie die Aufnahme beenden. Studio nimmt in voller Qualität zur Bearbeitung auf." [overlay.area] draw = "Bereich aufziehen" @@ -1468,6 +1475,12 @@ free_ratio = "Frei" hint = "Klicken und ziehen Sie, um einen Bereich auszuwählen" minimum_size = "Die Mindestgröße beträgt {width} x {height}" too_small = "{width} x {height} ist zu klein" +more_ratios_title = "Weitere Seitenverhältnisse" +more_ratios_body = "Den Bereich auf ein Seitenverhältnis wie 4:3, 9:16 oder 21:9 festlegen." +reset_title = "Auswahl zurücksetzen" +reset_body = "Den Bereich löschen, um einen neuen aufzuziehen." +fill_title = "Bildschirm füllen" +fill_body = "Den Bereich auf den ganzen Bildschirm vergrößern und dabei das Seitenverhältnis beibehalten." [overlay.camera_only] description = "Nur mit Kamera und Mikrofon aufnehmen" @@ -1487,6 +1500,21 @@ start_recording = "Aufnahme starten" studio = "Studio-Modus: Lokal aufnehmen und bearbeiten" take_screenshot = "Bildschirmfoto aufnehmen" +[overlay.window] +size_title = "Fenstergröße ändern" +size_body = "Das Fenster vor der Aufnahme auf ein Seitenverhältnis oder eine genaue Größe bringen." +size_needs_accessibility_title = "Größenänderung erfordert Bedienungshilfen" +size_needs_accessibility_body = "Aktivieren Sie Cap unter „Bedienungshilfen“ in den Systemeinstellungen, um die Fenster anderer Apps anzupassen." +size_unavailable_title = "Größenänderung nicht verfügbar" +size_unavailable_body = "Unter Wayland können Apps die Fenstergröße anderer Apps nicht ändern." +focus_title = "Fokusmodus" +focus_on = "Nur dieses Fenster wird aufgenommen" +focus_off = "Nur dieses Fenster aufnehmen" +focus_enable = "Fokusmodus aktivieren" +focus_disable = "Fokusmodus deaktivieren" +focus_body = "Zentriert dieses Fenster und nimmt nur dieses auf. Was sich davor bewegt, erscheint nicht im Video." +focus_body_instant_camera = "Zentriert dieses Fenster. Mit Kamera nimmt Instant weiterhin den Bildschirmbereich auf, damit die Kamera im Video bleibt." + [permission.guide] accessibility_body = "Legen Sie es oben in der Liste „Bedienungshilfen“ ab und achten Sie darauf, dass der Schalter aktiviert ist." screen_body = "Legen Sie es oben in der Liste „Bildschirm- & Systemaudioaufnahme“ ab und achten Sie darauf, dass der Schalter aktiviert ist." @@ -2388,6 +2416,11 @@ refining_size = "Größe wird präzisiert…" refining_time = "Dauer wird präzisiert…" unavailable = "Nicht verfügbar" +[editor.export.turbo] +title = "Turbo-Export" +description = "Exportiert deutlich schneller mit allen CPU-Kernen und der GPU" +warning = "Nutzt bis zum Ende des Exports die gesamte CPU, die GPU und ein Vielfaches an Arbeitsspeicher. Andere Apps können langsamer werden, und Ihr Computer kann warm und laut werden." + [editor.modal] cancel_export = "Export abbrechen" cancel_export_message = "Möchten Sie den Export wirklich abbrechen?" @@ -2629,6 +2662,7 @@ normal = "Normal" add_shot = "Einstellung hinzufügen" add_shot_with_look = "Einstellung hinzufügen · {look} · {seconds} s" paused = "Pausiert" +title = "Timeline" [editor.timeline.empty] add_audio = "Audio hinzufügen" @@ -2765,6 +2799,11 @@ speed = "TEMPO" split = "Am Abspielkopf teilen" split_hint = "Bewegen Sie den Abspielkopf in diesen Clip" volume = "Lautstärke" +transition = "ÜBERGANG" +transition_none = "Keiner" +transition_crossfade = "Überblendung" +transition_fade = "Abblende" +transition_seconds = "{seconds} s" [editor.window.frame] add_tooltip = "Rahmen hinzufügen" @@ -2911,3 +2950,62 @@ package_not_installed = "Das Paket wurde nicht installiert. Bestätigen Sie die ready = "Cap {version} ist bereit. Starten Sie Cap neu, um das Update abzuschließen." restart_now = "Jetzt neu starten" title = "Cap aktualisieren" + +[message.recording] +task_failed = "Die Aufnahmeaufgabe ist fehlgeschlagen." +startup_failed = "Die Aufnahme konnte nicht gestartet werden" +ended_unexpectedly = "Die Aufnahme wurde unerwartet beendet." +stop_task_failed_plain = "Die Stopp-Aufgabe ist fehlgeschlagen." +stop_not_acknowledged = "Das Beenden der Aufnahme wurde nicht bestätigt" +capture_state_timed_out = "Zeitüberschreitung beim Bestätigen des Aufnahmestatus" +pause_not_acknowledged = "Die Aufnahme hat den angeforderten Pausenstatus nicht bestätigt" +cleanup_unconfirmed_files_kept = "Das Aufräumen der Aufnahme ist nicht bestätigt. Lokale Dateien bleiben erhalten; drücken Sie Ctrl+Shift+F9, um das Stoppen zu wiederholen." +cleanup_unconfirmed_retry = "Das Aufräumen der Aufnahme ist nicht bestätigt; stoppen Sie erneut." +discard_not_acknowledged = "Das Stoppen des Studio-Verwerfens wurde nicht bestätigt; die lokale Aufnahme bleibt erhalten" +window_still_resizing = "Die Fenstergröße wird noch geändert. Starten Sie erneut." +startup_cancelled = "Start der Aufnahme abgebrochen; lokale Dateien bleiben erhalten" +instant_cleanup_unconfirmed = "Das Aufräumen der Instant-Aufnahme ist nicht bestätigt; lokale Dateien bleiben erhalten" +finalization_no_result = "Die Fertigstellung der Aufnahme endete ohne Ergebnis. Ihre Dateien wurden behalten." +stopped_metadata_unsaved = "Die Aufnahme wurde gestoppt, aber ihre Metadaten konnten nicht gespeichert werden; lokale Dateien bleiben erhalten" +discard_stop_unconfirmed = "Das Stoppen des Studio-Verwerfens ist nicht bestätigt; lokale Dateien bleiben erhalten" +discard_unconfirmed = "Das Studio-Verwerfen ist nicht bestätigt; lokale Dateien bleiben erhalten" +crashed_before_capture = "Cap wurde beendet, bevor diese Aufnahme etwas aufgenommen hat." +capture_state_failed = "Der Aufnahmestatus konnte nicht bestätigt werden: {error}" +completion_task_failed = "Die Abschlussaufgabe der Aufnahme ist fehlgeschlagen: {error}" +stop_task_failed = "Die Stopp-Aufgabe ist fehlgeschlagen: {error}" +control_task_failed = "Die Steuerungsaufgabe der Aufnahme ist fehlgeschlagen: {error}" +restart_hide_failed = "Beim Neustart konnten die Cap-Fenster nicht ausgeblendet werden: {error}" +discard_task_failed = "Die Studio-Verwerfen-Aufgabe ist fehlgeschlagen: {error}; das Aufräumen ist nicht bestätigt" +cancel_task_failed = "Die Abbrechen-Aufgabe ist fehlgeschlagen: {error}. Das Aufräumen ist nicht bestätigt; stoppen Sie erneut." +then_cleanup_unconfirmed_shortcut = "{error}. Das Aufräumen der Aufnahme ist nicht bestätigt. Drücken Sie Ctrl+Shift+F9, um das Stoppen zu wiederholen." +then_cleanup_unconfirmed_control = "{error}. Das Aufräumen der Aufnahme ist nicht bestätigt. Verwenden Sie das Stopp-Steuerelement der Aufnahme, um es erneut zu versuchen." +then_shutdown_unconfirmed = "{error}. Das Beenden von Studio ist weiterhin nicht bestätigt; Aufnahme und Dateien bleiben erhalten." +then_use_stop_if_unsafe = "{error}. Verwenden Sie Stopp, wenn die Aufnahme nicht sicher fortgesetzt werden kann." +then_use_stop_control = "{error}. Verwenden Sie das Stopp-Steuerelement der Aufnahme, um sie zu beenden." +then_discard_stopped = "{error}; Verwerfen/Neustart angehalten, lokale Aufnahme bleibt erhalten" +then_files_preserved = "{error}. Die Dateien der fehlgeschlagenen Aufnahme wurden behalten." +then_cleanup_retry = "{error}. Das Aufräumen ist nicht bestätigt; stoppen Sie erneut." +low_storage_preserved = "Wenig Speicherplatz. Ihre Aufnahmedateien bleiben unter {path} erhalten. Geben Sie Speicherplatz frei und stellen Sie die Aufnahme dann in Cap wieder her." + +[message.permission] +unavailable = "Der Zugriff auf „{permission}“ ist nicht verfügbar. Erlauben Sie Cap unter „Systemeinstellungen“ > „Datenschutz & Sicherheit“ > „{permission}“ und wählen Sie das Gerät dann erneut aus." + +[message.upload] +offline = "Offline" +authentication_expired = "Die Anmeldung ist abgelaufen" +no_audio_intent_review = "Für diese ältere Aufnahme ist keine Audioeinstellung gespeichert; prüfen Sie sie, bevor Sie es erneut versuchen" +legacy_no_account = "Für diesen älteren Upload sind weder Konto noch Audioeinstellung gespeichert. Die lokale Aufnahme bleibt erhalten." +verification_timed_out = "Zeitüberschreitung bei der Cloud-Prüfung; die lokale Aufnahme bleibt erhalten" +verification_failed_retry = "Die Cloud-Prüfung ist fehlgeschlagen; die gespeicherte Aufnahme wird erneut hochgeladen" +must_save_first = "Die Aufnahme muss lokal gespeichert sein, bevor der Upload fortgesetzt werden kann" +sign_in_original = "Melden Sie sich beim ursprünglichen Konto und Server der Aufnahme an, um es erneut zu versuchen" +sign_in_again = "Melden Sie sich erneut an, um diese Aufnahme hochzuladen" +needs_upgrade = "Instant-Aufnahmen erfordern einen höheren Tarif" +changed_during = "Die lokale Aufnahme hat sich während des Uploads geändert" +changed_after = "Die lokale Aufnahme hat sich nach dem Upload geändert; sie wurde behalten" +already_queued = "Diese Aufnahme ist bereits zum Hochladen eingereiht" +paused = "Upload pausiert; die lokale Aufnahme bleibt erhalten" +worker_failed = "Der Upload-Vorgang ist fehlgeschlagen; die lokale Aufnahme bleibt erhalten" +empty = "Die gespeicherte Aufnahme ist leer" +no_resumable_state = "Für diese Aufnahme gibt es keinen fortsetzbaren Upload" +verification_failed = "Die Prüfung der Aufnahme ist fehlgeschlagen: {status}" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index 7be0571f0cf..5408ab376f6 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -1185,6 +1185,11 @@ start_failed = "The system could not start this camera. It may be unavailable or timeout = "No frames were received from this camera. It may be closed, disconnected, covered, or in use by another app." unavailable_title = "Camera unavailable" +[camera.blur] +light = { text = "Light", context = "Tiny 7px label under the background blur button in the camera preview. Keep it to one short word." } +heavy = { text = "Heavy", context = "Tiny 7px label under the background blur button in the camera preview. Keep it to one short word." } +cutout = { text = "Cutout", context = "Tiny 7px label under the background blur button: the background is removed. Keep it to one short word." } + [onboarding.customize] footer = "Change any of these at any time in Settings" subtitle = "Customize everything from keyboard shortcuts to storage. Cap adapts to your workflow." @@ -1460,9 +1465,11 @@ metadata = "Creating project metadata..." starting = "Starting conversion..." [overlay] -choose_mode = "Choose recording mode" -countdown_tooltip = "Recording countdown" display_fallback = { text = "Display {id}", context = "Name for a display the system does not name; {id} is a number." } +countdown_title = { text = "Recording countdown", context = "Hint title over the countdown button in the recording picker." } +countdown_body = { text = "Wait a few seconds after you press Start before recording begins.", context = "Hint body for the countdown button. Start is the Start Recording button." } +mode_title = { text = "Change recording mode", context = "Hint title over the mode picker in the recording picker." } +mode_body = "Instant gives you a link to share the moment you stop. Studio records in full quality for editing." [overlay.area] draw = "Draw an area" @@ -1470,6 +1477,12 @@ free_ratio = { text = "Free", context = "Aspect ratio button with no ratio locke hint = "Click and drag to select an area" minimum_size = { text = "Minimum size is {width} x {height}", context = "{width} and {height} are pixel sizes." } too_small = { text = "{width} x {height} is too small", context = "{width} and {height} are pixel sizes." } +more_ratios_title = "More aspect ratios" +more_ratios_body = "Lock the area to a ratio such as 4:3, 9:16 or 21:9." +reset_title = "Reset selection" +reset_body = "Clear the area so you can draw a new one." +fill_title = "Fill display" +fill_body = "Grow the area to cover the display, keeping its ratio." [overlay.camera_only] description = "Record using only your camera and microphone" @@ -1489,6 +1502,21 @@ start_recording = "Start Recording" studio = { text = "Studio Mode — Record and edit locally", context = "Second line of the start button, 11px, truncates." } take_screenshot = "Take Screenshot" +[overlay.window] +size_title = "Change window size" +size_body = "Resize the window to a ratio or an exact size before you record." +size_needs_accessibility_title = { text = "Resizing needs Accessibility", context = "Accessibility is the macOS System Settings pane; use the name macOS shows in this language." } +size_needs_accessibility_body = { text = "Turn on Accessibility for Cap in System Settings to resize other apps' windows.", context = "Accessibility is the macOS System Settings pane; use the name macOS shows in this language." } +size_unavailable_title = "Resizing isn't available" +size_unavailable_body = "Wayland doesn't let apps resize other apps' windows." +focus_title = { text = "Focus mode", context = "Card title above a switch in the window picker. Focus mode centres the picked window and records only it." } +focus_on = { text = "Recording only this window", context = "Subtitle under Focus mode while it is on, before recording starts. 11.5px, one line." } +focus_off = { text = "Record only this window", context = "Subtitle under Focus mode while it is off. 11.5px, one line." } +focus_enable = "Enable Focus mode" +focus_disable = "Turn off Focus mode" +focus_body = "Centres this window and records only it. Anything that moves in front of it stays out of the video." +focus_body_instant_camera = "Centres this window. With a camera, Instant still records the screen region so the camera stays in the video." + [permission.guide] accessibility_body = { text = "Drop it on the Accessibility list above, then make sure its switch is on.", context = "Accessibility is the name of the macOS System Settings pane; use the name macOS shows in this language." } screen_body = { text = "Drop it on the Screen & System Audio Recording list above, then make sure its switch is on.", context = "Screen & System Audio Recording is the name of the macOS System Settings pane; use the name macOS shows in this language." } @@ -2390,6 +2418,11 @@ refining_size = "Refining size…" refining_time = "Refining time…" unavailable = "Unavailable" +[editor.export.turbo] +title = "Turbo export" +description = "Exports much faster using every CPU core and the GPU" +warning = "Uses all of your CPU, the GPU and several times more memory until the export finishes. Other apps may slow down, and your computer may get warm and loud." + [editor.modal] cancel_export = "Cancel export" cancel_export_message = "Are you sure you want to cancel the export?" @@ -2631,6 +2664,7 @@ normal = "Normal" add_shot = "Add shot" add_shot_with_look = { text = "Add shot · {look} · {seconds}s", context = "Ghost on the 3D track. {look} is the camera move name, {seconds} a length such as 2.5." } paused = "Paused" +title = { text = "Timeline", context = "Label above the timeline while the editor is still preparing the recording." } [editor.timeline.empty] add_audio = "Add audio" @@ -2767,6 +2801,11 @@ speed = { text = "SPEED", context = "Section heading in the clip menu, written i split = "Split at playhead" split_hint = "Move the playhead into this clip" volume = "Volume" +transition = { text = "TRANSITION", context = "Section heading in the clip menu, written in capitals." } +transition_none = { text = "None", context = "Transition chip: no transition before this clip." } +transition_crossfade = { text = "Crossfade", context = "Transition chip: the previous clip dissolves into this one." } +transition_fade = { text = "Fade", context = "Transition chip: fade through black into this clip." } +transition_seconds = { text = "{seconds}s", context = "Transition length chip; {seconds} is a number such as 0.5." } [editor.window.frame] add_tooltip = "Add a frame" @@ -2913,3 +2952,62 @@ package_not_installed = "The package wasn't installed. Approve the system prompt ready = { text = "Cap {version} is ready. Restart Cap to finish updating.", context = "{version} is a version number such as 0.6.2." } restart_now = "Restart Now" title = { text = "Update Cap", context = "Title of every update dialog." } + +[message.recording] +task_failed = "Recording task failed." +startup_failed = "Recording startup failed" +ended_unexpectedly = "Recording ended unexpectedly." +stop_task_failed_plain = "Stop task failed." +stop_not_acknowledged = "Capture stop was not acknowledged" +capture_state_timed_out = "Timed out confirming capture state" +pause_not_acknowledged = "Capture did not acknowledge the requested pause state" +cleanup_unconfirmed_files_kept = { text = "Capture cleanup is unconfirmed. Local files are preserved; use Ctrl+Shift+F9 to retry Stop.", context = "Ctrl+Shift+F9 is a keyboard shortcut; keep it as written." } +cleanup_unconfirmed_retry = "Capture cleanup is unconfirmed; retry Stop." +discard_not_acknowledged = "Studio discard stop was not acknowledged; local recording retained" +window_still_resizing = "The window is still being resized. Try starting again." +startup_cancelled = "Recording startup cancelled; local files preserved" +instant_cleanup_unconfirmed = "Instant capture cleanup is unconfirmed; local files preserved" +finalization_no_result = "Recording finalization ended without a result. Your files were retained." +stopped_metadata_unsaved = "Capture stopped, but stopped metadata could not be saved; local files preserved" +discard_stop_unconfirmed = "Studio discard stop is unconfirmed; local files preserved" +discard_unconfirmed = "Studio discard is unconfirmed; local files preserved" +crashed_before_capture = "Cap closed before this recording captured anything." +capture_state_failed = { text = "Could not confirm capture state: {error}", context = "{error} is a technical error message, usually in English." } +completion_task_failed = { text = "Recording completion task failed: {error}", context = "{error} is a technical error message, usually in English." } +stop_task_failed = { text = "Stop task failed: {error}", context = "{error} is a technical error message, usually in English." } +control_task_failed = { text = "Capture control task failed: {error}", context = "{error} is a technical error message, usually in English." } +restart_hide_failed = { text = "Restart could not hide Cap windows: {error}", context = "{error} is a technical error message, usually in English." } +discard_task_failed = { text = "Studio discard task failed: {error}; cleanup is unconfirmed", context = "{error} is a technical error message, usually in English." } +cancel_task_failed = { text = "Cancel task failed: {error}. Cleanup is unconfirmed; retry Stop.", context = "{error} is a technical error message, usually in English." } +then_cleanup_unconfirmed_shortcut = { text = "{error}. Capture cleanup is unconfirmed. Use Ctrl+Shift+F9 to retry Stop.", context = "{error} is a technical error message, usually in English. Ctrl+Shift+F9 is a keyboard shortcut; keep it as written." } +then_cleanup_unconfirmed_control = { text = "{error}. Capture cleanup is unconfirmed. Use your recording Stop control to retry.", context = "{error} is a technical error message, usually in English." } +then_shutdown_unconfirmed = { text = "{error}. Studio shutdown remains unconfirmed; recording and files retained.", context = "{error} is a technical error message, usually in English." } +then_use_stop_if_unsafe = { text = "{error}. Use Stop if recording cannot continue safely.", context = "{error} is a technical error message, usually in English." } +then_use_stop_control = { text = "{error}. Use your recording Stop control to stop recording.", context = "{error} is a technical error message, usually in English." } +then_discard_stopped = { text = "{error}; discard/restart stopped, local recording retained", context = "{error} is a technical error message, usually in English." } +then_files_preserved = { text = "{error}. Failed recording files were preserved.", context = "{error} is a technical error message, usually in English." } +then_cleanup_retry = { text = "{error}. Cleanup is unconfirmed; retry Stop.", context = "{error} is a technical error message, usually in English." } +low_storage_preserved = { text = "Low storage. Your recording files are preserved at {path}. Free up space, then recover the recording in Cap.", context = "{path} is a folder path." } + +[message.permission] +unavailable = { text = "{permission} access is unavailable. Allow Cap in System Settings > Privacy & Security > {permission}, then select the device again.", context = "{permission} is the macOS privacy pane name, such as Camera or Microphone, already translated." } + +[message.upload] +offline = { text = "offline", context = "Shown under a recording whose upload is waiting for the network. Written in lowercase in English." } +authentication_expired = "Authentication expired" +no_audio_intent_review = "This older recording has no saved audio intent; review it before retrying" +legacy_no_account = "This older upload has no saved account and audio intent. Its local recording is preserved." +verification_timed_out = "Cloud verification timed out; the local recording is preserved" +verification_failed_retry = "Cloud verification failed; the saved recording will upload again" +must_save_first = "Recording must be saved locally before its upload can resume" +sign_in_original = "Sign in to the original recording account and server to retry" +sign_in_again = "Sign in again to upload this recording" +needs_upgrade = "Instant recording requires an upgraded plan" +changed_during = "The local recording changed during upload" +changed_after = "The local recording changed after upload; it has been preserved" +already_queued = "This recording is already queued for upload" +paused = "Upload paused; the local recording is preserved" +worker_failed = "The upload worker failed; the local recording is preserved" +empty = "The saved recording is empty" +no_resumable_state = "This recording has no resumable upload state" +verification_failed = { text = "Recording verification failed: {status}", context = "{status} is a technical status, usually in English." } \ No newline at end of file diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index d44d81c9971..2d7ea0eac86 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -1183,6 +1183,11 @@ start_failed = "El sistema no pudo iniciar esta cámara. Puede que no esté disp timeout = "No se recibieron imágenes de esta cámara. Puede que esté cerrada, desconectada, tapada o en uso por otra app." unavailable_title = "Cámara no disponible" +[camera.blur] +light = "Suave" +heavy = "Fuerte" +cutout = "Recorte" + [onboarding.customize] footer = "Puedes cambiar todo esto en cualquier momento en Ajustes" subtitle = "Personaliza todo, desde los atajos de teclado hasta el almacenamiento. Cap se adapta a tu forma de trabajar." @@ -1458,9 +1463,11 @@ metadata = "Creando los metadatos del proyecto..." starting = "Iniciando la conversión..." [overlay] -choose_mode = "Elegir modo de grabación" -countdown_tooltip = "Cuenta atrás de la grabación" display_fallback = "Pantalla {id}" +countdown_title = "Cuenta atrás de la grabación" +countdown_body = "Espera unos segundos después de pulsar Iniciar grabación antes de empezar a grabar." +mode_title = "Cambiar el modo de grabación" +mode_body = "Instant te da un enlace para compartir en cuanto detienes la grabación. Studio graba con la máxima calidad para editar." [overlay.area] draw = "Dibuja un área" @@ -1468,6 +1475,12 @@ free_ratio = "Libre" hint = "Haz clic y arrastra para seleccionar un área" minimum_size = "El tamaño mínimo es {width} x {height}" too_small = "{width} x {height} es demasiado pequeño" +more_ratios_title = "Más relaciones de aspecto" +more_ratios_body = "Fija el área a una proporción como 4:3, 9:16 o 21:9." +reset_title = "Restablecer la selección" +reset_body = "Borra el área para dibujar una nueva." +fill_title = "Llenar la pantalla" +fill_body = "Amplía el área hasta cubrir la pantalla manteniendo su proporción." [overlay.camera_only] description = "Graba solo con tu cámara y tu micrófono" @@ -1487,6 +1500,21 @@ start_recording = "Iniciar grabación" studio = "Modo Studio: graba y edita en local" take_screenshot = "Hacer captura de pantalla" +[overlay.window] +size_title = "Cambiar el tamaño de la ventana" +size_body = "Ajusta la ventana a una proporción o a un tamaño exacto antes de grabar." +size_needs_accessibility_title = "Para cambiar el tamaño se necesita Accesibilidad" +size_needs_accessibility_body = "Activa Cap en Accesibilidad, en Ajustes del Sistema, para cambiar el tamaño de las ventanas de otras apps." +size_unavailable_title = "No se puede cambiar el tamaño" +size_unavailable_body = "Wayland no permite que las apps cambien el tamaño de las ventanas de otras apps." +focus_title = "Modo enfoque" +focus_on = "Solo se grabará esta ventana" +focus_off = "Grabar solo esta ventana" +focus_enable = "Activar el modo enfoque" +focus_disable = "Desactivar el modo enfoque" +focus_body = "Centra esta ventana y graba solo ella. Lo que pase por delante no aparecerá en el vídeo." +focus_body_instant_camera = "Centra esta ventana. Con una cámara, Instant sigue grabando la región de pantalla para que la cámara aparezca en el vídeo." + [permission.guide] accessibility_body = "Suéltalo en la lista «Accesibilidad» de arriba y asegúrate de que su interruptor esté activado." screen_body = "Suéltalo en la lista «Grabación de pantalla y audio del sistema» de arriba y asegúrate de que su interruptor esté activado." @@ -2388,6 +2416,11 @@ refining_size = "Ajustando tamaño…" refining_time = "Ajustando tiempo…" unavailable = "No disponible" +[editor.export.turbo] +title = "Exportación turbo" +description = "Exporta mucho más rápido usando todos los núcleos de la CPU y la GPU" +warning = "Usa toda tu CPU, la GPU y varias veces más memoria hasta que termina la exportación. Otras apps pueden ir más lentas, y tu ordenador puede calentarse y hacer ruido." + [editor.modal] cancel_export = "Cancelar exportación" cancel_export_message = "¿Seguro que quieres cancelar la exportación?" @@ -2629,6 +2662,7 @@ normal = "Normal" add_shot = "Añadir toma" add_shot_with_look = "Añadir toma · {look} · {seconds} s" paused = "En pausa" +title = "Línea de tiempo" [editor.timeline.empty] add_audio = "Añadir audio" @@ -2765,6 +2799,11 @@ speed = "VELOCIDAD" split = "Dividir en el cabezal" split_hint = "Mueve el cabezal dentro de este clip" volume = "Volumen" +transition = "TRANSICIÓN" +transition_none = "Ninguna" +transition_crossfade = "Fundido encadenado" +transition_fade = "Fundido" +transition_seconds = "{seconds} s" [editor.window.frame] add_tooltip = "Añadir un marco" @@ -2911,3 +2950,62 @@ package_not_installed = "No se instaló el paquete. Acepta el aviso del sistema ready = "Cap {version} está listo. Reinicia Cap para terminar de actualizar." restart_now = "Reiniciar ahora" title = "Actualizar Cap" + +[message.recording] +task_failed = "La tarea de grabación ha fallado." +startup_failed = "No se pudo iniciar la grabación" +ended_unexpectedly = "La grabación terminó de forma inesperada." +stop_task_failed_plain = "La tarea de detención ha fallado." +stop_not_acknowledged = "No se confirmó la detención de la captura" +capture_state_timed_out = "Se agotó el tiempo para confirmar el estado de la captura" +pause_not_acknowledged = "La captura no confirmó el estado de pausa solicitado" +cleanup_unconfirmed_files_kept = "No se ha confirmado la limpieza de la captura. Los archivos locales se conservan; usa Ctrl+Shift+F9 para volver a detener." +cleanup_unconfirmed_retry = "No se ha confirmado la limpieza de la captura; vuelve a detener." +discard_not_acknowledged = "No se confirmó la detención del descarte de Studio; la grabación local se conserva" +window_still_resizing = "La ventana aún se está redimensionando. Vuelve a iniciar la grabación." +startup_cancelled = "Se canceló el inicio de la grabación; los archivos locales se conservan" +instant_cleanup_unconfirmed = "No se ha confirmado la limpieza de la captura Instant; los archivos locales se conservan" +finalization_no_result = "La finalización de la grabación terminó sin resultado. Tus archivos se han conservado." +stopped_metadata_unsaved = "La captura se detuvo, pero no se pudieron guardar sus metadatos; los archivos locales se conservan" +discard_stop_unconfirmed = "No se ha confirmado la detención del descarte de Studio; los archivos locales se conservan" +discard_unconfirmed = "No se ha confirmado el descarte de Studio; los archivos locales se conservan" +crashed_before_capture = "Cap se cerró antes de que esta grabación capturara nada." +capture_state_failed = "No se pudo confirmar el estado de la captura: {error}" +completion_task_failed = "La tarea de finalización de la grabación ha fallado: {error}" +stop_task_failed = "La tarea de detención ha fallado: {error}" +control_task_failed = "La tarea de control de la captura ha fallado: {error}" +restart_hide_failed = "Al reiniciar no se pudieron ocultar las ventanas de Cap: {error}" +discard_task_failed = "La tarea de descarte de Studio ha fallado: {error}; no se ha confirmado la limpieza" +cancel_task_failed = "La tarea de cancelación ha fallado: {error}. No se ha confirmado la limpieza; vuelve a detener." +then_cleanup_unconfirmed_shortcut = "{error}. No se ha confirmado la limpieza de la captura. Usa Ctrl+Shift+F9 para volver a detener." +then_cleanup_unconfirmed_control = "{error}. No se ha confirmado la limpieza de la captura. Usa el control Detener de la grabación para volver a intentarlo." +then_shutdown_unconfirmed = "{error}. Sigue sin confirmarse el cierre de Studio; la grabación y los archivos se conservan." +then_use_stop_if_unsafe = "{error}. Usa Detener si la grabación no puede continuar con seguridad." +then_use_stop_control = "{error}. Usa el control Detener de la grabación para detenerla." +then_discard_stopped = "{error}; se detuvo el descarte/reinicio y la grabación local se conserva" +then_files_preserved = "{error}. Se conservaron los archivos de la grabación fallida." +then_cleanup_retry = "{error}. No se ha confirmado la limpieza; vuelve a detener." +low_storage_preserved = "Poco espacio de almacenamiento. Los archivos de tu grabación se conservan en {path}. Libera espacio y luego recupera la grabación en Cap." + +[message.permission] +unavailable = "El acceso a «{permission}» no está disponible. Permite Cap en Ajustes del Sistema > Privacidad y seguridad > {permission} y vuelve a seleccionar el dispositivo." + +[message.upload] +offline = "Sin conexión" +authentication_expired = "La autenticación ha caducado" +no_audio_intent_review = "Esta grabación antigua no tiene guardada la configuración de audio; revísala antes de reintentarlo" +legacy_no_account = "Esta subida antigua no tiene guardadas la cuenta ni la configuración de audio. Su grabación local se conserva." +verification_timed_out = "Se agotó el tiempo de la verificación en la nube; la grabación local se conserva" +verification_failed_retry = "La verificación en la nube ha fallado; la grabación guardada se volverá a subir" +must_save_first = "La grabación debe guardarse localmente antes de reanudar su subida" +sign_in_original = "Inicia sesión en la cuenta y el servidor originales de la grabación para reintentarlo" +sign_in_again = "Vuelve a iniciar sesión para subir esta grabación" +needs_upgrade = "La grabación Instant requiere un plan superior" +changed_during = "La grabación local cambió durante la subida" +changed_after = "La grabación local cambió después de la subida; se ha conservado" +already_queued = "Esta grabación ya está en la cola de subida" +paused = "Subida en pausa; la grabación local se conserva" +worker_failed = "El proceso de subida ha fallado; la grabación local se conserva" +empty = "La grabación guardada está vacía" +no_resumable_state = "Esta grabación no tiene una subida que se pueda reanudar" +verification_failed = "La verificación de la grabación ha fallado: {status}" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index 560ef406e0a..4715b7a28f2 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -1183,6 +1183,11 @@ start_failed = "Le système n'a pas pu démarrer cette caméra. Elle est peut-ê timeout = "Aucune image n'a été reçue de cette caméra. Elle est peut-être fermée, déconnectée, masquée ou utilisée par une autre app." unavailable_title = "Caméra indisponible" +[camera.blur] +light = "Léger" +heavy = "Fort" +cutout = "Détouré" + [onboarding.customize] footer = "Vous pouvez modifier tout cela à tout moment dans les paramètres" subtitle = "Personnalisez tout, des raccourcis clavier au stockage. Cap s'adapte à votre façon de travailler." @@ -1458,9 +1463,11 @@ metadata = "Création des métadonnées du projet..." starting = "Démarrage de la conversion..." [overlay] -choose_mode = "Choisir le mode d'enregistrement" -countdown_tooltip = "Compte à rebours de l'enregistrement" display_fallback = "Écran {id}" +countdown_title = "Compte à rebours de l'enregistrement" +countdown_body = "Attendre quelques secondes entre le clic sur Lancer l'enregistrement et le début de l'enregistrement." +mode_title = "Changer de mode d'enregistrement" +mode_body = "Instant vous donne un lien à partager dès que vous arrêtez. Studio enregistre en pleine qualité pour le montage." [overlay.area] draw = "Dessinez une zone" @@ -1468,6 +1475,12 @@ free_ratio = "Libre" hint = "Cliquez et faites glisser pour sélectionner une zone" minimum_size = "La taille minimale est de {width} x {height}" too_small = "{width} x {height} est trop petit" +more_ratios_title = "Autres formats" +more_ratios_body = "Verrouiller la zone sur un format comme 4:3, 9:16 ou 21:9." +reset_title = "Réinitialiser la sélection" +reset_body = "Effacer la zone pour en dessiner une nouvelle." +fill_title = "Remplir l'écran" +fill_body = "Agrandir la zone jusqu'à couvrir l'écran en conservant son format." [overlay.camera_only] description = "Enregistrez uniquement avec votre caméra et votre micro" @@ -1487,6 +1500,21 @@ start_recording = "Lancer l'enregistrement" studio = "Mode Studio : enregistrer et modifier en local" take_screenshot = "Prendre une capture d'écran" +[overlay.window] +size_title = "Modifier la taille de la fenêtre" +size_body = "Redimensionner la fenêtre selon un format ou une taille exacte avant d'enregistrer." +size_needs_accessibility_title = "Le redimensionnement nécessite l'accès Accessibilité" +size_needs_accessibility_body = "Activez Cap dans Accessibilité, dans Réglages Système, pour redimensionner les fenêtres des autres apps." +size_unavailable_title = "Redimensionnement indisponible" +size_unavailable_body = "Wayland ne permet pas aux apps de redimensionner les fenêtres d'autres apps." +focus_title = "Mode focus" +focus_on = "Seule cette fenêtre sera enregistrée" +focus_off = "Enregistrer uniquement cette fenêtre" +focus_enable = "Activer le mode focus" +focus_disable = "Désactiver le mode focus" +focus_body = "Centre cette fenêtre et n'enregistre qu'elle. Tout ce qui passe devant reste hors de la vidéo." +focus_body_instant_camera = "Centre cette fenêtre. Avec une caméra, Instant enregistre quand même la zone de l'écran pour que la caméra reste dans la vidéo." + [permission.guide] accessibility_body = "Déposez-le dans la liste « Accessibilité » ci-dessus, puis vérifiez que son interrupteur est activé." screen_body = "Déposez-le dans la liste « Enregistrement de l'écran et des sons du système » ci-dessus, puis vérifiez que son interrupteur est activé." @@ -2388,6 +2416,11 @@ refining_size = "Affinage de la taille…" refining_time = "Affinage de la durée…" unavailable = "Indisponible" +[editor.export.turbo] +title = "Export turbo" +description = "Exporte beaucoup plus vite en utilisant tous les cœurs du processeur et le GPU" +warning = "Utilise tout votre processeur, le GPU et plusieurs fois plus de mémoire jusqu'à la fin de l'export. Les autres apps peuvent ralentir, et votre ordinateur peut chauffer et devenir bruyant." + [editor.modal] cancel_export = "Annuler l'exportation" cancel_export_message = "Voulez-vous vraiment annuler l'exportation ?" @@ -2629,6 +2662,7 @@ normal = "Normal" add_shot = "Ajouter un plan" add_shot_with_look = "Ajouter un plan · {look} · {seconds} s" paused = "En pause" +title = "Timeline" [editor.timeline.empty] add_audio = "Ajouter de l'audio" @@ -2765,6 +2799,11 @@ speed = "VITESSE" split = "Couper à la tête de lecture" split_hint = "Placez la tête de lecture dans ce clip" volume = "Volume" +transition = "TRANSITION" +transition_none = "Aucune" +transition_crossfade = "Fondu enchaîné" +transition_fade = "Fondu" +transition_seconds = "{seconds} s" [editor.window.frame] add_tooltip = "Ajouter un cadre" @@ -2911,3 +2950,62 @@ package_not_installed = "Le paquet n'a pas été installé. Acceptez la demande ready = "Cap {version} est prêt. Redémarrez Cap pour terminer la mise à jour." restart_now = "Redémarrer maintenant" title = "Mettre à jour Cap" + +[message.recording] +task_failed = "La tâche d'enregistrement a échoué." +startup_failed = "Le démarrage de l'enregistrement a échoué" +ended_unexpectedly = "L'enregistrement s'est arrêté de façon inattendue." +stop_task_failed_plain = "La tâche d'arrêt a échoué." +stop_not_acknowledged = "L'arrêt de la capture n'a pas été confirmé" +capture_state_timed_out = "Délai dépassé pour confirmer l'état de la capture" +pause_not_acknowledged = "La capture n'a pas confirmé l'état de pause demandé" +cleanup_unconfirmed_files_kept = "Le nettoyage de la capture n'est pas confirmé. Les fichiers locaux sont conservés ; utilisez Ctrl+Shift+F9 pour relancer l'arrêt." +cleanup_unconfirmed_retry = "Le nettoyage de la capture n'est pas confirmé ; relancez l'arrêt." +discard_not_acknowledged = "L'arrêt de la suppression Studio n'a pas été confirmé ; l'enregistrement local est conservé" +window_still_resizing = "La fenêtre est encore en cours de redimensionnement. Réessayez de démarrer." +startup_cancelled = "Démarrage de l'enregistrement annulé ; les fichiers locaux sont conservés" +instant_cleanup_unconfirmed = "Le nettoyage de la capture Instant n'est pas confirmé ; les fichiers locaux sont conservés" +finalization_no_result = "La finalisation de l'enregistrement s'est terminée sans résultat. Vos fichiers ont été conservés." +stopped_metadata_unsaved = "La capture est arrêtée, mais ses métadonnées n'ont pas pu être enregistrées ; les fichiers locaux sont conservés" +discard_stop_unconfirmed = "L'arrêt de la suppression Studio n'est pas confirmé ; les fichiers locaux sont conservés" +discard_unconfirmed = "La suppression Studio n'est pas confirmée ; les fichiers locaux sont conservés" +crashed_before_capture = "Cap s'est fermé avant que cet enregistrement ne capture quoi que ce soit." +capture_state_failed = "Impossible de confirmer l'état de la capture : {error}" +completion_task_failed = "La tâche de fin d'enregistrement a échoué : {error}" +stop_task_failed = "La tâche d'arrêt a échoué : {error}" +control_task_failed = "La tâche de contrôle de la capture a échoué : {error}" +restart_hide_failed = "Le redémarrage n'a pas pu masquer les fenêtres de Cap : {error}" +discard_task_failed = "La tâche de suppression Studio a échoué : {error} ; le nettoyage n'est pas confirmé" +cancel_task_failed = "La tâche d'annulation a échoué : {error}. Le nettoyage n'est pas confirmé ; relancez l'arrêt." +then_cleanup_unconfirmed_shortcut = "{error}. Le nettoyage de la capture n'est pas confirmé. Utilisez Ctrl+Shift+F9 pour relancer l'arrêt." +then_cleanup_unconfirmed_control = "{error}. Le nettoyage de la capture n'est pas confirmé. Utilisez la commande Arrêter de l'enregistrement pour réessayer." +then_shutdown_unconfirmed = "{error}. L'arrêt de Studio n'est toujours pas confirmé ; l'enregistrement et les fichiers sont conservés." +then_use_stop_if_unsafe = "{error}. Utilisez Arrêter si l'enregistrement ne peut pas continuer en toute sécurité." +then_use_stop_control = "{error}. Utilisez la commande Arrêter de l'enregistrement pour l'arrêter." +then_discard_stopped = "{error} ; suppression/redémarrage interrompu, l'enregistrement local est conservé" +then_files_preserved = "{error}. Les fichiers de l'enregistrement échoué ont été conservés." +then_cleanup_retry = "{error}. Le nettoyage n'est pas confirmé ; relancez l'arrêt." +low_storage_preserved = "Espace de stockage insuffisant. Vos fichiers d'enregistrement sont conservés dans {path}. Libérez de l'espace, puis récupérez l'enregistrement dans Cap." + +[message.permission] +unavailable = "L'accès « {permission} » n'est pas disponible. Autorisez Cap dans Réglages Système > Confidentialité et sécurité > {permission}, puis sélectionnez à nouveau l'appareil." + +[message.upload] +offline = "Hors ligne" +authentication_expired = "L'authentification a expiré" +no_audio_intent_review = "Cet ancien enregistrement n'a pas de réglage audio enregistré ; vérifiez-le avant de réessayer" +legacy_no_account = "Cet ancien envoi n'a ni compte ni réglage audio enregistrés. Son enregistrement local est conservé." +verification_timed_out = "La vérification dans le cloud a expiré ; l'enregistrement local est conservé" +verification_failed_retry = "La vérification dans le cloud a échoué ; l'enregistrement sauvegardé sera envoyé à nouveau" +must_save_first = "L'enregistrement doit être sauvegardé localement avant que son envoi puisse reprendre" +sign_in_original = "Connectez-vous au compte et au serveur d'origine de l'enregistrement pour réessayer" +sign_in_again = "Reconnectez-vous pour envoyer cet enregistrement" +needs_upgrade = "L'enregistrement Instant nécessite une offre supérieure" +changed_during = "L'enregistrement local a changé pendant l'envoi" +changed_after = "L'enregistrement local a changé après l'envoi ; il a été conservé" +already_queued = "Cet enregistrement est déjà en attente d'envoi" +paused = "Envoi en pause ; l'enregistrement local est conservé" +worker_failed = "Le processus d'envoi a échoué ; l'enregistrement local est conservé" +empty = "L'enregistrement sauvegardé est vide" +no_resumable_state = "Cet enregistrement n'a pas d'envoi pouvant reprendre" +verification_failed = "La vérification de l'enregistrement a échoué : {status}" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index de0f312c6d6..be00a5d8c1a 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -1183,6 +1183,11 @@ start_failed = "सिस्टम यह कैमरा शुरू नही timeout = "इस कैमरे से कोई फ़्रेम नहीं मिला। हो सकता है यह बंद, डिस्कनेक्ट या ढका हुआ हो, या कोई दूसरा ऐप इसे इस्तेमाल कर रहा हो।" unavailable_title = "कैमरा उपलब्ध नहीं" +[camera.blur] +light = "हल्का" +heavy = "तेज़" +cutout = "कटआउट" + [onboarding.customize] footer = "इनमें से कुछ भी आप कभी भी सेटिंग्स में बदल सकते हैं" subtitle = "कीबोर्ड शॉर्टकट से लेकर स्टोरेज तक, सब कुछ अपने हिसाब से बदलें। Cap आपके काम करने के तरीके के अनुसार ढल जाता है।" @@ -1458,9 +1463,11 @@ metadata = "प्रोजेक्ट मेटाडेटा बन रह starting = "कन्वर्ज़न शुरू हो रहा है..." [overlay] -choose_mode = "रिकॉर्डिंग मोड चुनें" -countdown_tooltip = "रिकॉर्डिंग काउंटडाउन" display_fallback = "डिस्प्ले {id}" +countdown_title = "रिकॉर्डिंग काउंटडाउन" +countdown_body = "‘रिकॉर्डिंग शुरू करें’ दबाने के बाद रिकॉर्डिंग शुरू होने से पहले कुछ सेकंड इंतज़ार करें।" +mode_title = "रिकॉर्डिंग मोड बदलें" +mode_body = "Instant रिकॉर्डिंग रोकते ही आपको शेयर करने के लिए एक लिंक देता है। Studio एडिटिंग के लिए पूरी क्वालिटी में रिकॉर्ड करता है।" [overlay.area] draw = "एक क्षेत्र बनाएँ" @@ -1468,6 +1475,12 @@ free_ratio = "फ़्री" hint = "क्षेत्र चुनने के लिए क्लिक करके खींचें" minimum_size = "न्यूनतम साइज़ {width} x {height} है" too_small = "{width} x {height} बहुत छोटा है" +more_ratios_title = "और आस्पेक्ट रेशियो" +more_ratios_body = "क्षेत्र को 4:3, 9:16 या 21:9 जैसे अनुपात पर लॉक करें।" +reset_title = "चयन रीसेट करें" +reset_body = "क्षेत्र हटाएँ ताकि आप नया बना सकें।" +fill_title = "पूरा डिस्प्ले भरें" +fill_body = "अनुपात बनाए रखते हुए क्षेत्र को पूरे डिस्प्ले तक बढ़ाएँ।" [overlay.camera_only] description = "सिर्फ़ अपने कैमरा और माइक्रोफ़ोन से रिकॉर्ड करें" @@ -1487,6 +1500,21 @@ start_recording = "रिकॉर्डिंग शुरू करें" studio = "Studio मोड: लोकल रूप से रिकॉर्ड और एडिट करें" take_screenshot = "स्क्रीनशॉट लें" +[overlay.window] +size_title = "विंडो का आकार बदलें" +size_body = "रिकॉर्ड करने से पहले विंडो को किसी अनुपात या सटीक आकार में बदलें।" +size_needs_accessibility_title = "आकार बदलने के लिए ऐक्सेसिबिलिटी ज़रूरी है" +size_needs_accessibility_body = "दूसरे ऐप्स की विंडो का आकार बदलने के लिए सिस्टम सेटिंग्स में Cap के लिए ऐक्सेसिबिलिटी चालू करें।" +size_unavailable_title = "आकार बदलना उपलब्ध नहीं है" +size_unavailable_body = "Wayland ऐप्स को दूसरे ऐप्स की विंडो का आकार बदलने नहीं देता।" +focus_title = "फ़ोकस मोड" +focus_on = "सिर्फ़ यही विंडो रिकॉर्ड होगी" +focus_off = "सिर्फ़ यही विंडो रिकॉर्ड करें" +focus_enable = "फ़ोकस मोड चालू करें" +focus_disable = "फ़ोकस मोड बंद करें" +focus_body = "इस विंडो को बीच में रखता है और सिर्फ़ इसे रिकॉर्ड करता है। इसके सामने आने वाली कोई भी चीज़ वीडियो में नहीं आती।" +focus_body_instant_camera = "इस विंडो को बीच में रखता है। कैमरा होने पर Instant फिर भी स्क्रीन क्षेत्र रिकॉर्ड करता है ताकि कैमरा वीडियो में बना रहे।" + [permission.guide] accessibility_body = "इसे ऊपर दी गई ऐक्सेसिबिलिटी लिस्ट में छोड़ें, फिर पक्का करें कि इसका स्विच चालू है।" screen_body = "इसे ऊपर दी गई स्क्रीन और सिस्टम ऑडियो रिकॉर्डिंग लिस्ट में छोड़ें, फिर पक्का करें कि इसका स्विच चालू है।" @@ -2388,6 +2416,11 @@ refining_size = "साइज़ सटीक किया जा रहा ह refining_time = "समय सटीक किया जा रहा है…" unavailable = "उपलब्ध नहीं" +[editor.export.turbo] +title = "टर्बो एक्सपोर्ट" +description = "हर CPU कोर और GPU का इस्तेमाल करके कहीं तेज़ी से एक्सपोर्ट करता है" +warning = "एक्सपोर्ट पूरा होने तक आपका पूरा CPU, GPU और कई गुना ज़्यादा मेमोरी इस्तेमाल करता है। दूसरे ऐप्स धीमे हो सकते हैं, और आपका कंप्यूटर गर्म होकर ज़्यादा आवाज़ कर सकता है।" + [editor.modal] cancel_export = "एक्सपोर्ट रद्द करें" cancel_export_message = "क्या आप वाकई एक्सपोर्ट रद्द करना चाहते हैं?" @@ -2629,6 +2662,7 @@ normal = "सामान्य" add_shot = "शॉट जोड़ें" add_shot_with_look = "शॉट जोड़ें · {look} · {seconds} सेकंड" paused = "रुका हुआ" +title = "टाइमलाइन" [editor.timeline.empty] add_audio = "ऑडियो जोड़ें" @@ -2765,6 +2799,11 @@ speed = "स्पीड" split = "प्लेहेड पर विभाजित करें" split_hint = "प्लेहेड को इस क्लिप के अंदर ले जाएँ" volume = "वॉल्यूम" +transition = "ट्रांज़िशन" +transition_none = "कोई नहीं" +transition_crossfade = "क्रॉसफ़ेड" +transition_fade = "फ़ेड" +transition_seconds = "{seconds} सेकंड" [editor.window.frame] add_tooltip = "फ़्रेम जोड़ें" @@ -2911,3 +2950,62 @@ package_not_installed = "पैकेज इंस्टॉल नहीं ह ready = "Cap {version} तैयार है। अपडेट पूरा करने के लिए Cap रीस्टार्ट करें।" restart_now = "अभी रीस्टार्ट करें" title = "Cap अपडेट करें" + +[message.recording] +task_failed = "रिकॉर्डिंग का काम विफल हो गया।" +startup_failed = "रिकॉर्डिंग शुरू नहीं हो सकी" +ended_unexpectedly = "रिकॉर्डिंग अचानक बंद हो गई।" +stop_task_failed_plain = "रोकने का काम विफल हो गया।" +stop_not_acknowledged = "कैप्चर रुकने की पुष्टि नहीं हुई" +capture_state_timed_out = "कैप्चर की स्थिति की पुष्टि करते समय टाइम आउट हो गया" +pause_not_acknowledged = "कैप्चर ने माँगी गई पॉज़ स्थिति की पुष्टि नहीं की" +cleanup_unconfirmed_files_kept = "कैप्चर की सफ़ाई की पुष्टि नहीं हुई। लोकल फ़ाइलें सुरक्षित हैं; फिर से रोकने के लिए Ctrl+Shift+F9 दबाएँ।" +cleanup_unconfirmed_retry = "कैप्चर की सफ़ाई की पुष्टि नहीं हुई; फिर से रोकें।" +discard_not_acknowledged = "Studio हटाने के रुकने की पुष्टि नहीं हुई; लोकल रिकॉर्डिंग सुरक्षित है" +window_still_resizing = "विंडो का आकार अभी बदल रहा है। फिर से शुरू करके देखें।" +startup_cancelled = "रिकॉर्डिंग शुरू करना रद्द हुआ; लोकल फ़ाइलें सुरक्षित हैं" +instant_cleanup_unconfirmed = "Instant कैप्चर की सफ़ाई की पुष्टि नहीं हुई; लोकल फ़ाइलें सुरक्षित हैं" +finalization_no_result = "रिकॉर्डिंग पूरी करने का काम बिना नतीजे के खत्म हो गया। आपकी फ़ाइलें सुरक्षित रखी गई हैं।" +stopped_metadata_unsaved = "कैप्चर रुक गया, लेकिन उसका मेटाडेटा सेव नहीं हो सका; लोकल फ़ाइलें सुरक्षित हैं" +discard_stop_unconfirmed = "Studio हटाने के रुकने की पुष्टि नहीं हुई; लोकल फ़ाइलें सुरक्षित हैं" +discard_unconfirmed = "Studio हटाने की पुष्टि नहीं हुई; लोकल फ़ाइलें सुरक्षित हैं" +crashed_before_capture = "इस रिकॉर्डिंग में कुछ भी कैप्चर होने से पहले Cap बंद हो गया।" +capture_state_failed = "कैप्चर की स्थिति की पुष्टि नहीं हो सकी: {error}" +completion_task_failed = "रिकॉर्डिंग पूरी करने का काम विफल हुआ: {error}" +stop_task_failed = "रोकने का काम विफल हुआ: {error}" +control_task_failed = "कैप्चर कंट्रोल का काम विफल हुआ: {error}" +restart_hide_failed = "रीस्टार्ट करते समय Cap की विंडो छिपाई नहीं जा सकीं: {error}" +discard_task_failed = "Studio हटाने का काम विफल हुआ: {error}; सफ़ाई की पुष्टि नहीं हुई" +cancel_task_failed = "रद्द करने का काम विफल हुआ: {error}। सफ़ाई की पुष्टि नहीं हुई; फिर से रोकें।" +then_cleanup_unconfirmed_shortcut = "{error}। कैप्चर की सफ़ाई की पुष्टि नहीं हुई। फिर से रोकने के लिए Ctrl+Shift+F9 दबाएँ।" +then_cleanup_unconfirmed_control = "{error}। कैप्चर की सफ़ाई की पुष्टि नहीं हुई। फिर से कोशिश करने के लिए रिकॉर्डिंग का रोकें कंट्रोल इस्तेमाल करें।" +then_shutdown_unconfirmed = "{error}। Studio बंद होने की पुष्टि अब भी नहीं हुई; रिकॉर्डिंग और फ़ाइलें सुरक्षित हैं।" +then_use_stop_if_unsafe = "{error}। अगर रिकॉर्डिंग सुरक्षित रूप से जारी नहीं रह सकती, तो रोकें इस्तेमाल करें।" +then_use_stop_control = "{error}। रिकॉर्डिंग रोकने के लिए उसका रोकें कंट्रोल इस्तेमाल करें।" +then_discard_stopped = "{error}; हटाना/रीस्टार्ट रोका गया, लोकल रिकॉर्डिंग सुरक्षित है" +then_files_preserved = "{error}। विफल रिकॉर्डिंग की फ़ाइलें सुरक्षित रखी गईं।" +then_cleanup_retry = "{error}। सफ़ाई की पुष्टि नहीं हुई; फिर से रोकें।" +low_storage_preserved = "स्टोरेज कम है। आपकी रिकॉर्डिंग फ़ाइलें {path} में सुरक्षित हैं। जगह खाली करें, फिर Cap में रिकॉर्डिंग रिकवर करें।" + +[message.permission] +unavailable = "{permission} की अनुमति उपलब्ध नहीं है। सिस्टम सेटिंग्स > गोपनीयता और सुरक्षा > {permission} में Cap को अनुमति दें, फिर डिवाइस दोबारा चुनें।" + +[message.upload] +offline = "ऑफ़लाइन" +authentication_expired = "ऑथेंटिकेशन की समय-सीमा खत्म हो गई" +no_audio_intent_review = "इस पुरानी रिकॉर्डिंग में ऑडियो सेटिंग सेव नहीं है; फिर से कोशिश करने से पहले इसे जाँच लें" +legacy_no_account = "इस पुराने अपलोड में अकाउंट और ऑडियो सेटिंग सेव नहीं हैं। इसकी लोकल रिकॉर्डिंग सुरक्षित है।" +verification_timed_out = "क्लाउड जाँच का समय खत्म हो गया; लोकल रिकॉर्डिंग सुरक्षित है" +verification_failed_retry = "क्लाउड जाँच विफल हुई; सेव की गई रिकॉर्डिंग फिर से अपलोड होगी" +must_save_first = "अपलोड फिर से शुरू करने से पहले रिकॉर्डिंग को लोकल रूप से सेव करना ज़रूरी है" +sign_in_original = "फिर से कोशिश करने के लिए रिकॉर्डिंग वाले मूल अकाउंट और सर्वर में साइन इन करें" +sign_in_again = "यह रिकॉर्डिंग अपलोड करने के लिए फिर से साइन इन करें" +needs_upgrade = "Instant रिकॉर्डिंग के लिए अपग्रेड किया हुआ प्लान ज़रूरी है" +changed_during = "अपलोड के दौरान लोकल रिकॉर्डिंग बदल गई" +changed_after = "अपलोड के बाद लोकल रिकॉर्डिंग बदल गई; उसे सुरक्षित रखा गया है" +already_queued = "यह रिकॉर्डिंग पहले से अपलोड की कतार में है" +paused = "अपलोड रुका हुआ है; लोकल रिकॉर्डिंग सुरक्षित है" +worker_failed = "अपलोड प्रक्रिया विफल हुई; लोकल रिकॉर्डिंग सुरक्षित है" +empty = "सेव की गई रिकॉर्डिंग खाली है" +no_resumable_state = "इस रिकॉर्डिंग का कोई ऐसा अपलोड नहीं है जिसे फिर से शुरू किया जा सके" +verification_failed = "रिकॉर्डिंग की जाँच विफल हुई: {status}" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 5ae44a227f3..9eba81873b2 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -1183,6 +1183,11 @@ start_failed = "システムがこのカメラを起動できませんでした timeout = "このカメラから映像が届きませんでした。閉じている、接続が切れている、覆われている、またはほかのアプリで使用中の可能性があります。" unavailable_title = "カメラを使用できません" +[camera.blur] +light = "弱" +heavy = "強" +cutout = "切り抜き" + [onboarding.customize] footer = "これらはすべて、いつでも設定で変更できます" subtitle = "キーボードショートカットから保存先まで、すべてをカスタマイズ。Capはあなたの作業スタイルに合わせます。" @@ -1458,9 +1463,11 @@ metadata = "プロジェクトのメタデータを作成中..." starting = "変換を開始しています..." [overlay] -choose_mode = "録画モードを選択" -countdown_tooltip = "録画のカウントダウン" display_fallback = "ディスプレイ{id}" +countdown_title = "録画のカウントダウン" +countdown_body = "「録画を開始」を押してから、数秒待って録画を始めます。" +mode_title = "録画モードを変更" +mode_body = "Instantは録画を止めた瞬間に共有リンクを作成します。Studioは編集用にフル画質で録画します。" [overlay.area] draw = "範囲を描く" @@ -1468,6 +1475,12 @@ free_ratio = "自由" hint = "クリックしてドラッグし、範囲を選択" minimum_size = "最小サイズは{width} x {height}です" too_small = "{width} x {height}は小さすぎます" +more_ratios_title = "その他のアスペクト比" +more_ratios_body = "範囲を4:3、9:16、21:9などの比率に固定します。" +reset_title = "選択範囲をリセット" +reset_body = "範囲を消去して、新しく描き直せるようにします。" +fill_title = "ディスプレイ全体に広げる" +fill_body = "比率を保ったまま、範囲をディスプレイ全体に広げます。" [overlay.camera_only] description = "カメラとマイクだけで録画します" @@ -1487,6 +1500,21 @@ start_recording = "録画を開始" studio = "Studioモード:ローカルで録画して編集" take_screenshot = "スクリーンショットを撮る" +[overlay.window] +size_title = "ウィンドウサイズを変更" +size_body = "録画の前に、ウィンドウを特定の比率や正確なサイズに変更します。" +size_needs_accessibility_title = "サイズ変更にはアクセシビリティが必要です" +size_needs_accessibility_body = "ほかのアプリのウィンドウサイズを変更するには、システム設定でCapのアクセシビリティをオンにしてください。" +size_unavailable_title = "サイズ変更は使用できません" +size_unavailable_body = "Waylandでは、アプリがほかのアプリのウィンドウサイズを変更できません。" +focus_title = "フォーカスモード" +focus_on = "このウィンドウだけを録画します" +focus_off = "このウィンドウだけを録画" +focus_enable = "フォーカスモードをオンにする" +focus_disable = "フォーカスモードをオフにする" +focus_body = "このウィンドウを中央に配置し、このウィンドウだけを録画します。手前を横切るものは動画に映りません。" +focus_body_instant_camera = "このウィンドウを中央に配置します。カメラを使う場合、カメラが動画に残るよう、Instantは引き続き画面の範囲を録画します。" + [permission.guide] accessibility_body = "上の「アクセシビリティ」のリストにドロップし、スイッチがオンになっていることを確認してください。" screen_body = "上の「画面収録とシステムオーディオ録音」のリストにドロップし、スイッチがオンになっていることを確認してください。" @@ -2388,6 +2416,11 @@ refining_size = "サイズを精査中…" refining_time = "時間を精査中…" unavailable = "利用不可" +[editor.export.turbo] +title = "ターボ書き出し" +description = "すべてのCPUコアとGPUを使って、はるかに高速に書き出します" +warning = "書き出しが終わるまで、CPUとGPUをすべて使い、メモリも数倍多く消費します。ほかのアプリの動作が遅くなったり、コンピュータが熱くなってファンの音が大きくなったりすることがあります。" + [editor.modal] cancel_export = "書き出しをキャンセル" cancel_export_message = "書き出しをキャンセルしてもよろしいですか?" @@ -2629,6 +2662,7 @@ normal = "標準" add_shot = "ショットを追加" add_shot_with_look = "ショットを追加 · {look} · {seconds}秒" paused = "一時停止中" +title = "タイムライン" [editor.timeline.empty] add_audio = "音声を追加" @@ -2765,6 +2799,11 @@ speed = "速度" split = "再生ヘッドで分割" split_hint = "再生ヘッドをこのクリップ内に移動してください" volume = "音量" +transition = "トランジション" +transition_none = "なし" +transition_crossfade = "クロスフェード" +transition_fade = "フェード" +transition_seconds = "{seconds}秒" [editor.window.frame] add_tooltip = "フレームを追加" @@ -2911,3 +2950,62 @@ package_not_installed = "パッケージはインストールされませんで ready = "Cap {version}の準備ができました。アップデートを完了するにはCapを再起動してください。" restart_now = "今すぐ再起動" title = "Capをアップデート" + +[message.recording] +task_failed = "録画タスクが失敗しました。" +startup_failed = "録画を開始できませんでした" +ended_unexpectedly = "録画が予期せず終了しました。" +stop_task_failed_plain = "停止タスクが失敗しました。" +stop_not_acknowledged = "キャプチャの停止が確認されませんでした" +capture_state_timed_out = "キャプチャの状態の確認がタイムアウトしました" +pause_not_acknowledged = "キャプチャが要求された一時停止状態を確認しませんでした" +cleanup_unconfirmed_files_kept = "キャプチャの後処理を確認できません。ローカルファイルは保持されています。Ctrl+Shift+F9で停止をやり直してください。" +cleanup_unconfirmed_retry = "キャプチャの後処理を確認できません。停止をやり直してください。" +discard_not_acknowledged = "Studioの破棄の停止が確認されませんでした。ローカルの録画は保持されています" +window_still_resizing = "ウィンドウのサイズ変更がまだ終わっていません。もう一度開始してください。" +startup_cancelled = "録画の開始がキャンセルされました。ローカルファイルは保持されています" +instant_cleanup_unconfirmed = "Instantのキャプチャの後処理を確認できません。ローカルファイルは保持されています" +finalization_no_result = "録画の仕上げが結果を返さずに終了しました。ファイルは保持されています。" +stopped_metadata_unsaved = "キャプチャは停止しましたが、停止時のメタデータを保存できませんでした。ローカルファイルは保持されています" +discard_stop_unconfirmed = "Studioの破棄の停止を確認できません。ローカルファイルは保持されています" +discard_unconfirmed = "Studioの破棄を確認できません。ローカルファイルは保持されています" +crashed_before_capture = "この録画で何もキャプチャされないうちにCapが終了しました。" +capture_state_failed = "キャプチャの状態を確認できませんでした:{error}" +completion_task_failed = "録画の完了処理が失敗しました:{error}" +stop_task_failed = "停止タスクが失敗しました:{error}" +control_task_failed = "キャプチャの制御タスクが失敗しました:{error}" +restart_hide_failed = "やり直し時にCapのウィンドウを非表示にできませんでした:{error}" +discard_task_failed = "Studioの破棄タスクが失敗しました:{error}。後処理を確認できません" +cancel_task_failed = "キャンセルタスクが失敗しました:{error}。後処理を確認できません。停止をやり直してください。" +then_cleanup_unconfirmed_shortcut = "{error}。キャプチャの後処理を確認できません。Ctrl+Shift+F9で停止をやり直してください。" +then_cleanup_unconfirmed_control = "{error}。キャプチャの後処理を確認できません。録画の停止コントロールでやり直してください。" +then_shutdown_unconfirmed = "{error}。Studioの終了を確認できません。録画とファイルは保持されています。" +then_use_stop_if_unsafe = "{error}。録画を安全に続けられない場合は、停止を使ってください。" +then_use_stop_control = "{error}。録画を止めるには、録画の停止コントロールを使ってください。" +then_discard_stopped = "{error}。破棄/やり直しを中止しました。ローカルの録画は保持されています" +then_files_preserved = "{error}。失敗した録画のファイルは保持されています。" +then_cleanup_retry = "{error}。後処理を確認できません。停止をやり直してください。" +low_storage_preserved = "ストレージの空き容量が不足しています。録画ファイルは{path}に保持されています。空き容量を確保してから、Capで録画を復元してください。" + +[message.permission] +unavailable = "{permission}へのアクセスを利用できません。「システム設定」>「プライバシーとセキュリティ」>「{permission}」でCapを許可してから、デバイスをもう一度選択してください。" + +[message.upload] +offline = "オフライン" +authentication_expired = "認証の有効期限が切れました" +no_audio_intent_review = "この古い録画には音声の設定が保存されていません。再試行する前に確認してください" +legacy_no_account = "この古いアップロードにはアカウントと音声の設定が保存されていません。ローカルの録画は保持されています。" +verification_timed_out = "クラウドでの確認がタイムアウトしました。ローカルの録画は保持されています" +verification_failed_retry = "クラウドでの確認に失敗しました。保存された録画をもう一度アップロードします" +must_save_first = "アップロードを再開するには、先に録画をローカルに保存する必要があります" +sign_in_original = "再試行するには、録画したときのアカウントとサーバーにサインインしてください" +sign_in_again = "この録画をアップロードするには、もう一度サインインしてください" +needs_upgrade = "Instant録画には上位プランが必要です" +changed_during = "アップロード中にローカルの録画が変更されました" +changed_after = "アップロード後にローカルの録画が変更されました。録画は保持されています" +already_queued = "この録画はすでにアップロード待ちです" +paused = "アップロードを一時停止しました。ローカルの録画は保持されています" +worker_failed = "アップロード処理が失敗しました。ローカルの録画は保持されています" +empty = "保存された録画は空です" +no_resumable_state = "この録画には再開できるアップロード状態がありません" +verification_failed = "録画の確認に失敗しました:{status}" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index d7cfd598d91..8b792a0af9f 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -1183,6 +1183,11 @@ start_failed = "系统无法启动此摄像头。它可能不可用,或正被 timeout = "未从此摄像头收到任何画面。它可能已关闭、断开连接、被遮挡,或正被其他应用使用。" unavailable_title = "摄像头不可用" +[camera.blur] +light = "轻" +heavy = "重" +cutout = "抠像" + [onboarding.customize] footer = "以上内容都可以随时在设置中更改" subtitle = "从键盘快捷键到存储,一切皆可自定义。Cap 会适应你的工作方式。" @@ -1458,9 +1463,11 @@ metadata = "正在创建项目元数据..." starting = "正在开始转换..." [overlay] -choose_mode = "选择录制模式" -countdown_tooltip = "录制倒计时" display_fallback = "显示器 {id}" +countdown_title = "录制倒计时" +countdown_body = "按下“开始录制”后,等待几秒再开始录制。" +mode_title = "更改录制模式" +mode_body = "Instant 会在你停止录制的那一刻给你一个分享链接。Studio 以完整画质录制,方便后期编辑。" [overlay.area] draw = "绘制一个区域" @@ -1468,6 +1475,12 @@ free_ratio = "自由" hint = "点击并拖动以选择区域" minimum_size = "最小尺寸为 {width} x {height}" too_small = "{width} x {height} 太小" +more_ratios_title = "更多宽高比" +more_ratios_body = "将区域锁定为 4:3、9:16 或 21:9 等比例。" +reset_title = "重置选区" +reset_body = "清除该区域,以便重新绘制。" +fill_title = "填满屏幕" +fill_body = "保持比例,将区域扩大到整个屏幕。" [overlay.camera_only] description = "仅使用摄像头和麦克风录制" @@ -1487,6 +1500,21 @@ start_recording = "开始录制" studio = "Studio 模式:在本地录制和编辑" take_screenshot = "截图" +[overlay.window] +size_title = "更改窗口大小" +size_body = "在录制前将窗口调整为某个比例或精确尺寸。" +size_needs_accessibility_title = "调整大小需要辅助功能权限" +size_needs_accessibility_body = "在系统设置中为 Cap 打开辅助功能,即可调整其他应用的窗口大小。" +size_unavailable_title = "无法调整大小" +size_unavailable_body = "Wayland 不允许应用调整其他应用的窗口大小。" +focus_title = "聚焦模式" +focus_on = "将只录制此窗口" +focus_off = "只录制此窗口" +focus_enable = "开启聚焦模式" +focus_disable = "关闭聚焦模式" +focus_body = "将此窗口居中并只录制它。从它前面经过的任何内容都不会出现在视频中。" +focus_body_instant_camera = "将此窗口居中。使用摄像头时,Instant 仍会录制屏幕区域,以便摄像头画面保留在视频中。" + [permission.guide] accessibility_body = "将它拖放到上方的“辅助功能”列表中,然后确认它的开关已打开。" screen_body = "将它拖放到上方的“录屏与系统录音”列表中,然后确认它的开关已打开。" @@ -2388,6 +2416,11 @@ refining_size = "正在精确估算大小…" refining_time = "正在精确估算用时…" unavailable = "不可用" +[editor.export.turbo] +title = "极速导出" +description = "使用所有 CPU 核心和 GPU,大幅加快导出" +warning = "导出完成前会占用全部 CPU、GPU 以及数倍的内存。其他应用可能会变慢,电脑也可能发热、风扇声变大。" + [editor.modal] cancel_export = "取消导出" cancel_export_message = "确定要取消导出吗?" @@ -2629,6 +2662,7 @@ normal = "常规" add_shot = "添加镜头" add_shot_with_look = "添加镜头 · {look} · {seconds} 秒" paused = "已暂停" +title = "时间线" [editor.timeline.empty] add_audio = "添加音频" @@ -2765,6 +2799,11 @@ speed = "速度" split = "在播放头处拆分" split_hint = "将播放头移到此片段内" volume = "音量" +transition = "转场" +transition_none = "无" +transition_crossfade = "交叉淡化" +transition_fade = "淡入淡出" +transition_seconds = "{seconds} 秒" [editor.window.frame] add_tooltip = "添加边框" @@ -2911,3 +2950,62 @@ package_not_installed = "软件包未安装。请批准系统提示,或从 Cap ready = "Cap {version} 已准备就绪。重新启动 Cap 以完成更新。" restart_now = "立即重新启动" title = "更新 Cap" + +[message.recording] +task_failed = "录制任务失败。" +startup_failed = "录制启动失败" +ended_unexpectedly = "录制意外结束。" +stop_task_failed_plain = "停止任务失败。" +stop_not_acknowledged = "未确认捕获已停止" +capture_state_timed_out = "确认捕获状态超时" +pause_not_acknowledged = "捕获未确认所请求的暂停状态" +cleanup_unconfirmed_files_kept = "无法确认捕获清理已完成。本地文件已保留;请按 Ctrl+Shift+F9 重试停止。" +cleanup_unconfirmed_retry = "无法确认捕获清理已完成;请重试停止。" +discard_not_acknowledged = "未确认 Studio 丢弃已停止;本地录制已保留" +window_still_resizing = "窗口仍在调整大小。请重新开始。" +startup_cancelled = "录制启动已取消;本地文件已保留" +instant_cleanup_unconfirmed = "无法确认 Instant 捕获清理已完成;本地文件已保留" +finalization_no_result = "录制收尾未返回结果。你的文件已保留。" +stopped_metadata_unsaved = "捕获已停止,但无法保存停止信息;本地文件已保留" +discard_stop_unconfirmed = "无法确认 Studio 丢弃已停止;本地文件已保留" +discard_unconfirmed = "无法确认 Studio 丢弃已完成;本地文件已保留" +crashed_before_capture = "Cap 在此录制捕获到任何内容之前就已关闭。" +capture_state_failed = "无法确认捕获状态:{error}" +completion_task_failed = "录制收尾任务失败:{error}" +stop_task_failed = "停止任务失败:{error}" +control_task_failed = "捕获控制任务失败:{error}" +restart_hide_failed = "重新开始时无法隐藏 Cap 窗口:{error}" +discard_task_failed = "Studio 丢弃任务失败:{error};无法确认清理已完成" +cancel_task_failed = "取消任务失败:{error}。无法确认清理已完成;请重试停止。" +then_cleanup_unconfirmed_shortcut = "{error}。无法确认捕获清理已完成。请按 Ctrl+Shift+F9 重试停止。" +then_cleanup_unconfirmed_control = "{error}。无法确认捕获清理已完成。请使用录制的停止控件重试。" +then_shutdown_unconfirmed = "{error}。无法确认 Studio 已关闭;录制和文件已保留。" +then_use_stop_if_unsafe = "{error}。如果录制无法安全继续,请使用“停止”。" +then_use_stop_control = "{error}。请使用录制的停止控件来停止录制。" +then_discard_stopped = "{error};丢弃/重新开始已停止,本地录制已保留" +then_files_preserved = "{error}。失败录制的文件已保留。" +then_cleanup_retry = "{error}。无法确认清理已完成;请重试停止。" +low_storage_preserved = "存储空间不足。你的录制文件已保留在 {path}。请释放空间,然后在 Cap 中恢复该录制。" + +[message.permission] +unavailable = "无法使用{permission}权限。请在“系统设置”>“隐私与安全性”>“{permission}”中允许 Cap,然后重新选择该设备。" + +[message.upload] +offline = "离线" +authentication_expired = "登录已过期" +no_audio_intent_review = "这个较早的录制没有保存音频设置;请先检查后再重试" +legacy_no_account = "这个较早的上传没有保存账户和音频设置。其本地录制已保留。" +verification_timed_out = "云端验证超时;本地录制已保留" +verification_failed_retry = "云端验证失败;已保存的录制将重新上传" +must_save_first = "录制必须先保存到本地,才能继续上传" +sign_in_original = "请登录录制时使用的账户和服务器后重试" +sign_in_again = "请重新登录以上传此录制" +needs_upgrade = "Instant 录制需要升级套餐" +changed_during = "本地录制在上传期间发生了变化" +changed_after = "本地录制在上传后发生了变化;已将其保留" +already_queued = "此录制已在上传队列中" +paused = "上传已暂停;本地录制已保留" +worker_failed = "上传进程失败;本地录制已保留" +empty = "已保存的录制为空" +no_resumable_state = "此录制没有可继续的上传状态" +verification_failed = "录制验证失败:{status}" \ No newline at end of file diff --git a/apps/desktop-gpui/src/controls_window.rs b/apps/desktop-gpui/src/controls_window.rs index 37a723cd7a4..a2b5bbc9d5a 100644 --- a/apps/desktop-gpui/src/controls_window.rs +++ b/apps/desktop-gpui/src/controls_window.rs @@ -573,7 +573,7 @@ fn recording_issue_text(error: String) -> impl IntoElement + Styled { { tr::controls::disk_full_resume().to_owned() } else { - error + crate::i18n::messages::localize(&error) }, ) } diff --git a/apps/desktop-gpui/src/editor_export.rs b/apps/desktop-gpui/src/editor_export.rs index 1d176ad0c61..69d83c3f84c 100644 --- a/apps/desktop-gpui/src/editor_export.rs +++ b/apps/desktop-gpui/src/editor_export.rs @@ -467,8 +467,8 @@ fn export_file_name(pretty_name: &str, extension: &str) -> String { } fn format_estimate_range(range: [f64; 2], time: bool) -> String { - type Single = fn(f64) -> String; - type Range = fn(f64, f64) -> String; + type Single = fn(String) -> String; + type Range = fn(String, String) -> String; if range[1] < 1.0 { return if time { tr::editor::export::estimate::under_second() @@ -514,9 +514,9 @@ fn format_estimate_range(range: [f64; 2], time: bool) -> String { let lower = ((range[0] / scale * precision).round() / precision).max(1.0 / precision); let upper = ((range[1] / scale * precision).round() / precision).max(lower); if lower == upper { - single(lower) + single(crate::i18n::decimal(lower)) } else { - range_of(lower, upper) + range_of(crate::i18n::decimal(lower), crate::i18n::decimal(upper)) } } @@ -2300,8 +2300,8 @@ impl EditorWindow { .gap(px(4.)) .child(self.export_toggle_row( "export-turbo", - "Turbo export", - "Exports much faster using every CPU core and the GPU", + tr::editor::export::turbo::title(), + tr::editor::export::turbo::description(), ui.turbo, cx.listener(|this, _, window, cx| { if let Some(ui) = this.export.as_mut() { @@ -2337,9 +2337,7 @@ impl EditorWindow { .text_size(px(11.5)) .line_height(px(15.)) .text_color(Hsla::from(editor.text_2)) - .child( - "Uses all of your CPU, the GPU and several times more memory until the export finishes. Other apps may slow down, and your computer may get warm and loud.", - ), + .child(tr::editor::export::turbo::warning()), ), ) }) diff --git a/apps/desktop-gpui/src/editor_panels.rs b/apps/desktop-gpui/src/editor_panels.rs index 098fafba179..1eaa738e15c 100644 --- a/apps/desktop-gpui/src/editor_panels.rs +++ b/apps/desktop-gpui/src/editor_panels.rs @@ -7435,7 +7435,7 @@ impl EditorWindow { .text_color(Hsla::from(theme.editor.text_2)) .child(tr::editor::panel::camera3d::header( look, - format!("{duration:.1}"), + crate::i18n::decimal(format!("{duration:.1}")), )), ) .child( diff --git a/apps/desktop-gpui/src/editor_timeline.rs b/apps/desktop-gpui/src/editor_timeline.rs index 2df7a8ebfea..b4a22b8b16c 100644 --- a/apps/desktop-gpui/src/editor_timeline.rs +++ b/apps/desktop-gpui/src/editor_timeline.rs @@ -2513,7 +2513,7 @@ fn camera3d_ghost_label(width: f32, duration: f64) -> Option { if width >= 208. { Some(tr::editor::timeline::add_shot_with_look( tr::editor::panel::camera3d::look::glide_across(), - format!("{duration:.1}"), + crate::i18n::decimal(format!("{duration:.1}")), )) } else if width >= 84. { Some(tr::editor::timeline::add_shot().to_string()) @@ -5602,7 +5602,7 @@ pub(crate) fn render_preparing_timeline( .items_center() .pl(px(10.)) .text_size(px(12.)) - .child("Clip"), + .child(tr::editor::timeline::track::clip()), ) .child( div() @@ -5643,7 +5643,7 @@ pub(crate) fn render_preparing_timeline( .bottom_0() .text_size(px(12.)) .text_color(Hsla::from(theme.editor.text_3)) - .child("Timeline"), + .child(tr::editor::timeline::title()), ), ) .child( diff --git a/apps/desktop-gpui/src/editor_window.rs b/apps/desktop-gpui/src/editor_window.rs index 4daabf19dd0..02e2e44f8de 100644 --- a/apps/desktop-gpui/src/editor_window.rs +++ b/apps/desktop-gpui/src/editor_window.rs @@ -8808,20 +8808,31 @@ impl EditorWindow { let duration = transition.map_or(DEFAULT_CLIP_TRANSITION_DURATION, |current| current.duration); let kinds = [ - ("None", None), - ("Crossfade", Some(ClipTransitionType::CrossFade)), - ("Fade", Some(ClipTransitionType::FadeThroughBlack)), + ("None", tr::editor::window::clip_menu::transition_none(), None), + ( + "Crossfade", + tr::editor::window::clip_menu::transition_crossfade(), + Some(ClipTransitionType::CrossFade), + ), + ( + "Fade", + tr::editor::window::clip_menu::transition_fade(), + Some(ClipTransitionType::FadeThroughBlack), + ), ]; card.child( div() .flex() .flex_col() .gap(px(4.)) - .child(clip_menu_section(&theme, "TRANSITION")) - .child(chip_row().children(kinds.into_iter().map(|(label, kind)| { + .child(clip_menu_section( + &theme, + tr::editor::window::clip_menu::transition(), + )) + .child(chip_row().children(kinds.into_iter().map(|(id, label, kind)| { let selected = transition.map(|current| current.kind) == kind; chip( - SharedString::from(format!("clip-transition-{label}")), + SharedString::from(format!("clip-transition-{id}")), selected, label.to_string(), ) @@ -8848,7 +8859,9 @@ impl EditorWindow { "clip-transition-{seconds}s" )), (current.duration - seconds).abs() < 1e-3, - format!("{seconds}s"), + tr::editor::window::clip_menu::transition_seconds( + crate::i18n::decimal(seconds), + ), ) .on_click( cx.listener(move |this, _, window, cx| { diff --git a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs index b7a04b038a8..48bcfe7edcd 100644 --- a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs +++ b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs @@ -326,8 +326,65 @@ fn dialog_surfaces_match_the_literals_they_replaced() { (tr::upload::status::sign_in(), "Sign in to retry upload"), (tr::upload::status::uploaded(), "Uploaded"), (tr::upload::status::uploading(), "Uploading"), - (tr::overlay::choose_mode(), "Choose recording mode"), - (tr::overlay::countdown_tooltip(), "Recording countdown"), + (tr::overlay::mode_title(), "Change recording mode"), + ( + tr::overlay::mode_body(), + "Instant gives you a link to share the moment you stop. Studio records in full quality for editing.", + ), + (tr::overlay::countdown_title(), "Recording countdown"), + ( + tr::overlay::countdown_body(), + "Wait a few seconds after you press Start before recording begins.", + ), + (tr::overlay::area::more_ratios_title(), "More aspect ratios"), + ( + tr::overlay::area::more_ratios_body(), + "Lock the area to a ratio such as 4:3, 9:16 or 21:9.", + ), + (tr::overlay::area::reset_title(), "Reset selection"), + ( + tr::overlay::area::reset_body(), + "Clear the area so you can draw a new one.", + ), + (tr::overlay::area::fill_title(), "Fill display"), + ( + tr::overlay::area::fill_body(), + "Grow the area to cover the display, keeping its ratio.", + ), + (tr::overlay::window::size_title(), "Change window size"), + ( + tr::overlay::window::size_body(), + "Resize the window to a ratio or an exact size before you record.", + ), + ( + tr::overlay::window::size_needs_accessibility_title(), + "Resizing needs Accessibility", + ), + ( + tr::overlay::window::size_needs_accessibility_body(), + "Turn on Accessibility for Cap in System Settings to resize other apps' windows.", + ), + ( + tr::overlay::window::size_unavailable_title(), + "Resizing isn't available", + ), + ( + tr::overlay::window::size_unavailable_body(), + "Wayland doesn't let apps resize other apps' windows.", + ), + (tr::overlay::window::focus_title(), "Focus mode"), + (tr::overlay::window::focus_on(), "Recording only this window"), + (tr::overlay::window::focus_off(), "Record only this window"), + (tr::overlay::window::focus_enable(), "Enable Focus mode"), + (tr::overlay::window::focus_disable(), "Turn off Focus mode"), + ( + tr::overlay::window::focus_body(), + "Centres this window and records only it. Anything that moves in front of it stays out of the video.", + ), + ( + tr::overlay::window::focus_body_instant_camera(), + "Centres this window. With a camera, Instant still records the screen region so the camera stays in the video.", + ), (tr::overlay::area::draw(), "Draw an area"), (tr::overlay::area::free_ratio(), "Free"), ( diff --git a/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs b/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs index aae6efb6922..39bda5e3c16 100644 --- a/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs +++ b/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs @@ -17,6 +17,7 @@ pub(super) fn is_editor_shell_surface_key(key: &str) -> bool { } const FORMATTED: &[&str] = &[ + "editor.window.clip_menu.transition_seconds", "editor.clips.append_error", "editor.clips.clip", "editor.clips.error.cannot_decode", @@ -1136,6 +1137,23 @@ fn editor_shell_surfaces_match_the_literals_they_replaced() { (tr::ui::coming_soon(), "Coming Soon"), (tr::ui::selection::delete(), "Delete"), (tr::ui::selection::done(), "Done"), + (tr::editor::export::turbo::title(), "Turbo export"), + ( + tr::editor::export::turbo::description(), + "Exports much faster using every CPU core and the GPU", + ), + ( + tr::editor::export::turbo::warning(), + "Uses all of your CPU, the GPU and several times more memory until the export finishes. Other apps may slow down, and your computer may get warm and loud.", + ), + (tr::editor::window::clip_menu::transition(), "TRANSITION"), + (tr::editor::window::clip_menu::transition_none(), "None"), + ( + tr::editor::window::clip_menu::transition_crossfade(), + "Crossfade", + ), + (tr::editor::window::clip_menu::transition_fade(), "Fade"), + (tr::editor::timeline::title(), "Timeline"), (tr::ui::selection::select_all(), "Select all"), ]; for (translated, literal) in pairs { @@ -1196,6 +1214,12 @@ fn editor_shell_messages_format_like_the_code_they_replaced() { let path = std::path::Path::new("/tmp/Demo.cap"); let message = "Source Cap project is still recording".to_string(); + for seconds in [0.25, 0.5, 1.0] { + assert_eq!( + tr::editor::window::clip_menu::transition_seconds(seconds), + format!("{seconds}s") + ); + } assert_eq!( tr::editor::clips::append_error(&message), format!("Failed to add clip: {message}") diff --git a/apps/desktop-gpui/src/i18n/messages.rs b/apps/desktop-gpui/src/i18n/messages.rs new file mode 100644 index 00000000000..fe3aa40fa56 --- /dev/null +++ b/apps/desktop-gpui/src/i18n/messages.rs @@ -0,0 +1,267 @@ +//! Recording and upload errors stay English where they are made: they are +//! logged, matched to classify failures and saved with the upload state. +//! `localize` translates the ones Cap writes itself where they are shown. +//! Text it does not know, such as an engine error, passes through unchanged. + +use super::tr; +use crate::permissions::OSPermission; + +#[cfg(test)] +pub(super) const KEY_PREFIX: &str = "message."; + +const EXACT: &[(&str, fn() -> &'static str)] = &[ + ("Recording task failed.", tr::message::recording::task_failed), + ("Recording startup failed", tr::message::recording::startup_failed), + ("Recording ended unexpectedly.", tr::message::recording::ended_unexpectedly), + ("Stop task failed.", tr::message::recording::stop_task_failed_plain), + ("Capture stop was not acknowledged", tr::message::recording::stop_not_acknowledged), + ("Timed out confirming capture state", tr::message::recording::capture_state_timed_out), + ( + "Capture did not acknowledge the requested pause state", + tr::message::recording::pause_not_acknowledged, + ), + ( + "Capture cleanup is unconfirmed. Local files are preserved; use Ctrl+Shift+F9 to retry Stop.", + tr::message::recording::cleanup_unconfirmed_files_kept, + ), + ( + "Capture cleanup is unconfirmed; retry Stop.", + tr::message::recording::cleanup_unconfirmed_retry, + ), + ( + "Studio discard stop was not acknowledged; local recording retained", + tr::message::recording::discard_not_acknowledged, + ), + ( + "The window is still being resized. Try starting again.", + tr::message::recording::window_still_resizing, + ), + ( + "Recording startup cancelled; local files preserved", + tr::message::recording::startup_cancelled, + ), + ( + "Instant capture cleanup is unconfirmed; local files preserved", + tr::message::recording::instant_cleanup_unconfirmed, + ), + ( + "Recording finalization ended without a result. Your files were retained.", + tr::message::recording::finalization_no_result, + ), + ( + "Capture stopped, but stopped metadata could not be saved; local files preserved", + tr::message::recording::stopped_metadata_unsaved, + ), + ( + "Studio discard stop is unconfirmed; local files preserved", + tr::message::recording::discard_stop_unconfirmed, + ), + ( + "Studio discard is unconfirmed; local files preserved", + tr::message::recording::discard_unconfirmed, + ), + ( + "Cap closed before this recording captured anything.", + tr::message::recording::crashed_before_capture, + ), + ("offline", tr::message::upload::offline), + ("Authentication expired", tr::message::upload::authentication_expired), + ( + "This older recording has no saved audio intent; review it before retrying", + tr::message::upload::no_audio_intent_review, + ), + ( + "This older upload has no saved account and audio intent. Its local recording is preserved.", + tr::message::upload::legacy_no_account, + ), + ( + "Cloud verification timed out; the local recording is preserved", + tr::message::upload::verification_timed_out, + ), + ( + "Cloud verification failed; the saved recording will upload again", + tr::message::upload::verification_failed_retry, + ), + ( + "Recording must be saved locally before its upload can resume", + tr::message::upload::must_save_first, + ), + ( + "Sign in to the original recording account and server to retry", + tr::message::upload::sign_in_original, + ), + ("Sign in again to upload this recording", tr::message::upload::sign_in_again), + ("Instant recording requires an upgraded plan", tr::message::upload::needs_upgrade), + ("The local recording changed during upload", tr::message::upload::changed_during), + ( + "The local recording changed after upload; it has been preserved", + tr::message::upload::changed_after, + ), + ("This recording is already queued for upload", tr::message::upload::already_queued), + ("Upload paused; the local recording is preserved", tr::message::upload::paused), + ( + "The upload worker failed; the local recording is preserved", + tr::message::upload::worker_failed, + ), + ("The saved recording is empty", tr::message::upload::empty), + ( + "This recording has no resumable upload state", + tr::message::upload::no_resumable_state, + ), +]; + +/// `prefix{inner}suffix`. `nested` translates the inner part too, for an +/// inner part that is itself one of these messages. +struct Wrapped { + prefix: &'static str, + suffix: &'static str, + nested: bool, + translate: fn(String) -> String, +} + +const WRAPPED: &[Wrapped] = &[ + Wrapped { + prefix: "", + suffix: ". Capture cleanup is unconfirmed. Use Ctrl+Shift+F9 to retry Stop.", + nested: true, + translate: |error| tr::message::recording::then_cleanup_unconfirmed_shortcut(error), + }, + Wrapped { + prefix: "", + suffix: ". Capture cleanup is unconfirmed. Use your recording Stop control to retry.", + nested: true, + translate: |error| tr::message::recording::then_cleanup_unconfirmed_control(error), + }, + Wrapped { + prefix: "", + suffix: ". Studio shutdown remains unconfirmed; recording and files retained.", + nested: true, + translate: |error| tr::message::recording::then_shutdown_unconfirmed(error), + }, + Wrapped { + prefix: "", + suffix: ". Use Stop if recording cannot continue safely.", + nested: true, + translate: |error| tr::message::recording::then_use_stop_if_unsafe(error), + }, + Wrapped { + prefix: "", + suffix: ". Use your recording Stop control to stop recording.", + nested: true, + translate: |error| tr::message::recording::then_use_stop_control(error), + }, + Wrapped { + prefix: "", + suffix: "; discard/restart stopped, local recording retained", + nested: true, + translate: |error| tr::message::recording::then_discard_stopped(error), + }, + Wrapped { + prefix: "", + suffix: ". Failed recording files were preserved.", + nested: true, + translate: |error| tr::message::recording::then_files_preserved(error), + }, + Wrapped { + prefix: "Cancel task failed: ", + suffix: ". Cleanup is unconfirmed; retry Stop.", + nested: true, + translate: |error| tr::message::recording::cancel_task_failed(error), + }, + Wrapped { + prefix: "", + suffix: ". Cleanup is unconfirmed; retry Stop.", + nested: true, + translate: |error| tr::message::recording::then_cleanup_retry(error), + }, + Wrapped { + prefix: "Studio discard task failed: ", + suffix: "; cleanup is unconfirmed", + nested: true, + translate: |error| tr::message::recording::discard_task_failed(error), + }, + Wrapped { + prefix: "Could not confirm capture state: ", + suffix: "", + nested: true, + translate: |error| tr::message::recording::capture_state_failed(error), + }, + Wrapped { + prefix: "Recording completion task failed: ", + suffix: "", + nested: true, + translate: |error| tr::message::recording::completion_task_failed(error), + }, + Wrapped { + prefix: "Stop task failed: ", + suffix: "", + nested: true, + translate: |error| tr::message::recording::stop_task_failed(error), + }, + Wrapped { + prefix: "Capture control task failed: ", + suffix: "", + nested: true, + translate: |error| tr::message::recording::control_task_failed(error), + }, + Wrapped { + prefix: "Restart could not hide Cap windows: ", + suffix: "", + nested: true, + translate: |error| tr::message::recording::restart_hide_failed(error), + }, + Wrapped { + prefix: "Low storage. Your recording files are preserved at ", + suffix: ". Free up space, then recover the recording in Cap.", + nested: false, + translate: |path| tr::message::recording::low_storage_preserved(path), + }, + Wrapped { + prefix: "Recording verification failed: ", + suffix: "", + nested: false, + translate: |status| tr::message::upload::verification_failed(status), + }, +]; + +/// Wrappers nest only a few deep; the bound keeps hostile text cheap. +const MAX_DEPTH: u8 = 4; + +pub fn localize(message: &str) -> String { + localize_at(message, 0) +} + +fn localize_at(message: &str, depth: u8) -> String { + if let Some((_, translate)) = EXACT.iter().find(|(english, _)| *english == message) { + return translate().to_string(); + } + if let Some(permission) = OSPermission::ALL + .iter() + .find(|permission| permission.unavailable_message() == message) + { + return tr::message::permission::unavailable(permission.display_name()); + } + if depth < MAX_DEPTH { + for wrapped in WRAPPED { + let Some(inner) = message + .strip_prefix(wrapped.prefix) + .and_then(|rest| rest.strip_suffix(wrapped.suffix)) + else { + continue; + }; + if inner.is_empty() { + continue; + } + let inner = if wrapped.nested { + localize_at(inner, depth + 1) + } else { + inner.to_string() + }; + return (wrapped.translate)(inner); + } + } + message.to_string() +} + +#[cfg(test)] +mod tests; diff --git a/apps/desktop-gpui/src/i18n/messages/tests.rs b/apps/desktop-gpui/src/i18n/messages/tests.rs new file mode 100644 index 00000000000..0202b7b7e4a --- /dev/null +++ b/apps/desktop-gpui/src/i18n/messages/tests.rs @@ -0,0 +1,144 @@ +use super::*; +use crate::i18n::{KEYS, LOCALE_ENV, Locale, init}; + +#[test] +fn english_messages_read_exactly_as_before() { + for (english, translate) in EXACT { + assert_eq!(translate(), *english); + assert_eq!(localize(english), *english); + } + for wrapped in WRAPPED { + let message = format!("{}disk full{}", wrapped.prefix, wrapped.suffix); + assert_eq!((wrapped.translate)("disk full".into()), message); + assert_eq!(localize(&message), message); + } + for permission in OSPermission::ALL { + let message = permission.unavailable_message(); + assert_eq!(localize(&message), message); + } +} + +#[test] +fn every_message_is_still_written_by_the_app() { + let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); + let i18n = src.join("i18n"); + let mut sources = String::new(); + let mut pending = vec![src]; + while let Some(dir) = pending.pop() { + for entry in std::fs::read_dir(&dir).unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + if path != i18n { + pending.push(path); + } + } else if path.extension().is_some_and(|extension| extension == "rs") { + sources.push_str(&std::fs::read_to_string(&path).unwrap()); + } + } + } + let texts = EXACT.iter().map(|(english, _)| *english).chain( + WRAPPED + .iter() + .flat_map(|wrapped| [wrapped.prefix, wrapped.suffix]) + .filter(|text| !text.is_empty()), + ); + for text in texts { + assert!( + sources.contains(text), + "no code writes {text:?} any more; update i18n::messages" + ); + } +} + +#[test] +fn unknown_and_hostile_text_passes_through() { + for message in ["", "encoder died", "Stop task failed: ", ". Cleanup is unconfirmed; retry Stop."] { + assert_eq!(localize(message), message); + } + let deep = format!( + "{}x{}", + "Stop task failed: ".repeat(64), + ". Use Stop if recording cannot continue safely.".repeat(64) + ); + assert_eq!(localize(&deep), deep); +} + +#[test] +fn messages_follow_the_language() { + const CHILD: &str = "CAP_GPUI_TEST_MESSAGES_LANGUAGE"; + if std::env::var_os(CHILD).is_none() { + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "i18n::messages::tests::messages_follow_the_language", + "--nocapture", + ]) + .env(CHILD, "1") + .env_remove(LOCALE_ENV) + .output() + .unwrap(); + assert!( + output.status.success(), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + return; + } + + init(Some(Locale::Ja.code())); + assert_eq!(localize("Recording task failed."), "録画タスクが失敗しました。"); + assert_eq!( + localize( + "Stop task failed: Recording startup cancelled; local files preserved. Capture cleanup is unconfirmed. Use Ctrl+Shift+F9 to retry Stop." + ), + "停止タスクが失敗しました:録画の開始がキャンセルされました。ローカルファイルは保持されています。キャプチャの後処理を確認できません。Ctrl+Shift+F9で停止をやり直してください。" + ); + assert_eq!( + localize("Stop task failed: encoder died"), + "停止タスクが失敗しました:encoder died" + ); + assert_eq!( + localize(&OSPermission::Camera.unavailable_message()), + "カメラへのアクセスを利用できません。「システム設定」>「プライバシーとセキュリティ」>「カメラ」でCapを許可してから、デバイスをもう一度選択してください。" + ); + assert_eq!( + localize("Low storage. Your recording files are preserved at /Users/me/Cap/Stop task failed: x. Free up space, then recover the recording in Cap."), + "ストレージの空き容量が不足しています。録画ファイルは/Users/me/Cap/Stop task failed: xに保持されています。空き容量を確保してから、Capで録画を復元してください。" + ); + + for (locale, shown) in [ + (Locale::En, "1.5"), + (Locale::ZhCn, "1.5"), + (Locale::Ja, "1.5"), + (Locale::Fr, "1,5"), + (Locale::Es, "1,5"), + (Locale::De, "1,5"), + (Locale::Hi, "1.5"), + ] { + init(Some(locale.code())); + assert_eq!(crate::i18n::decimal(1.5), shown, "{}", locale.code()); + assert_eq!(crate::i18n::decimal(12.0), "12", "{}", locale.code()); + } + + for locale in Locale::ALL { + init(Some(locale.code())); + for (english, _) in EXACT { + let shown = localize(english); + assert!(!shown.is_empty(), "{} {english}", locale.code()); + if locale != Locale::En { + assert_ne!(shown, *english, "{} {english}", locale.code()); + } + } + } + init(Some(Locale::En.code())); +} + +#[test] +fn every_message_key_has_one_english_source() { + let keys = KEYS + .iter() + .filter(|key| key.starts_with(KEY_PREFIX)) + .count(); + assert_eq!(keys, EXACT.len() + WRAPPED.len() + 1); +} diff --git a/apps/desktop-gpui/src/i18n/mod.rs b/apps/desktop-gpui/src/i18n/mod.rs index 85873876e30..31706fe0c85 100644 --- a/apps/desktop-gpui/src/i18n/mod.rs +++ b/apps/desktop-gpui/src/i18n/mod.rs @@ -23,6 +23,16 @@ pub fn current() -> Locale { Locale::ALL[index()] } +/// A number for a translated sentence, with the decimal comma French, +/// Spanish and German write (`1,5 GB`). +pub fn decimal(value: impl std::fmt::Display) -> String { + let text = value.to_string(); + match current() { + Locale::Fr | Locale::Es | Locale::De => text.replace('.', ","), + _ => text, + } +} + /// What the language picker stores: follow the system, or one catalog. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Preference { @@ -139,6 +149,8 @@ pub fn apply(preference: Preference, cx: &mut App) { cx.refresh_windows(); } +pub mod messages; + #[cfg(test)] mod dialogs_surface_tests; #[cfg(test)] diff --git a/apps/desktop-gpui/src/i18n/surface_tests.rs b/apps/desktop-gpui/src/i18n/surface_tests.rs index 9da560ce9e0..fa2636664ee 100644 --- a/apps/desktop-gpui/src/i18n/surface_tests.rs +++ b/apps/desktop-gpui/src/i18n/surface_tests.rs @@ -77,6 +77,9 @@ fn main_surfaces_match_the_literals_they_replaced() { ), (tr::main::mode::info::title(), "Recording Modes"), (tr::main::mode::info_tooltip(), "Recording mode info"), + (tr::camera::blur::light(), "Light"), + (tr::camera::blur::heavy(), "Heavy"), + (tr::camera::blur::cutout(), "Cutout"), (tr::main::target::display(), "Display"), (tr::main::target::window(), "Window"), (tr::main::target::area(), "Area"), diff --git a/apps/desktop-gpui/src/i18n/tests.rs b/apps/desktop-gpui/src/i18n/tests.rs index 9d677ab79e6..7937a1b8f1e 100644 --- a/apps/desktop-gpui/src/i18n/tests.rs +++ b/apps/desktop-gpui/src/i18n/tests.rs @@ -503,6 +503,7 @@ fn english_matches_the_literals_it_replaced() { && !super::dialogs_surface_tests::is_dialogs_surface_key(key) && !super::editor_panels_surface_tests::is_editor_panels_surface_key(key) && !super::editor_shell_surface_tests::is_editor_shell_surface_key(key) + && !key.starts_with(super::messages::KEY_PREFIX) }) .count() // `tray.version`, `excluded_windows.missing_body`, `self_host.confirm_change`, @@ -859,6 +860,7 @@ fn every_key_is_used_outside_the_catalog() { } } } + sources.push_str(&std::fs::read_to_string(i18n.join("messages.rs")).unwrap()); let unused: Vec<&str> = KEYS .iter() .copied() diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index aa83e9893cd..c07ea0c4b85 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -2519,7 +2519,10 @@ impl MainWindow { .ok(); return; } - let available = format!("{:.2}", storage.available_bytes as f64 / 1_073_741_824.0); + let available = crate::i18n::decimal(format!( + "{:.2}", + storage.available_bytes as f64 / 1_073_741_824.0 + )); let detail = if can_start { tr::main::storage::can_start(&available) } else { @@ -3746,14 +3749,15 @@ impl MainWindow { fn render_idle_error(&self, error: String, cx: &mut Context) -> impl IntoElement { let theme = self.theme; let copied = self.copied_error.as_deref() == Some(error.as_str()); - let copy_error = error.clone(); + let shown = crate::i18n::messages::localize(&error); + let copy_text = shown.clone(); let body = div() .flex() .flex_col() .gap(px(14.)) .child( - recording_error_text(error) + recording_error_text(shown) .rounded(px(8.)) .bg(theme.gray_3) .px(px(10.)) @@ -3788,9 +3792,9 @@ impl MainWindow { .on_click(cx.listener( move |this, _, _, cx| { cx.write_to_clipboard(gpui::ClipboardItem::new_string( - copy_error.clone(), + copy_text.clone(), )); - this.copied_error = Some(copy_error.clone()); + this.copied_error = Some(error.clone()); cx.notify(); }, )), @@ -4732,10 +4736,10 @@ impl MainWindow { let subtitle = item.upload.as_ref().map_or_else( || item.mode.label().to_string(), |upload| { - upload - .last_error - .clone() - .unwrap_or_else(|| upload.label().to_string()) + upload.last_error.as_deref().map_or_else( + || upload.label().to_string(), + crate::i18n::messages::localize, + ) }, ); let actions = self.render_recording_card_actions(item, cx); diff --git a/apps/desktop-gpui/src/permissions.rs b/apps/desktop-gpui/src/permissions.rs index b41ad8b8891..c0932b39ae4 100644 --- a/apps/desktop-gpui/src/permissions.rs +++ b/apps/desktop-gpui/src/permissions.rs @@ -55,6 +55,16 @@ impl OSPermission { } } + /// English, like the other recording errors; `i18n::messages` translates + /// it where it is shown. + pub fn unavailable_message(self) -> String { + format!( + "{} access is unavailable. Allow Cap in System Settings > Privacy & Security > {}, then select the device again.", + self.label(), + self.label() + ) + } + pub fn display_name(self) -> &'static str { match self { Self::ScreenRecording => tr::permission::name::screen_recording(), @@ -291,11 +301,7 @@ async fn media_permission_result( MediaAuthorization::NotDetermined if request.await? => Ok(()), MediaAuthorization::NotDetermined | MediaAuthorization::Denied - | MediaAuthorization::Restricted => Err(format!( - "{} access is unavailable. Allow Cap in System Settings > Privacy & Security > {}, then select the device again.", - permission.label(), - permission.label() - )), + | MediaAuthorization::Restricted => Err(permission.unavailable_message()), } } diff --git a/apps/desktop-gpui/src/settings_window.rs b/apps/desktop-gpui/src/settings_window.rs index 410d4c267f0..c3e649a37f6 100644 --- a/apps/desktop-gpui/src/settings_window.rs +++ b/apps/desktop-gpui/src/settings_window.rs @@ -3018,8 +3018,8 @@ impl SettingsWindow { let label = upload.label(); let message = upload .last_error - .clone() - .unwrap_or_else(|| label.to_string()); + .as_deref() + .map_or_else(|| label.to_string(), crate::i18n::messages::localize); this.child( pill(theme.settings_fill()) .id("recording-upload-status") diff --git a/apps/desktop-gpui/src/store.rs b/apps/desktop-gpui/src/store.rs index f5024bc239f..c918a4ee05b 100644 --- a/apps/desktop-gpui/src/store.rs +++ b/apps/desktop-gpui/src/store.rs @@ -63,9 +63,9 @@ impl BlurMode { pub fn label(self) -> Option<&'static str> { match self { Self::Off => None, - Self::Light => Some("Light"), - Self::Heavy => Some("Heavy"), - Self::Remove => Some("Cutout"), + Self::Light => Some(tr::camera::blur::light()), + Self::Heavy => Some(tr::camera::blur::heavy()), + Self::Remove => Some(tr::camera::blur::cutout()), } } } diff --git a/apps/desktop-gpui/src/target_overlay.rs b/apps/desktop-gpui/src/target_overlay.rs index 10698df20c8..28f0a3a79a1 100644 --- a/apps/desktop-gpui/src/target_overlay.rs +++ b/apps/desktop-gpui/src/target_overlay.rs @@ -2196,15 +2196,14 @@ impl OverlayWindow { } let arrangeable = crate::foreign_window::can_arrange(); let hint = if arrangeable { - Hint::new("Change window size") - .body("Resize the window to a ratio or an exact size before you record.") + Hint::new(tr::overlay::window::size_title()) + .body(tr::overlay::window::size_body()) } else if cfg!(target_os = "macos") { - Hint::new("Resizing needs Accessibility").body( - "Turn on Accessibility for Cap in System Settings to resize other apps' windows.", - ) + Hint::new(tr::overlay::window::size_needs_accessibility_title()) + .body(tr::overlay::window::size_needs_accessibility_body()) } else { - Hint::new("Resizing isn't available") - .body("Wayland doesn't let apps resize other apps' windows.") + Hint::new(tr::overlay::window::size_unavailable_title()) + .body(tr::overlay::window::size_unavailable_body()) }; let chip = self.glass_capsule(28.).id("window-size"); self.hinted(chip, "window-size", hint, cx) @@ -2247,17 +2246,17 @@ impl OverlayWindow { .read(cx) .is_ok_and(|view| view.camera_selection().is_some()); let title = if focus { - "Turn off Focus mode" + tr::overlay::window::focus_disable() } else { - "Enable Focus mode" + tr::overlay::window::focus_enable() }; let body = if cfg!(any(target_os = "macos", windows)) && self.select.read(cx).recording_mode == Mode::Instant && camera { - "Centres this window. With a camera, Instant still records the screen region so the camera stays in the video." + tr::overlay::window::focus_body_instant_camera() } else { - "Centres this window and records only it. Anything that moves in front of it stays out of the video." + tr::overlay::window::focus_body() }; // Regular, as the controls below it are: it carries text. let card = self.glass_surface().id("window-focus"); @@ -2315,7 +2314,7 @@ impl OverlayWindow { .line_height(px(17.)) .font_weight(FontWeight::SEMIBOLD) .text_color(theme.gray_12) - .child("Focus mode"), + .child(tr::overlay::window::focus_title()), ) .child( div() @@ -2324,9 +2323,9 @@ impl OverlayWindow { .font_weight(FontWeight::NORMAL) .text_color(theme.gray_11) .child(if focus { - "Recording only this window" + tr::overlay::window::focus_on() } else { - "Record only this window" + tr::overlay::window::focus_off() }), ), ) @@ -3111,8 +3110,8 @@ impl OverlayWindow { this.open_aspect_menu(event.position(), cx); })), "area-ratio-more", - Hint::new("More aspect ratios") - .body("Lock the area to a ratio such as 4:3, 9:16 or 21:9."), + Hint::new(tr::overlay::area::more_ratios_title()) + .body(tr::overlay::area::more_ratios_body()), cx, ), ) @@ -3122,8 +3121,8 @@ impl OverlayWindow { icon_button("area-reset", "icons/rotate-ccw.svg") .on_click(cx.listener(|this, _, _, cx| this.reset_area(cx))), "area-reset", - Hint::new("Reset selection") - .body("Clear the area so you can draw a new one."), + Hint::new(tr::overlay::area::reset_title()) + .body(tr::overlay::area::reset_body()), cx, ), ) @@ -3132,8 +3131,8 @@ impl OverlayWindow { icon_button("area-fill", "icons/maximize.svg") .on_click(cx.listener(|this, _, _, cx| this.fill_area(cx))), "area-fill", - Hint::new("Fill display") - .body("Grow the area to cover the display, keeping its ratio."), + Hint::new(tr::overlay::area::fill_title()) + .body(tr::overlay::area::fill_body()), cx, ), ), @@ -3381,9 +3380,8 @@ impl OverlayWindow { self.hinted( self.glass_button(36.).id("overlay-countdown"), "overlay-countdown", - Hint::new("Recording countdown").body( - "Wait a few seconds after you press Start before recording begins.", - ), + Hint::new(tr::overlay::countdown_title()) + .body(tr::overlay::countdown_body()), cx, ) .child( @@ -3591,9 +3589,7 @@ impl OverlayWindow { self.hinted( div().id("overlay-mode-picker"), "overlay-mode-picker", - Hint::new("Change recording mode").body( - "Instant gives you a link to share the moment you stop. Studio records in full quality for editing.", - ), + Hint::new(tr::overlay::mode_title()).body(tr::overlay::mode_body()), cx, ) .on_mouse_down( From b9586bf7dbdde6e39c4707d561995049fcd4c2cb Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:29:46 +0000 Subject: [PATCH 20/25] docs(desktop-gpui): record the review's terminology and the error and number rules in the glossary --- apps/desktop-gpui/locales/GLOSSARY.md | 29 ++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/desktop-gpui/locales/GLOSSARY.md b/apps/desktop-gpui/locales/GLOSSARY.md index d9569b2e374..1f1b9110af8 100644 --- a/apps/desktop-gpui/locales/GLOSSARY.md +++ b/apps/desktop-gpui/locales/GLOSSARY.md @@ -33,7 +33,14 @@ from English, and `cargo test -p cap-i18n-build` fails while any key is missing. | hi | आप | Devanagari with common English loanwords in Devanagari (रिकॉर्डिंग, स्क्रीनशॉट); product names stay in Latin script. | Menu bar items follow the wording macOS itself uses in each language, so they -sit naturally next to the items AppKit adds. +sit naturally next to the items AppKit adds. That is why the Japanese Window +menu says ウインドウ while the rest of the app says ウィンドウ, and Chinese menus +say 拷贝 while the app says 复制. + +Within a catalog, one term per concept: the same English word gets the same +translation in the editor, the screenshot editor, settings and automations. +French uses the straight apostrophe `'`. German puts no space before `…` or +`...`. Keep the English punctuation style of each string: `...` stays three dots and `…` stays one character, because both appear in the source. Duration units @@ -44,6 +51,15 @@ Instant-Modus, Instantモード). Error messages that end in `{error}` show a technical message from the recording engine, which stays in English. +Recording and upload errors that Cap writes in English (they are also logged, +matched and saved) live under `message.*` and are translated where they are +shown, by `i18n::messages::localize`. A new error sentence the user can see +goes into its table and the catalog together; the tests fail if the English +in the code and in the table drift apart. + +Numbers inside a sentence go through `i18n::decimal`, which writes the decimal +comma French, Spanish and German use (`1,5 GB`). + ## Terms | English | zh-CN | ja | fr | es | de | hi | @@ -83,6 +99,17 @@ recording engine, which stays in English. | caption (subtitle) | 字幕 | 字幕 | sous-titre | subtítulo | Untertitel | कैप्शन | | track (timeline) | 轨道 | トラック | piste | pista | Spur | ट्रैक | | preset | 预设 | プリセット | préréglage | preajuste | Vorlage | प्रीसेट | +| video | 视频 | 動画 | vidéo | vídeo | Video | वीडियो | +| audio | 音频 | 音声 | audio | audio | Audio | ऑडियो | +| copy (to the clipboard) | 复制 | コピー | copier | copiar | kopieren | कॉपी करें | +| transcription | 转写 | 文字起こし | transcription | transcripción | Transkription | ट्रांसक्रिप्शन | +| click | 点击 | クリック | cliquer | hacer clic | klicken | क्लिक करें | +| plan (paid tier) | 套餐 | プラン | offre | plan | Tarif | प्लान | +| aspect ratio | 宽高比 | アスペクト比 | format d'image | relación de aspecto | Seitenverhältnis | आस्पेक्ट रेशियो | +| shadow | 阴影 | 影 | ombre | sombra | Schatten | छाया | +| frames per second | FPS | FPS | i/s | FPS | FPS | FPS | +| key (on the keyboard, licence key) | 键 / 密钥 | キー | touche / clé | tecla / clave | Taste / Schlüssel | कुंजी | +| restart / resume | 重新开始 / 继续 | やり直す / 再開 | recommencer / reprendre | reiniciar / reanudar | neu starten / fortsetzen | रीस्टार्ट करें / फिर से शुरू करें | ## Fitting the UI From b6ed7751f3217ffb8ad1e30faff18cd670327017 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:09:52 +0000 Subject: [PATCH 21/25] fix(desktop-gpui): refine the new picker, export and error translations after a second review --- apps/desktop-gpui/locales/de.toml | 30 ++++++++++++++-------------- apps/desktop-gpui/locales/es.toml | 16 +++++++-------- apps/desktop-gpui/locales/fr.toml | 12 +++++------ apps/desktop-gpui/locales/hi.toml | 6 +++--- apps/desktop-gpui/locales/ja.toml | 4 ++-- apps/desktop-gpui/locales/zh-CN.toml | 12 +++++------ 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index fc9c378503e..cc1f5e667fe 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -1186,7 +1186,7 @@ unavailable_title = "Kamera nicht verfügbar" [camera.blur] light = "Leicht" heavy = "Stark" -cutout = "Freistellen" +cutout = "Freigestellt" [onboarding.customize] footer = "Alles davon können Sie jederzeit in den Einstellungen ändern" @@ -2662,7 +2662,7 @@ normal = "Normal" add_shot = "Einstellung hinzufügen" add_shot_with_look = "Einstellung hinzufügen · {look} · {seconds} s" paused = "Pausiert" -title = "Timeline" +title = "Zeitleiste" [editor.timeline.empty] add_audio = "Audio hinzufügen" @@ -2802,7 +2802,7 @@ volume = "Lautstärke" transition = "ÜBERGANG" transition_none = "Keiner" transition_crossfade = "Überblendung" -transition_fade = "Abblende" +transition_fade = "Schwarzblende" transition_seconds = "{seconds} s" [editor.window.frame] @@ -2960,31 +2960,31 @@ stop_not_acknowledged = "Das Beenden der Aufnahme wurde nicht bestätigt" capture_state_timed_out = "Zeitüberschreitung beim Bestätigen des Aufnahmestatus" pause_not_acknowledged = "Die Aufnahme hat den angeforderten Pausenstatus nicht bestätigt" cleanup_unconfirmed_files_kept = "Das Aufräumen der Aufnahme ist nicht bestätigt. Lokale Dateien bleiben erhalten; drücken Sie Ctrl+Shift+F9, um das Stoppen zu wiederholen." -cleanup_unconfirmed_retry = "Das Aufräumen der Aufnahme ist nicht bestätigt; stoppen Sie erneut." -discard_not_acknowledged = "Das Stoppen des Studio-Verwerfens wurde nicht bestätigt; die lokale Aufnahme bleibt erhalten" -window_still_resizing = "Die Fenstergröße wird noch geändert. Starten Sie erneut." +cleanup_unconfirmed_retry = "Das Aufräumen der Aufnahme ist nicht bestätigt; drücken Sie erneut Stopp." +discard_not_acknowledged = "Beim Verwerfen in Studio wurde das Beenden der Aufnahme nicht bestätigt; die lokale Aufnahme bleibt erhalten" +window_still_resizing = "Die Fenstergröße wird noch geändert. Versuchen Sie erneut, die Aufnahme zu starten." startup_cancelled = "Start der Aufnahme abgebrochen; lokale Dateien bleiben erhalten" instant_cleanup_unconfirmed = "Das Aufräumen der Instant-Aufnahme ist nicht bestätigt; lokale Dateien bleiben erhalten" -finalization_no_result = "Die Fertigstellung der Aufnahme endete ohne Ergebnis. Ihre Dateien wurden behalten." +finalization_no_result = "Die Fertigstellung der Aufnahme endete ohne Ergebnis. Ihre Dateien bleiben erhalten." stopped_metadata_unsaved = "Die Aufnahme wurde gestoppt, aber ihre Metadaten konnten nicht gespeichert werden; lokale Dateien bleiben erhalten" -discard_stop_unconfirmed = "Das Stoppen des Studio-Verwerfens ist nicht bestätigt; lokale Dateien bleiben erhalten" -discard_unconfirmed = "Das Studio-Verwerfen ist nicht bestätigt; lokale Dateien bleiben erhalten" +discard_stop_unconfirmed = "Beim Verwerfen in Studio ist das Beenden der Aufnahme nicht bestätigt; lokale Dateien bleiben erhalten" +discard_unconfirmed = "Das Verwerfen in Studio ist nicht bestätigt; lokale Dateien bleiben erhalten" crashed_before_capture = "Cap wurde beendet, bevor diese Aufnahme etwas aufgenommen hat." capture_state_failed = "Der Aufnahmestatus konnte nicht bestätigt werden: {error}" completion_task_failed = "Die Abschlussaufgabe der Aufnahme ist fehlgeschlagen: {error}" stop_task_failed = "Die Stopp-Aufgabe ist fehlgeschlagen: {error}" control_task_failed = "Die Steuerungsaufgabe der Aufnahme ist fehlgeschlagen: {error}" restart_hide_failed = "Beim Neustart konnten die Cap-Fenster nicht ausgeblendet werden: {error}" -discard_task_failed = "Die Studio-Verwerfen-Aufgabe ist fehlgeschlagen: {error}; das Aufräumen ist nicht bestätigt" -cancel_task_failed = "Die Abbrechen-Aufgabe ist fehlgeschlagen: {error}. Das Aufräumen ist nicht bestätigt; stoppen Sie erneut." +discard_task_failed = "Das Verwerfen in Studio ist fehlgeschlagen: {error}; das Aufräumen ist nicht bestätigt" +cancel_task_failed = "Die Abbruchaufgabe ist fehlgeschlagen: {error}. Das Aufräumen ist nicht bestätigt; drücken Sie erneut Stopp." then_cleanup_unconfirmed_shortcut = "{error}. Das Aufräumen der Aufnahme ist nicht bestätigt. Drücken Sie Ctrl+Shift+F9, um das Stoppen zu wiederholen." then_cleanup_unconfirmed_control = "{error}. Das Aufräumen der Aufnahme ist nicht bestätigt. Verwenden Sie das Stopp-Steuerelement der Aufnahme, um es erneut zu versuchen." then_shutdown_unconfirmed = "{error}. Das Beenden von Studio ist weiterhin nicht bestätigt; Aufnahme und Dateien bleiben erhalten." then_use_stop_if_unsafe = "{error}. Verwenden Sie Stopp, wenn die Aufnahme nicht sicher fortgesetzt werden kann." then_use_stop_control = "{error}. Verwenden Sie das Stopp-Steuerelement der Aufnahme, um sie zu beenden." -then_discard_stopped = "{error}; Verwerfen/Neustart angehalten, lokale Aufnahme bleibt erhalten" -then_files_preserved = "{error}. Die Dateien der fehlgeschlagenen Aufnahme wurden behalten." -then_cleanup_retry = "{error}. Das Aufräumen ist nicht bestätigt; stoppen Sie erneut." +then_discard_stopped = "{error}; Verwerfen/Neustart abgebrochen, lokale Aufnahme bleibt erhalten" +then_files_preserved = "{error}. Die Dateien der fehlgeschlagenen Aufnahme bleiben erhalten." +then_cleanup_retry = "{error}. Das Aufräumen ist nicht bestätigt; drücken Sie erneut Stopp." low_storage_preserved = "Wenig Speicherplatz. Ihre Aufnahmedateien bleiben unter {path} erhalten. Geben Sie Speicherplatz frei und stellen Sie die Aufnahme dann in Cap wieder her." [message.permission] @@ -3002,7 +3002,7 @@ sign_in_original = "Melden Sie sich beim ursprünglichen Konto und Server der Au sign_in_again = "Melden Sie sich erneut an, um diese Aufnahme hochzuladen" needs_upgrade = "Instant-Aufnahmen erfordern einen höheren Tarif" changed_during = "Die lokale Aufnahme hat sich während des Uploads geändert" -changed_after = "Die lokale Aufnahme hat sich nach dem Upload geändert; sie wurde behalten" +changed_after = "Die lokale Aufnahme hat sich nach dem Upload geändert; sie bleibt erhalten" already_queued = "Diese Aufnahme ist bereits zum Hochladen eingereiht" paused = "Upload pausiert; die lokale Aufnahme bleibt erhalten" worker_failed = "Der Upload-Vorgang ist fehlgeschlagen; die lokale Aufnahme bleibt erhalten" diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index 2d7ea0eac86..fb3d304e885 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -1512,7 +1512,7 @@ focus_on = "Solo se grabará esta ventana" focus_off = "Grabar solo esta ventana" focus_enable = "Activar el modo enfoque" focus_disable = "Desactivar el modo enfoque" -focus_body = "Centra esta ventana y graba solo ella. Lo que pase por delante no aparecerá en el vídeo." +focus_body = "Centra esta ventana y graba solo su contenido. Lo que pase por delante no aparecerá en el vídeo." focus_body_instant_camera = "Centra esta ventana. Con una cámara, Instant sigue grabando la región de pantalla para que la cámara aparezca en el vídeo." [permission.guide] @@ -2959,15 +2959,15 @@ stop_task_failed_plain = "La tarea de detención ha fallado." stop_not_acknowledged = "No se confirmó la detención de la captura" capture_state_timed_out = "Se agotó el tiempo para confirmar el estado de la captura" pause_not_acknowledged = "La captura no confirmó el estado de pausa solicitado" -cleanup_unconfirmed_files_kept = "No se ha confirmado la limpieza de la captura. Los archivos locales se conservan; usa Ctrl+Shift+F9 para volver a detener." -cleanup_unconfirmed_retry = "No se ha confirmado la limpieza de la captura; vuelve a detener." -discard_not_acknowledged = "No se confirmó la detención del descarte de Studio; la grabación local se conserva" +cleanup_unconfirmed_files_kept = "No se ha confirmado la limpieza de la captura. Los archivos locales se conservan; usa Ctrl+Shift+F9 para volver a intentar detenerla." +cleanup_unconfirmed_retry = "No se ha confirmado la limpieza de la captura; vuelve a pulsar Detener." +discard_not_acknowledged = "Al descartar en Studio no se confirmó la detención de la captura; la grabación local se conserva" window_still_resizing = "La ventana aún se está redimensionando. Vuelve a iniciar la grabación." startup_cancelled = "Se canceló el inicio de la grabación; los archivos locales se conservan" instant_cleanup_unconfirmed = "No se ha confirmado la limpieza de la captura Instant; los archivos locales se conservan" finalization_no_result = "La finalización de la grabación terminó sin resultado. Tus archivos se han conservado." stopped_metadata_unsaved = "La captura se detuvo, pero no se pudieron guardar sus metadatos; los archivos locales se conservan" -discard_stop_unconfirmed = "No se ha confirmado la detención del descarte de Studio; los archivos locales se conservan" +discard_stop_unconfirmed = "Al descartar en Studio no se ha confirmado la detención de la captura; los archivos locales se conservan" discard_unconfirmed = "No se ha confirmado el descarte de Studio; los archivos locales se conservan" crashed_before_capture = "Cap se cerró antes de que esta grabación capturara nada." capture_state_failed = "No se pudo confirmar el estado de la captura: {error}" @@ -2976,15 +2976,15 @@ stop_task_failed = "La tarea de detención ha fallado: {error}" control_task_failed = "La tarea de control de la captura ha fallado: {error}" restart_hide_failed = "Al reiniciar no se pudieron ocultar las ventanas de Cap: {error}" discard_task_failed = "La tarea de descarte de Studio ha fallado: {error}; no se ha confirmado la limpieza" -cancel_task_failed = "La tarea de cancelación ha fallado: {error}. No se ha confirmado la limpieza; vuelve a detener." -then_cleanup_unconfirmed_shortcut = "{error}. No se ha confirmado la limpieza de la captura. Usa Ctrl+Shift+F9 para volver a detener." +cancel_task_failed = "La tarea de cancelación ha fallado: {error}. No se ha confirmado la limpieza; vuelve a pulsar Detener." +then_cleanup_unconfirmed_shortcut = "{error}. No se ha confirmado la limpieza de la captura. Usa Ctrl+Shift+F9 para volver a intentar detenerla." then_cleanup_unconfirmed_control = "{error}. No se ha confirmado la limpieza de la captura. Usa el control Detener de la grabación para volver a intentarlo." then_shutdown_unconfirmed = "{error}. Sigue sin confirmarse el cierre de Studio; la grabación y los archivos se conservan." then_use_stop_if_unsafe = "{error}. Usa Detener si la grabación no puede continuar con seguridad." then_use_stop_control = "{error}. Usa el control Detener de la grabación para detenerla." then_discard_stopped = "{error}; se detuvo el descarte/reinicio y la grabación local se conserva" then_files_preserved = "{error}. Se conservaron los archivos de la grabación fallida." -then_cleanup_retry = "{error}. No se ha confirmado la limpieza; vuelve a detener." +then_cleanup_retry = "{error}. No se ha confirmado la limpieza; vuelve a pulsar Detener." low_storage_preserved = "Poco espacio de almacenamiento. Los archivos de tu grabación se conservan en {path}. Libera espacio y luego recupera la grabación en Cap." [message.permission] diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index 4715b7a28f2..a6f0be65052 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -1465,7 +1465,7 @@ starting = "Démarrage de la conversion..." [overlay] display_fallback = "Écran {id}" countdown_title = "Compte à rebours de l'enregistrement" -countdown_body = "Attendre quelques secondes entre le clic sur Lancer l'enregistrement et le début de l'enregistrement." +countdown_body = "Laisser quelques secondes entre le clic sur Lancer l'enregistrement et le début de la capture." mode_title = "Changer de mode d'enregistrement" mode_body = "Instant vous donne un lien à partager dès que vous arrêtez. Studio enregistre en pleine qualité pour le montage." @@ -2417,9 +2417,9 @@ refining_time = "Affinage de la durée…" unavailable = "Indisponible" [editor.export.turbo] -title = "Export turbo" +title = "Exportation turbo" description = "Exporte beaucoup plus vite en utilisant tous les cœurs du processeur et le GPU" -warning = "Utilise tout votre processeur, le GPU et plusieurs fois plus de mémoire jusqu'à la fin de l'export. Les autres apps peuvent ralentir, et votre ordinateur peut chauffer et devenir bruyant." +warning = "Utilise tout votre processeur, le GPU et plusieurs fois plus de mémoire jusqu'à la fin de l'exportation. Les autres apps peuvent ralentir, et votre ordinateur peut chauffer et devenir bruyant." [editor.modal] cancel_export = "Annuler l'exportation" @@ -2961,13 +2961,13 @@ capture_state_timed_out = "Délai dépassé pour confirmer l'état de la capture pause_not_acknowledged = "La capture n'a pas confirmé l'état de pause demandé" cleanup_unconfirmed_files_kept = "Le nettoyage de la capture n'est pas confirmé. Les fichiers locaux sont conservés ; utilisez Ctrl+Shift+F9 pour relancer l'arrêt." cleanup_unconfirmed_retry = "Le nettoyage de la capture n'est pas confirmé ; relancez l'arrêt." -discard_not_acknowledged = "L'arrêt de la suppression Studio n'a pas été confirmé ; l'enregistrement local est conservé" +discard_not_acknowledged = "Lors de la suppression Studio, l'arrêt de la capture n'a pas été confirmé ; l'enregistrement local est conservé" window_still_resizing = "La fenêtre est encore en cours de redimensionnement. Réessayez de démarrer." startup_cancelled = "Démarrage de l'enregistrement annulé ; les fichiers locaux sont conservés" instant_cleanup_unconfirmed = "Le nettoyage de la capture Instant n'est pas confirmé ; les fichiers locaux sont conservés" finalization_no_result = "La finalisation de l'enregistrement s'est terminée sans résultat. Vos fichiers ont été conservés." stopped_metadata_unsaved = "La capture est arrêtée, mais ses métadonnées n'ont pas pu être enregistrées ; les fichiers locaux sont conservés" -discard_stop_unconfirmed = "L'arrêt de la suppression Studio n'est pas confirmé ; les fichiers locaux sont conservés" +discard_stop_unconfirmed = "Lors de la suppression Studio, l'arrêt de la capture n'est pas confirmé ; les fichiers locaux sont conservés" discard_unconfirmed = "La suppression Studio n'est pas confirmée ; les fichiers locaux sont conservés" crashed_before_capture = "Cap s'est fermé avant que cet enregistrement ne capture quoi que ce soit." capture_state_failed = "Impossible de confirmer l'état de la capture : {error}" @@ -2983,7 +2983,7 @@ then_shutdown_unconfirmed = "{error}. L'arrêt de Studio n'est toujours pas conf then_use_stop_if_unsafe = "{error}. Utilisez Arrêter si l'enregistrement ne peut pas continuer en toute sécurité." then_use_stop_control = "{error}. Utilisez la commande Arrêter de l'enregistrement pour l'arrêter." then_discard_stopped = "{error} ; suppression/redémarrage interrompu, l'enregistrement local est conservé" -then_files_preserved = "{error}. Les fichiers de l'enregistrement échoué ont été conservés." +then_files_preserved = "{error}. Les fichiers de l'enregistrement ayant échoué ont été conservés." then_cleanup_retry = "{error}. Le nettoyage n'est pas confirmé ; relancez l'arrêt." low_storage_preserved = "Espace de stockage insuffisant. Vos fichiers d'enregistrement sont conservés dans {path}. Libérez de l'espace, puis récupérez l'enregistrement dans Cap." diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index be00a5d8c1a..4f1aa2f7474 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -1465,7 +1465,7 @@ starting = "कन्वर्ज़न शुरू हो रहा है..." [overlay] display_fallback = "डिस्प्ले {id}" countdown_title = "रिकॉर्डिंग काउंटडाउन" -countdown_body = "‘रिकॉर्डिंग शुरू करें’ दबाने के बाद रिकॉर्डिंग शुरू होने से पहले कुछ सेकंड इंतज़ार करें।" +countdown_body = "“रिकॉर्डिंग शुरू करें” दबाने के बाद रिकॉर्डिंग शुरू होने से पहले कुछ सेकंड इंतज़ार करें।" mode_title = "रिकॉर्डिंग मोड बदलें" mode_body = "Instant रिकॉर्डिंग रोकते ही आपको शेयर करने के लिए एक लिंक देता है। Studio एडिटिंग के लिए पूरी क्वालिटी में रिकॉर्ड करता है।" @@ -2961,13 +2961,13 @@ capture_state_timed_out = "कैप्चर की स्थिति की pause_not_acknowledged = "कैप्चर ने माँगी गई पॉज़ स्थिति की पुष्टि नहीं की" cleanup_unconfirmed_files_kept = "कैप्चर की सफ़ाई की पुष्टि नहीं हुई। लोकल फ़ाइलें सुरक्षित हैं; फिर से रोकने के लिए Ctrl+Shift+F9 दबाएँ।" cleanup_unconfirmed_retry = "कैप्चर की सफ़ाई की पुष्टि नहीं हुई; फिर से रोकें।" -discard_not_acknowledged = "Studio हटाने के रुकने की पुष्टि नहीं हुई; लोकल रिकॉर्डिंग सुरक्षित है" +discard_not_acknowledged = "Studio रिकॉर्डिंग हटाते समय कैप्चर रुकने की पुष्टि नहीं हुई; लोकल रिकॉर्डिंग सुरक्षित है" window_still_resizing = "विंडो का आकार अभी बदल रहा है। फिर से शुरू करके देखें।" startup_cancelled = "रिकॉर्डिंग शुरू करना रद्द हुआ; लोकल फ़ाइलें सुरक्षित हैं" instant_cleanup_unconfirmed = "Instant कैप्चर की सफ़ाई की पुष्टि नहीं हुई; लोकल फ़ाइलें सुरक्षित हैं" finalization_no_result = "रिकॉर्डिंग पूरी करने का काम बिना नतीजे के खत्म हो गया। आपकी फ़ाइलें सुरक्षित रखी गई हैं।" stopped_metadata_unsaved = "कैप्चर रुक गया, लेकिन उसका मेटाडेटा सेव नहीं हो सका; लोकल फ़ाइलें सुरक्षित हैं" -discard_stop_unconfirmed = "Studio हटाने के रुकने की पुष्टि नहीं हुई; लोकल फ़ाइलें सुरक्षित हैं" +discard_stop_unconfirmed = "Studio रिकॉर्डिंग हटाते समय कैप्चर रुकने की पुष्टि नहीं हुई; लोकल फ़ाइलें सुरक्षित हैं" discard_unconfirmed = "Studio हटाने की पुष्टि नहीं हुई; लोकल फ़ाइलें सुरक्षित हैं" crashed_before_capture = "इस रिकॉर्डिंग में कुछ भी कैप्चर होने से पहले Cap बंद हो गया।" capture_state_failed = "कैप्चर की स्थिति की पुष्टि नहीं हो सकी: {error}" diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 9eba81873b2..8acbb99b19f 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -2961,13 +2961,13 @@ capture_state_timed_out = "キャプチャの状態の確認がタイムアウ pause_not_acknowledged = "キャプチャが要求された一時停止状態を確認しませんでした" cleanup_unconfirmed_files_kept = "キャプチャの後処理を確認できません。ローカルファイルは保持されています。Ctrl+Shift+F9で停止をやり直してください。" cleanup_unconfirmed_retry = "キャプチャの後処理を確認できません。停止をやり直してください。" -discard_not_acknowledged = "Studioの破棄の停止が確認されませんでした。ローカルの録画は保持されています" +discard_not_acknowledged = "Studioの録画を破棄する際、キャプチャの停止が確認されませんでした。ローカルの録画は保持されています" window_still_resizing = "ウィンドウのサイズ変更がまだ終わっていません。もう一度開始してください。" startup_cancelled = "録画の開始がキャンセルされました。ローカルファイルは保持されています" instant_cleanup_unconfirmed = "Instantのキャプチャの後処理を確認できません。ローカルファイルは保持されています" finalization_no_result = "録画の仕上げが結果を返さずに終了しました。ファイルは保持されています。" stopped_metadata_unsaved = "キャプチャは停止しましたが、停止時のメタデータを保存できませんでした。ローカルファイルは保持されています" -discard_stop_unconfirmed = "Studioの破棄の停止を確認できません。ローカルファイルは保持されています" +discard_stop_unconfirmed = "Studioの録画を破棄する際、キャプチャの停止を確認できません。ローカルファイルは保持されています" discard_unconfirmed = "Studioの破棄を確認できません。ローカルファイルは保持されています" crashed_before_capture = "この録画で何もキャプチャされないうちにCapが終了しました。" capture_state_failed = "キャプチャの状態を確認できませんでした:{error}" diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index 8b792a0af9f..188d01ac528 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -1185,7 +1185,7 @@ unavailable_title = "摄像头不可用" [camera.blur] light = "轻" -heavy = "重" +heavy = "强" cutout = "抠像" [onboarding.customize] @@ -2961,13 +2961,13 @@ capture_state_timed_out = "确认捕获状态超时" pause_not_acknowledged = "捕获未确认所请求的暂停状态" cleanup_unconfirmed_files_kept = "无法确认捕获清理已完成。本地文件已保留;请按 Ctrl+Shift+F9 重试停止。" cleanup_unconfirmed_retry = "无法确认捕获清理已完成;请重试停止。" -discard_not_acknowledged = "未确认 Studio 丢弃已停止;本地录制已保留" -window_still_resizing = "窗口仍在调整大小。请重新开始。" +discard_not_acknowledged = "丢弃 Studio 录制时未确认捕获已停止;本地录制已保留" +window_still_resizing = "窗口仍在调整大小。请再次尝试开始录制。" startup_cancelled = "录制启动已取消;本地文件已保留" instant_cleanup_unconfirmed = "无法确认 Instant 捕获清理已完成;本地文件已保留" finalization_no_result = "录制收尾未返回结果。你的文件已保留。" stopped_metadata_unsaved = "捕获已停止,但无法保存停止信息;本地文件已保留" -discard_stop_unconfirmed = "无法确认 Studio 丢弃已停止;本地文件已保留" +discard_stop_unconfirmed = "丢弃 Studio 录制时无法确认捕获已停止;本地文件已保留" discard_unconfirmed = "无法确认 Studio 丢弃已完成;本地文件已保留" crashed_before_capture = "Cap 在此录制捕获到任何内容之前就已关闭。" capture_state_failed = "无法确认捕获状态:{error}" @@ -2982,13 +2982,13 @@ then_cleanup_unconfirmed_control = "{error}。无法确认捕获清理已完成 then_shutdown_unconfirmed = "{error}。无法确认 Studio 已关闭;录制和文件已保留。" then_use_stop_if_unsafe = "{error}。如果录制无法安全继续,请使用“停止”。" then_use_stop_control = "{error}。请使用录制的停止控件来停止录制。" -then_discard_stopped = "{error};丢弃/重新开始已停止,本地录制已保留" +then_discard_stopped = "{error};已中止丢弃/重新开始,本地录制已保留" then_files_preserved = "{error}。失败录制的文件已保留。" then_cleanup_retry = "{error}。无法确认清理已完成;请重试停止。" low_storage_preserved = "存储空间不足。你的录制文件已保留在 {path}。请释放空间,然后在 Cap 中恢复该录制。" [message.permission] -unavailable = "无法使用{permission}权限。请在“系统设置”>“隐私与安全性”>“{permission}”中允许 Cap,然后重新选择该设备。" +unavailable = "无法访问{permission}。请在“系统设置”>“隐私与安全性”>“{permission}”中允许 Cap,然后重新选择该设备。" [message.upload] offline = "离线" From 36cf24ececd771829a4a24ff3ee2537eb66c265f Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:16:35 +0000 Subject: [PATCH 22/25] fix(desktop-gpui): localize clip merge hints, caption font names, teleprompter speed and the missing CLI message --- apps/desktop-gpui/locales/de.toml | 17 +++++++++++- apps/desktop-gpui/locales/en.toml | 17 +++++++++++- apps/desktop-gpui/locales/es.toml | 17 +++++++++++- apps/desktop-gpui/locales/fr.toml | 17 +++++++++++- apps/desktop-gpui/locales/hi.toml | 17 +++++++++++- apps/desktop-gpui/locales/ja.toml | 17 +++++++++++- apps/desktop-gpui/locales/zh-CN.toml | 17 +++++++++++- apps/desktop-gpui/src/editor_edits.rs | 10 ++++--- apps/desktop-gpui/src/editor_tabs.rs | 26 ++++++++++++++----- .../src/i18n/dialogs_surface_tests.rs | 5 ++++ .../src/i18n/editor_shell_surface_tests.rs | 19 ++++++++++++++ apps/desktop-gpui/src/i18n/messages.rs | 6 +++++ apps/desktop-gpui/src/i18n/messages/tests.rs | 3 +++ apps/desktop-gpui/src/settings_pages.rs | 6 +++-- apps/desktop-gpui/src/teleprompter_window.rs | 4 ++- 15 files changed, 178 insertions(+), 20 deletions(-) diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index cc1f5e667fe..32fa0ab7e2c 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -1442,6 +1442,7 @@ hidden = "Dieses Fenster ist in Cap-Aufnahmen ausgeblendet" mirror = "Text spiegeln" paused = "Scrollen zusammen mit der Aufnahme pausiert" placeholder = "Skript einfügen oder eingeben…" +words_per_minute = "{wpm} Wörter/Min." [import] error_title = "Importfehler" @@ -2658,6 +2659,11 @@ bold = "Fett" medium = "Mittel" normal = "Normal" +[editor.tabs.font] +system_sans_serif = "System-Sans-Serif" +system_serif = "System-Serif" +system_monospace = "System-Monospace" + [editor.timeline] add_shot = "Einstellung hinzufügen" add_shot_with_look = "Einstellung hinzufügen · {look} · {seconds} s" @@ -2805,6 +2811,12 @@ transition_crossfade = "Überblendung" transition_fade = "Schwarzblende" transition_seconds = "{seconds} s" +[editor.window.clip_menu.merge_blocker] +edge = "Auf dieser Seite ist kein Clip" +different_recording = "Andere Aufnahme" +gap = "Dazwischen wurde Material herausgeschnitten" +speed = "Gleichen Sie zuerst die Clip-Geschwindigkeiten an" + [editor.window.frame] add_tooltip = "Rahmen hinzufügen" browser = "Browser" @@ -3008,4 +3020,7 @@ paused = "Upload pausiert; die lokale Aufnahme bleibt erhalten" worker_failed = "Der Upload-Vorgang ist fehlgeschlagen; die lokale Aufnahme bleibt erhalten" empty = "Die gespeicherte Aufnahme ist leer" no_resumable_state = "Für diese Aufnahme gibt es keinen fortsetzbaren Upload" -verification_failed = "Die Prüfung der Aufnahme ist fehlgeschlagen: {status}" \ No newline at end of file +verification_failed = "Die Prüfung der Aufnahme ist fehlgeschlagen: {status}" + +[message.cli] +binary_missing = "Mitgelieferte CLI unter {path} nicht gefunden" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index 5408ab376f6..1d23f85c5aa 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -1444,6 +1444,7 @@ hidden = "This window is hidden from Cap recordings" mirror = { text = "Mirror text", context = "Toggle in a 192px popover." } paused = "Scrolling paused with recording" placeholder = "Paste or type your script…" +words_per_minute = { text = "{wpm} wpm", context = "Teleprompter scroll speed in words per minute; {wpm} is a number such as 150." } [import] error_title = "Import Error" @@ -2660,6 +2661,11 @@ bold = "Bold" medium = "Medium" normal = "Normal" +[editor.tabs.font] +system_sans_serif = { text = "System Sans-Serif", context = "Font option for captions and keyboard overlays. Match editor.panel.font." } +system_serif = { text = "System Serif", context = "Font option for captions and keyboard overlays. Match editor.panel.font." } +system_monospace = { text = "System Monospace", context = "Font option for captions and keyboard overlays. Match editor.panel.font." } + [editor.timeline] add_shot = "Add shot" add_shot_with_look = { text = "Add shot · {look} · {seconds}s", context = "Ghost on the 3D track. {look} is the camera move name, {seconds} a length such as 2.5." } @@ -2807,6 +2813,12 @@ transition_crossfade = { text = "Crossfade", context = "Transition chip: the pre transition_fade = { text = "Fade", context = "Transition chip: fade through black into this clip." } transition_seconds = { text = "{seconds}s", context = "Transition length chip; {seconds} is a number such as 0.5." } +[editor.window.clip_menu.merge_blocker] +edge = { text = "No clip on that side", context = "Grey hint next to a disabled Merge with next/previous clip row." } +different_recording = { text = "Different recording", context = "Grey hint next to a disabled merge row: the neighbouring clip comes from another recording." } +gap = { text = "Footage was cut between them", context = "Grey hint next to a disabled merge row." } +speed = { text = "Match the clip speeds first", context = "Grey hint next to a disabled merge row." } + [editor.window.frame] add_tooltip = "Add a frame" browser = { text = "Browser", context = "Frame style: browser chrome around the recording." } @@ -3010,4 +3022,7 @@ paused = "Upload paused; the local recording is preserved" worker_failed = "The upload worker failed; the local recording is preserved" empty = "The saved recording is empty" no_resumable_state = "This recording has no resumable upload state" -verification_failed = { text = "Recording verification failed: {status}", context = "{status} is a technical status, usually in English." } \ No newline at end of file +verification_failed = { text = "Recording verification failed: {status}", context = "{status} is a technical status, usually in English." } + +[message.cli] +binary_missing = { text = "Bundled CLI binary not found at {path}", context = "{path} is a file path." } \ No newline at end of file diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index fb3d304e885..61a9ea572a8 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -1442,6 +1442,7 @@ hidden = "Esta ventana no aparece en las grabaciones de Cap" mirror = "Texto en espejo" paused = "Desplazamiento en pausa junto con la grabación" placeholder = "Pega o escribe tu guion…" +words_per_minute = "{wpm} ppm" [import] error_title = "Error de importación" @@ -2658,6 +2659,11 @@ bold = "Negrita" medium = "Media" normal = "Normal" +[editor.tabs.font] +system_sans_serif = "Sans-serif del sistema" +system_serif = "Serif del sistema" +system_monospace = "Monospace del sistema" + [editor.timeline] add_shot = "Añadir toma" add_shot_with_look = "Añadir toma · {look} · {seconds} s" @@ -2805,6 +2811,12 @@ transition_crossfade = "Fundido encadenado" transition_fade = "Fundido" transition_seconds = "{seconds} s" +[editor.window.clip_menu.merge_blocker] +edge = "No hay ningún clip a ese lado" +different_recording = "Es de otra grabación" +gap = "Se recortó metraje entre ellos" +speed = "Iguala primero la velocidad de los clips" + [editor.window.frame] add_tooltip = "Añadir un marco" browser = "Navegador" @@ -3008,4 +3020,7 @@ paused = "Subida en pausa; la grabación local se conserva" worker_failed = "El proceso de subida ha fallado; la grabación local se conserva" empty = "La grabación guardada está vacía" no_resumable_state = "Esta grabación no tiene una subida que se pueda reanudar" -verification_failed = "La verificación de la grabación ha fallado: {status}" \ No newline at end of file +verification_failed = "La verificación de la grabación ha fallado: {status}" + +[message.cli] +binary_missing = "No se encontró la CLI incluida en {path}" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index a6f0be65052..9b7ab971aad 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -1442,6 +1442,7 @@ hidden = "Cette fenêtre est masquée dans les enregistrements Cap" mirror = "Texte en miroir" paused = "Défilement en pause avec l'enregistrement" placeholder = "Collez ou saisissez votre script…" +words_per_minute = "{wpm} mots/min" [import] error_title = "Erreur d'importation" @@ -2658,6 +2659,11 @@ bold = "Gras" medium = "Moyen" normal = "Normal" +[editor.tabs.font] +system_sans_serif = "Sans-serif système" +system_serif = "Serif système" +system_monospace = "Monospace système" + [editor.timeline] add_shot = "Ajouter un plan" add_shot_with_look = "Ajouter un plan · {look} · {seconds} s" @@ -2805,6 +2811,12 @@ transition_crossfade = "Fondu enchaîné" transition_fade = "Fondu" transition_seconds = "{seconds} s" +[editor.window.clip_menu.merge_blocker] +edge = "Aucun clip de ce côté" +different_recording = "Autre enregistrement" +gap = "Des images ont été coupées entre les deux" +speed = "Harmonisez d'abord la vitesse des clips" + [editor.window.frame] add_tooltip = "Ajouter un cadre" browser = "Navigateur" @@ -3008,4 +3020,7 @@ paused = "Envoi en pause ; l'enregistrement local est conservé" worker_failed = "Le processus d'envoi a échoué ; l'enregistrement local est conservé" empty = "L'enregistrement sauvegardé est vide" no_resumable_state = "Cet enregistrement n'a pas d'envoi pouvant reprendre" -verification_failed = "La vérification de l'enregistrement a échoué : {status}" \ No newline at end of file +verification_failed = "La vérification de l'enregistrement a échoué : {status}" + +[message.cli] +binary_missing = "CLI intégrée introuvable dans {path}" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index 4f1aa2f7474..0a80a79313c 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -1442,6 +1442,7 @@ hidden = "यह विंडो Cap रिकॉर्डिंग में mirror = "टेक्स्ट मिरर करें" paused = "रिकॉर्डिंग के साथ स्क्रॉलिंग रुकी है" placeholder = "अपनी स्क्रिप्ट पेस्ट करें या लिखें…" +words_per_minute = "{wpm} शब्द/मिनट" [import] error_title = "इम्पोर्ट त्रुटि" @@ -2658,6 +2659,11 @@ bold = "बोल्ड" medium = "मीडियम" normal = "सामान्य" +[editor.tabs.font] +system_sans_serif = "सिस्टम Sans-Serif" +system_serif = "सिस्टम Serif" +system_monospace = "सिस्टम Monospace" + [editor.timeline] add_shot = "शॉट जोड़ें" add_shot_with_look = "शॉट जोड़ें · {look} · {seconds} सेकंड" @@ -2805,6 +2811,12 @@ transition_crossfade = "क्रॉसफ़ेड" transition_fade = "फ़ेड" transition_seconds = "{seconds} सेकंड" +[editor.window.clip_menu.merge_blocker] +edge = "उस तरफ़ कोई क्लिप नहीं है" +different_recording = "अलग रिकॉर्डिंग से है" +gap = "इनके बीच की फ़ुटेज काटी गई है" +speed = "पहले क्लिप की स्पीड एक जैसी करें" + [editor.window.frame] add_tooltip = "फ़्रेम जोड़ें" browser = "ब्राउज़र" @@ -3008,4 +3020,7 @@ paused = "अपलोड रुका हुआ है; लोकल रिक worker_failed = "अपलोड प्रक्रिया विफल हुई; लोकल रिकॉर्डिंग सुरक्षित है" empty = "सेव की गई रिकॉर्डिंग खाली है" no_resumable_state = "इस रिकॉर्डिंग का कोई ऐसा अपलोड नहीं है जिसे फिर से शुरू किया जा सके" -verification_failed = "रिकॉर्डिंग की जाँच विफल हुई: {status}" \ No newline at end of file +verification_failed = "रिकॉर्डिंग की जाँच विफल हुई: {status}" + +[message.cli] +binary_missing = "{path} पर बंडल की गई CLI नहीं मिली" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 8acbb99b19f..74dae204183 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -1442,6 +1442,7 @@ hidden = "このウィンドウはCapの録画に映りません" mirror = "テキストを反転" paused = "録画に合わせてスクロールを一時停止中" placeholder = "台本を貼り付けるか入力してください…" +words_per_minute = "{wpm}語/分" [import] error_title = "読み込みエラー" @@ -2658,6 +2659,11 @@ bold = "太字" medium = "中" normal = "標準" +[editor.tabs.font] +system_sans_serif = "システムSans-Serif" +system_serif = "システムSerif" +system_monospace = "システムMonospace" + [editor.timeline] add_shot = "ショットを追加" add_shot_with_look = "ショットを追加 · {look} · {seconds}秒" @@ -2805,6 +2811,12 @@ transition_crossfade = "クロスフェード" transition_fade = "フェード" transition_seconds = "{seconds}秒" +[editor.window.clip_menu.merge_blocker] +edge = "その側にクリップがありません" +different_recording = "別の録画です" +gap = "間の映像がカットされています" +speed = "先にクリップの速度を揃えてください" + [editor.window.frame] add_tooltip = "フレームを追加" browser = "ブラウザ" @@ -3008,4 +3020,7 @@ paused = "アップロードを一時停止しました。ローカルの録画 worker_failed = "アップロード処理が失敗しました。ローカルの録画は保持されています" empty = "保存された録画は空です" no_resumable_state = "この録画には再開できるアップロード状態がありません" -verification_failed = "録画の確認に失敗しました:{status}" \ No newline at end of file +verification_failed = "録画の確認に失敗しました:{status}" + +[message.cli] +binary_missing = "{path}に同梱のCLIが見つかりません" \ No newline at end of file diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index 188d01ac528..5e169b57239 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -1442,6 +1442,7 @@ hidden = "此窗口不会出现在 Cap 录制中" mirror = "镜像文字" paused = "滚动已随录制暂停" placeholder = "粘贴或输入你的脚本…" +words_per_minute = "{wpm} 字/分钟" [import] error_title = "导入错误" @@ -2658,6 +2659,11 @@ bold = "粗体" medium = "中等" normal = "常规" +[editor.tabs.font] +system_sans_serif = "系统无衬线" +system_serif = "系统衬线" +system_monospace = "系统等宽" + [editor.timeline] add_shot = "添加镜头" add_shot_with_look = "添加镜头 · {look} · {seconds} 秒" @@ -2805,6 +2811,12 @@ transition_crossfade = "交叉淡化" transition_fade = "淡入淡出" transition_seconds = "{seconds} 秒" +[editor.window.clip_menu.merge_blocker] +edge = "该侧没有片段" +different_recording = "来自不同的录制" +gap = "两者之间的素材已被剪掉" +speed = "请先统一片段速度" + [editor.window.frame] add_tooltip = "添加边框" browser = "浏览器" @@ -3008,4 +3020,7 @@ paused = "上传已暂停;本地录制已保留" worker_failed = "上传进程失败;本地录制已保留" empty = "已保存的录制为空" no_resumable_state = "此录制没有可继续的上传状态" -verification_failed = "录制验证失败:{status}" \ No newline at end of file +verification_failed = "录制验证失败:{status}" + +[message.cli] +binary_missing = "未在 {path} 找到内置的 CLI 程序" \ No newline at end of file diff --git a/apps/desktop-gpui/src/editor_edits.rs b/apps/desktop-gpui/src/editor_edits.rs index fff71bfeecb..13631e4537f 100644 --- a/apps/desktop-gpui/src/editor_edits.rs +++ b/apps/desktop-gpui/src/editor_edits.rs @@ -2770,10 +2770,12 @@ pub enum ClipMergeBlocker { impl ClipMergeBlocker { pub fn hint(self) -> &'static str { match self { - Self::Edge => "No clip on that side", - Self::DifferentRecording => "Different recording", - Self::Gap => "Footage was cut between them", - Self::Speed => "Match the clip speeds first", + Self::Edge => crate::i18n::tr::editor::window::clip_menu::merge_blocker::edge(), + Self::DifferentRecording => { + crate::i18n::tr::editor::window::clip_menu::merge_blocker::different_recording() + } + Self::Gap => crate::i18n::tr::editor::window::clip_menu::merge_blocker::gap(), + Self::Speed => crate::i18n::tr::editor::window::clip_menu::merge_blocker::speed(), } } } diff --git a/apps/desktop-gpui/src/editor_tabs.rs b/apps/desktop-gpui/src/editor_tabs.rs index 76f1c0de9f6..68a14d33227 100644 --- a/apps/desktop-gpui/src/editor_tabs.rs +++ b/apps/desktop-gpui/src/editor_tabs.rs @@ -121,6 +121,16 @@ pub const CURSOR_STYLES: [(CursorAnimationStyle, Label, Label); 4] = [ /// `FONT_OPTIONS` (`text-style.tsx:12-16`). pub const FONT_OPTIONS: [&str; 3] = ["System Sans-Serif", "System Serif", "System Monospace"]; +/// The project keeps the English `FONT_OPTIONS` value; menus show it translated. +fn font_option_label(font: &str) -> SharedString { + match font { + "System Sans-Serif" => tr::editor::tabs::font::system_sans_serif().into(), + "System Serif" => tr::editor::tabs::font::system_serif().into(), + "System Monospace" => tr::editor::tabs::font::system_monospace().into(), + other => SharedString::from(other.to_string()), + } +} + /// `TEXT_WEIGHT_OPTIONS` (`text-style.tsx:36-40`). pub const TEXT_WEIGHTS: [(u32, Label); 3] = [ (400, tr::editor::tabs::weight::normal), @@ -812,7 +822,7 @@ impl EditorWindow { SharedString::from(format!( "{} · {} · {}", (model.label)(), - model.size, + crate::i18n::decimal(model.size), (model.description)() )), model.name == selected, @@ -828,7 +838,7 @@ impl EditorWindow { .collect(), SidebarMenu::CaptionFont => FONT_OPTIONS .iter() - .map(|font| ui::MenuItem::new(*font, *font == captions.font)) + .map(|font| ui::MenuItem::new(font_option_label(font), *font == captions.font)) .collect(), SidebarMenu::CaptionHighlightStyle => CAPTION_HIGHLIGHT_STYLES .iter() @@ -850,7 +860,7 @@ impl EditorWindow { .collect(), SidebarMenu::KeyboardFont => FONT_OPTIONS .iter() - .map(|font| ui::MenuItem::new(*font, *font == keyboard.font)) + .map(|font| ui::MenuItem::new(font_option_label(font), *font == keyboard.font)) .collect(), SidebarMenu::KeyboardPosition => KEYBOARD_POSITIONS .iter() @@ -2372,7 +2382,7 @@ impl EditorWindow { .timeline .as_ref() .is_some_and(|timeline| !timeline.keyboard_segments.is_empty()); - let font_label = settings.font.clone(); + let font_label = font_option_label(&settings.font); let position_label = KEYBOARD_POSITIONS .iter() .find(|(value, _)| *value == settings.position) @@ -2698,7 +2708,7 @@ impl EditorWindow { tr::editor::tabs::captions::language::auto(), |(_, label)| label(), ); - let font_label = settings.font.clone(); + let font_label = font_option_label(&settings.font); let position_label = CAPTION_POSITIONS .iter() .find(|(value, _)| *value == settings.position) @@ -2799,7 +2809,7 @@ impl EditorWindow { .flex_shrink_0() .text_size(px(10.)) .text_color(Hsla::from(theme.gray_10)) - .child(model.size), + .child(crate::i18n::decimal(model.size)), ) .child( svg() @@ -3964,6 +3974,10 @@ mod tests { assert_eq!(CAPTION_POSITIONS[0].0, "manual"); assert_eq!(KEYBOARD_POSITIONS.len(), 6); assert_eq!(FONT_OPTIONS.len(), 3); + for font in FONT_OPTIONS { + assert_eq!(font_option_label(font).as_ref(), font); + } + assert_eq!(font_option_label("Inter").as_ref(), "Inter"); assert_eq!(TEXT_WEIGHTS.len(), 3); assert_eq!( CAMERA_BLUR_MODES.len(), diff --git a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs index 48bcfe7edcd..f24f2280823 100644 --- a/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs +++ b/apps/desktop-gpui/src/i18n/dialogs_surface_tests.rs @@ -17,6 +17,7 @@ pub(super) fn is_dialogs_surface_key(key: &str) -> bool { } const FORMATTED: &[&str] = &[ + "dialog.teleprompter.words_per_minute", "diagnostics.error.create_folder", "diagnostics.error.exited", "diagnostics.error.serialize", @@ -1090,6 +1091,10 @@ fn dialog_messages_format_like_the_code_they_replaced() { #[test] fn update_and_quit_messages_format_like_the_code_they_replaced() { + assert_eq!( + tr::dialog::teleprompter::words_per_minute(150), + format!("{} wpm", 150) + ); let error = "connection reset by peer"; let version = semver::Version::new(0, 6, 2); let pairs: Vec<(String, String)> = vec![ diff --git a/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs b/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs index 39bda5e3c16..7ba482c4968 100644 --- a/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs +++ b/apps/desktop-gpui/src/i18n/editor_shell_surface_tests.rs @@ -1154,6 +1154,25 @@ fn editor_shell_surfaces_match_the_literals_they_replaced() { ), (tr::editor::window::clip_menu::transition_fade(), "Fade"), (tr::editor::timeline::title(), "Timeline"), + ( + tr::editor::window::clip_menu::merge_blocker::edge(), + "No clip on that side", + ), + ( + tr::editor::window::clip_menu::merge_blocker::different_recording(), + "Different recording", + ), + ( + tr::editor::window::clip_menu::merge_blocker::gap(), + "Footage was cut between them", + ), + ( + tr::editor::window::clip_menu::merge_blocker::speed(), + "Match the clip speeds first", + ), + (tr::editor::tabs::font::system_sans_serif(), "System Sans-Serif"), + (tr::editor::tabs::font::system_serif(), "System Serif"), + (tr::editor::tabs::font::system_monospace(), "System Monospace"), (tr::ui::selection::select_all(), "Select all"), ]; for (translated, literal) in pairs { diff --git a/apps/desktop-gpui/src/i18n/messages.rs b/apps/desktop-gpui/src/i18n/messages.rs index fe3aa40fa56..351481784a4 100644 --- a/apps/desktop-gpui/src/i18n/messages.rs +++ b/apps/desktop-gpui/src/i18n/messages.rs @@ -216,6 +216,12 @@ const WRAPPED: &[Wrapped] = &[ nested: false, translate: |path| tr::message::recording::low_storage_preserved(path), }, + Wrapped { + prefix: "Bundled CLI binary not found at ", + suffix: "", + nested: false, + translate: |path| tr::message::cli::binary_missing(path), + }, Wrapped { prefix: "Recording verification failed: ", suffix: "", diff --git a/apps/desktop-gpui/src/i18n/messages/tests.rs b/apps/desktop-gpui/src/i18n/messages/tests.rs index 0202b7b7e4a..ea2fcc5c78b 100644 --- a/apps/desktop-gpui/src/i18n/messages/tests.rs +++ b/apps/desktop-gpui/src/i18n/messages/tests.rs @@ -36,6 +36,9 @@ fn every_message_is_still_written_by_the_app() { } } } + let cli_install = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../crates/cli-install/src/lib.rs"); + sources.push_str(&std::fs::read_to_string(cli_install).unwrap()); let texts = EXACT.iter().map(|(english, _)| *english).chain( WRAPPED .iter() diff --git a/apps/desktop-gpui/src/settings_pages.rs b/apps/desktop-gpui/src/settings_pages.rs index 29deb0a3a57..3ab0cd94daf 100644 --- a/apps/desktop-gpui/src/settings_pages.rs +++ b/apps/desktop-gpui/src/settings_pages.rs @@ -2139,7 +2139,9 @@ impl SettingsWindow { .text_size(px(12.)) .line_height(px(18.)) .text_color(Hsla::from(theme.red_9)) - .child(tr::settings::cli::load_failed(error)), + .child(tr::settings::cli::load_failed( + crate::i18n::messages::localize(error), + )), ) .child(div().child(self.button( "cli-retry", @@ -2336,7 +2338,7 @@ impl SettingsWindow { div() .text_size(px(12.)) .text_color(Hsla::from(theme.red_9)) - .child(error), + .child(crate::i18n::messages::localize(&error)), ) }) .when(status.installed && !status.on_path, |this| { diff --git a/apps/desktop-gpui/src/teleprompter_window.rs b/apps/desktop-gpui/src/teleprompter_window.rs index 27d6572ffa4..8be2fdb22c7 100644 --- a/apps/desktop-gpui/src/teleprompter_window.rs +++ b/apps/desktop-gpui/src/teleprompter_window.rs @@ -871,7 +871,9 @@ impl TeleprompterWindow { .text_right() .text_size(px(10.)) .text_color(Hsla::from(theme.gray_9)) - .child(format!("{} wpm", self.state.words_per_minute)), + .child(tr::dialog::teleprompter::words_per_minute( + self.state.words_per_minute, + )), ), ) .child( From 4c212e5d52a7d6032ec1bcfe897d53b9f6e440f0 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:24:14 +0000 Subject: [PATCH 23/25] fix(desktop-gpui): shorten the French and Spanish device badges and sign-in row so they fit --- apps/desktop-gpui/locales/es.toml | 8 +++----- apps/desktop-gpui/locales/fr.toml | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index 61a9ea572a8..11937e37fc8 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -270,7 +270,7 @@ account = "Cuenta" cancel_sign_in = "Cancelar inicio de sesión" check_for_updates = "Buscar actualizaciones" checking_for_updates = "Buscando actualizaciones…" -click_to_sign_in = "Haz clic para iniciar sesión" +click_to_sign_in = "Inicia sesión" downloading = "Descargando v{version}…" downloading_progress = "Descargando v{version}… {percent} %" installing = "Instalando v{version}…" @@ -1014,8 +1014,8 @@ allow = "Permitir" allow_in_settings = "Permitir en Ajustes" asking = "Solicitando…" not_connected = "Desconectado" -off = "Desactivado" -on = "Activado" +off = "No" +on = "Sí" [main.error] camera_only_needs_camera = "Para grabar solo con cámara hay que seleccionar una cámara." @@ -1731,8 +1731,6 @@ sign_in = "Inicia sesión para reintentar la subida" uploaded = "Subido" uploading = "Subiendo" -# Editor sidebar, segment panels, colour correction, audio library and crop dialog. - [editor.audio.library] add_hint = "Añade audio, música u otros sonidos a tu vídeo" add_title = "Añadir audio" diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index 9b7ab971aad..60cd75dd83f 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -270,7 +270,7 @@ account = "Compte" cancel_sign_in = "Annuler la connexion" check_for_updates = "Rechercher des mises à jour" checking_for_updates = "Recherche de mises à jour…" -click_to_sign_in = "Cliquez pour vous connecter" +click_to_sign_in = "Connectez-vous" downloading = "Téléchargement de la v{version}…" downloading_progress = "Téléchargement de la v{version}… {percent} %" installing = "Installation de la v{version}…" @@ -1014,8 +1014,8 @@ allow = "Autoriser" allow_in_settings = "Activer dans Réglages" asking = "Demande…" not_connected = "Déconnecté" -off = "Désactivé" -on = "Activé" +off = "Non" +on = "Oui" [main.error] camera_only_needs_camera = "L'enregistrement caméra seule nécessite une caméra sélectionnée." @@ -1731,8 +1731,6 @@ sign_in = "Connectez-vous pour relancer l'envoi" uploaded = "Envoyé" uploading = "Envoi en cours" -# Editor sidebar, segment panels, colour correction, audio library and crop dialog. - [editor.audio.library] add_hint = "Ajoutez de l'audio, de la musique ou d'autres sons à votre vidéo" add_title = "Ajouter de l'audio" From 945f79955e427a8b3ba1b513320aecf4840174f5 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:24:14 +0000 Subject: [PATCH 24/25] fix(desktop-gpui): keep clip menu labels whole and wrap the disabled-row reason --- apps/desktop-gpui/src/editor_window.rs | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/desktop-gpui/src/editor_window.rs b/apps/desktop-gpui/src/editor_window.rs index 02e2e44f8de..64edb658fe8 100644 --- a/apps/desktop-gpui/src/editor_window.rs +++ b/apps/desktop-gpui/src/editor_window.rs @@ -1859,20 +1859,28 @@ fn clip_menu_row( .flex_none() .text_color(icon_color), ) - .child( - div() - .flex_1() - .min_w_0() - .overflow_hidden() - .whitespace_nowrap() - .text_ellipsis() - .child(label), - ); + .overflow_hidden(); + // A disabled row keeps its own label whole; the reason beside it wraps + // onto a second line instead, which longer translations need. + let hinted = matches!(trailing, ClipMenuTrailing::Hint(_)) && !enabled; + let row = row.child( + div() + .when(hinted, |label| label.flex_none()) + .when(!hinted, |label| label.flex_1().min_w_0()) + .overflow_hidden() + .whitespace_nowrap() + .text_ellipsis() + .child(label), + ); match trailing { ClipMenuTrailing::Hint(hint) if !enabled => row.child( div() - .flex_none() + .flex_1() + .min_w_0() + .text_right() .text_size(px(10.5)) + .line_height(px(12.)) + .line_clamp(2) .text_color(Hsla::from(theme.editor.text_3)) .child(hint), ), From accf131b43bb9f5e156d825bef83019c1cae9b6e Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:24:14 +0000 Subject: [PATCH 25/25] fix(desktop-gpui): translate the error inside the quit dialog and drop comments that restate code --- apps/desktop-gpui/locales/de.toml | 2 -- apps/desktop-gpui/locales/en.toml | 2 -- apps/desktop-gpui/locales/hi.toml | 2 -- apps/desktop-gpui/locales/ja.toml | 2 -- apps/desktop-gpui/locales/zh-CN.toml | 2 -- apps/desktop-gpui/src/editor_tabs.rs | 1 - apps/desktop-gpui/src/menus.rs | 4 +++- 7 files changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index 32fa0ab7e2c..66e4f6c0a58 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -1731,8 +1731,6 @@ sign_in = "Zum erneuten Hochladen anmelden" uploaded = "Hochgeladen" uploading = "Wird hochgeladen" -# Editor sidebar, segment panels, colour correction, audio library and crop dialog. - [editor.audio.library] add_hint = "Fügen Sie Ihrem Video Audio, Musik oder andere Klänge hinzu" add_title = "Audio hinzufügen" diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index 1d23f85c5aa..826160211f9 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -1733,8 +1733,6 @@ sign_in = "Sign in to retry upload" uploaded = "Uploaded" uploading = "Uploading" -# Editor sidebar, segment panels, colour correction, audio library and crop dialog. - [editor.audio.library] add_hint = "Add audio, music or other sounds to your video" add_title = "Add audio" diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index 0a80a79313c..615fa7e1b1c 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -1731,8 +1731,6 @@ sign_in = "अपलोड दोबारा करने के लिए स uploaded = "अपलोड हो गया" uploading = "अपलोड हो रहा है" -# Editor sidebar, segment panels, colour correction, audio library and crop dialog. - [editor.audio.library] add_hint = "अपने वीडियो में ऑडियो, संगीत या दूसरी आवाज़ें जोड़ें" add_title = "ऑडियो जोड़ें" diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 74dae204183..6290c258225 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -1731,8 +1731,6 @@ sign_in = "サインインしてアップロードを再試行" uploaded = "アップロード済み" uploading = "アップロード中" -# Editor sidebar, segment panels, colour correction, audio library and crop dialog. - [editor.audio.library] add_hint = "動画に音声や音楽などのサウンドを追加" add_title = "音声を追加" diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index 5e169b57239..590ca0dfd42 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -1731,8 +1731,6 @@ sign_in = "登录以重试上传" uploaded = "已上传" uploading = "正在上传" -# Editor sidebar, segment panels, colour correction, audio library and crop dialog. - [editor.audio.library] add_hint = "为视频添加音频、音乐或其他声音" add_title = "添加音频" diff --git a/apps/desktop-gpui/src/editor_tabs.rs b/apps/desktop-gpui/src/editor_tabs.rs index 68a14d33227..3a49d8e5c14 100644 --- a/apps/desktop-gpui/src/editor_tabs.rs +++ b/apps/desktop-gpui/src/editor_tabs.rs @@ -48,7 +48,6 @@ use crate::{ // Catalogues // --------------------------------------------------------------------------- -/// A catalogue label, read in the current language each time it is drawn. pub type Label = fn() -> &'static str; /// `CAMERA_SHAPES` (`ConfigSidebar.tsx:381-391`). diff --git a/apps/desktop-gpui/src/menus.rs b/apps/desktop-gpui/src/menus.rs index 125471a1977..3d7b7d88d3d 100644 --- a/apps/desktop-gpui/src/menus.rs +++ b/apps/desktop-gpui/src/menus.rs @@ -773,7 +773,9 @@ fn apply_quit_action(action: QuitAction, cx: &mut App) { cx.global_mut::().observer = None; let message = RecordingSession::global(cx).update(cx, |session, cx| { let message = match session.error.as_deref() { - Some(error) => tr::dialog::quit::canceled(error), + Some(error) => { + tr::dialog::quit::canceled(crate::i18n::messages::localize(error)) + } None if action == QuitAction::TimedOut => tr::dialog::quit::pending().into(), None => tr::dialog::quit::unconfirmed().into(), };