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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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 67ca26829ffaeb58e17e2ccf48ef5fcdad5f351c Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:48:09 +0000 Subject: [PATCH 07/13] feat(desktop-gpui): localize the settings window and pages --- apps/desktop-gpui/locales/de.toml | 691 ++++++ apps/desktop-gpui/locales/en.toml | 691 ++++++ apps/desktop-gpui/locales/es.toml | 691 ++++++ apps/desktop-gpui/locales/fr.toml | 691 ++++++ apps/desktop-gpui/locales/hi.toml | 691 ++++++ apps/desktop-gpui/locales/ja.toml | 691 ++++++ apps/desktop-gpui/locales/zh-CN.toml | 691 ++++++ apps/desktop-gpui/src/i18n/mod.rs | 2 + .../src/i18n/settings_surface_tests.rs | 1607 ++++++++++++++ apps/desktop-gpui/src/i18n/tests.rs | 1 + apps/desktop-gpui/src/settings_pages.rs | 1872 ++++++++++------- apps/desktop-gpui/src/settings_window.rs | 246 +-- apps/desktop-gpui/src/store.rs | 34 +- 13 files changed, 7724 insertions(+), 875 deletions(-) create mode 100644 apps/desktop-gpui/src/i18n/settings_surface_tests.rs diff --git a/apps/desktop-gpui/locales/de.toml b/apps/desktop-gpui/locales/de.toml index 6a03591f376..5821e023ccb 100644 --- a/apps/desktop-gpui/locales/de.toml +++ b/apps/desktop-gpui/locales/de.toml @@ -239,6 +239,697 @@ title = "Datenschutz" [settings.general.privacy.telemetry] description = "Cap nutzt anonyme Telemetrie, um die Zuverlässigkeit zu verbessern und Fehler zu beheben. Wir erfassen niemals Aufnahmeinhalte, Fenstertitel, Dateipfade oder persönliche Daten." label = "Anonyme Telemetrie teilen" +[settings.prompt] +ok = "OK" + +[settings.sidebar] +account = "Konto" +cancel_sign_in = "Anmeldung abbrechen" +check_for_updates = "Nach Updates suchen" +click_to_sign_in = "Zum Anmelden klicken" +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" +sign_out = "Abmelden" +signed_in = "Angemeldet" + +[settings.recordings] +corrupted_body = "Die Aufnahme ist fehlgeschlagen, daher kann diese Datei im Editor Probleme machen! Wenn Sie die Datei nicht wiederherstellen können, wenden Sie sich bitte an den Support!" +corrupted_title = "Die Aufnahme ist möglicherweise beschädigt" +delete = "Löschen" +description = "Verwalten Sie Ihre Aufnahmen und führen Sie Aktionen aus." +edit = "Bearbeiten" +empty_library = "Keine Aufnahmen gefunden" +failed = "Aufnahme fehlgeschlagen" +import = "Importieren" +in_progress = "Aufnahme läuft" +load_more = "Mehr laden" +open_bundle = "Aufnahmepaket öffnen" +open_link = "Link öffnen" +retry_upload = "Hochladen wiederholen" +show_all = "Alle anzeigen" +title = "Aufnahmen" + +[settings.recordings.empty] +all = "Keine Aufnahmen" +all_matching = "Keine passenden Aufnahmen" +instant = "Keine Instant-Aufnahmen" +instant_matching = "Keine passenden Instant-Aufnahmen" +studio = "Keine Studio-Aufnahmen" +studio_matching = "Keine passenden Studio-Aufnahmen" + +[settings.screenshots] +copy = "In die Zwischenablage kopieren" +delete = "Löschen" +description = "Verwalten Sie Ihre Bildschirmfotos und führen Sie Aktionen aus." +empty = "Keine Bildschirmfotos" +empty_library = "Keine Bildschirmfotos gefunden" +empty_matching = "Keine passenden Bildschirmfotos" +import = "Bild importieren" +load_more = "Mehr laden" +open_folder = "Ordner öffnen" +save_as = "Speichern unter" +title = "Bildschirmfotos" + +[settings.quality] +save_failed = "Ihre Aufnahmeeinstellungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut." + +[settings.quality.studio] +balanced = "Ausgewogen" +balanced_description = "Scharfe Details bei vernünftiger Dateigröße. Richtig 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." +recommended = "Empfohlen" +title = "Studio-Aufnahmen" +ultra = "Maximale Details" +ultra_description = "Behält die meisten Details für aufwendige Bearbeitung. Belegt den meisten Speicherplatz." + +[settings.quality.instant] +description = "Wird während der Aufnahme hochgeladen, sodass der Link bereit ist, sobald Sie stoppen. Für die beste Qualität nutzen Sie den Studio-Modus: Er nimmt in der höchsten Qualität auf, die Ihr Bildschirm erlaubt, und Sie können bis 4K exportieren." +free_plan = "720p im kostenlosen Tarif. Cap Pro nimmt bis zu 4K auf." +max_resolution = "Maximale Auflösung" +res_1080p = "Scharfer Text bei praktischer Upload-Größe." +res_1440p = "Mehr Details für große, hochauflösende Bildschirme." +res_4k = "Die meisten Details. Am besten mit schneller Verbindung." +res_720p = "Kleinste Uploads, daher sind Links am schnellsten bereit." +title = "Instant-Aufnahmen" + +[settings.quality.sharing] +auto_open = "Links zum Teilen automatisch öffnen" +auto_open_description = "Öffnet den Link in Ihrem Browser, wenn ein Upload abgeschlossen ist." +title = "Teilen" + +[settings.options.studio_quality] +balanced = "Ausgewogen" +compatibility = "Kompatibilität" +ultra = "Ultra" + +[settings.options.main_window_start] +close = "Schließen" +minimise = "Minimieren" + +[settings.options.post_studio] +do_nothing = "Nichts tun" +open_editor = "Editor öffnen" +show_cap = "Cap anzeigen" + +[settings.options.post_screenshot] +copy = "In Zwischenablage kopieren" +copy_and_open_editor = "Kopieren, Editor öffnen" +do_nothing = "Nichts tun" +open_editor = "Editor öffnen" + +[settings.options.post_deletion] +do_nothing = "Nichts tun" +reopen = "Rekorder wieder öffnen" + +[settings.options.preview_quality] +full = "Voll" +half = "Halb" +quarter = "Viertel" + +[settings.shortcuts] +blocked_while_recording = "Tastenkürzel können nicht geändert werden, solange diese Aufnahme ihre Stopp-Taste belegt. Stoppen Sie zuerst die Aufnahme." +needs_modifier_macos = "Verwenden Sie ⌘, ⌃ oder ⌥, damit das Tastenkürzel beim Tippen nicht stört." +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_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 …" +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" + +[settings.shortcuts.after_screenshot] +hint = "Für jedes Bildschirmfoto, per Tastenkürzel oder aus Cap" +title = "Nach einem Bildschirmfoto" + +[settings.shortcuts.after_studio] +hint = "Beim Stoppen" +title = "Nach einer Studio-Aufnahme" + +[settings.shortcuts.after_instant] +hint = "Der Link zum Teilen wird immer kopiert" +title = "Nach einer Instant-Aufnahme" + +[settings.shortcuts.outcome] +instant_copy_link = "Nur Link kopieren" +instant_copy_link_hint = "Nichts wird geöffnet" +instant_open_link = "Link öffnen" +instant_open_link_hint = "In Ihrem Browser" +saved_to_library = "In der Bibliothek gespeichert" +screenshot_copy = "Kopieren" +screenshot_copy_and_edit = "Kopieren und bearbeiten" +screenshot_copy_and_edit_hint = "Beides zugleich" +screenshot_copy_hint = "Überall einfügen" +screenshot_do_nothing = "Nichts tun" +screenshot_open_editor = "Editor öffnen" +screenshot_open_editor_hint = "Erst beschriften" +studio_do_nothing = "Nichts tun" +studio_open_editor = "Editor öffnen" +studio_open_editor_hint = "Sofort bearbeiten" +studio_show_cap = "Cap anzeigen" +studio_show_cap_hint = "Zurück zum Rekorder" + +[settings.shortcuts.label] +cycle_mode = "Aufnahmemodus wechseln" +fallback = "Tastenkürzel" +open_picker = "Aufnahmeauswahl öffnen" +pause = "Pausieren oder fortsetzen" +record_area = "Bereich aufnehmen" +record_display = "Bildschirm aufnehmen" +record_window = "Fenster aufnehmen" +restart = "Aufnahme neu starten" +screenshot_area = "Bereich fotografieren" +screenshot_display = "Bildschirm fotografieren" +screenshot_window = "Fenster fotografieren" +start_instant = "Instant-Aufnahme starten" +start_studio = "Studio-Aufnahme starten" +stop = "Aufnahme stoppen" + +[settings.shortcuts.effect] +cycle_mode = "zwischen Studio-, Instant- und Bildschirmfoto-Modus zu wechseln" +instant_copy = "den ausgewählten Bildschirm oder das ausgewählte Fenster im Instant-Modus aufzunehmen und beim Stoppen den Link zum Teilen zu kopieren" +instant_copy_and_open = "den ausgewählten Bildschirm oder das ausgewählte Fenster im Instant-Modus aufzunehmen und beim Stoppen den Link zum Teilen zu kopieren und zu öffnen" +open_picker = "Cap zu öffnen und auszuwählen, was Sie aufnehmen möchten" +pause = "die laufende Aufnahme zu pausieren oder fortzusetzen" +picker_area_instant = "einen Bereich auszuwählen und ihn im Instant-Modus aufzunehmen" +picker_area_screenshot = "einen Bereich auszuwählen und ein Bildschirmfoto davon zu machen" +picker_area_studio = "einen Bereich auszuwählen und ihn im Studio-Modus aufzunehmen" +picker_display_instant = "einen Bildschirm zu wählen und ihn im Instant-Modus aufzunehmen" +picker_display_screenshot = "einen Bildschirm zu wählen und ein Bildschirmfoto davon zu machen" +picker_display_studio = "einen Bildschirm zu wählen und ihn im Studio-Modus aufzunehmen" +picker_window_instant = "ein Fenster zu wählen und es im Instant-Modus aufzunehmen" +picker_window_screenshot = "ein Fenster zu wählen und ein Bildschirmfoto davon zu machen" +picker_window_studio = "ein Fenster zu wählen und es im Studio-Modus aufzunehmen" +restart = "die aktuelle Aufnahme zu verwerfen und neu zu beginnen" +screenshot_area_copy = "einen Bereich auszuwählen und das Bildschirmfoto in die Zwischenablage zu kopieren" +screenshot_area_copy_and_open_editor = "einen Bereich auszuwählen, das Bildschirmfoto zu kopieren und den Editor zu öffnen" +screenshot_area_open_editor = "einen Bereich auszuwählen und das Bildschirmfoto im Editor zu öffnen" +screenshot_area_save = "einen Bereich auszuwählen und das Bildschirmfoto in Ihrer Bibliothek zu speichern, ohne etwas zu öffnen" +screenshot_display_copy = "den Bildschirm unter Ihrem Zeiger aufzunehmen und das Bildschirmfoto in die Zwischenablage zu kopieren" +screenshot_display_copy_and_open_editor = "den Bildschirm unter Ihrem Zeiger aufzunehmen, das Bildschirmfoto zu kopieren und den Editor zu öffnen" +screenshot_display_open_editor = "den Bildschirm unter Ihrem Zeiger aufzunehmen und das Bildschirmfoto im Editor zu öffnen" +screenshot_display_save = "den Bildschirm unter Ihrem Zeiger aufzunehmen und das Bildschirmfoto in Ihrer Bibliothek zu speichern, ohne etwas zu öffnen" +screenshot_window_copy = "das Fenster unter Ihrem Zeiger aufzunehmen und das Bildschirmfoto in die Zwischenablage zu kopieren" +screenshot_window_copy_and_open_editor = "das Fenster unter Ihrem Zeiger aufzunehmen, das Bildschirmfoto zu kopieren und den Editor zu öffnen" +screenshot_window_open_editor = "das Fenster unter Ihrem Zeiger aufzunehmen und das Bildschirmfoto im Editor zu öffnen" +screenshot_window_save = "das Fenster unter Ihrem Zeiger aufzunehmen und das Bildschirmfoto in Ihrer Bibliothek zu speichern, ohne etwas zu öffnen" +stop = "die laufende Aufnahme zu stoppen" +studio = "den ausgewählten Bildschirm oder das ausgewählte Fenster im Studio-Modus aufzunehmen" +studio_open_editor = "den ausgewählten Bildschirm oder das ausgewählte Fenster im Studio-Modus aufzunehmen und beim Stoppen den Editor zu öffnen" + +[settings.shortcuts.sentence] +cycle_mode = "Zwischen Studio-, Instant- und Bildschirmfoto-Modus wechseln." +instant_copy = "Ausgewählten Bildschirm oder ausgewähltes Fenster im Instant-Modus aufnehmen und beim Stoppen den Link zum Teilen kopieren." +instant_copy_and_open = "Ausgewählten Bildschirm oder ausgewähltes Fenster im Instant-Modus aufnehmen und beim Stoppen den Link zum Teilen kopieren und öffnen." +open_picker = "Cap öffnen und auswählen, was Sie aufnehmen möchten." +pause = "Die laufende Aufnahme pausieren oder fortsetzen." +picker_area_instant = "Bereich auswählen und im Instant-Modus aufnehmen." +picker_area_screenshot = "Bereich auswählen und ein Bildschirmfoto davon machen." +picker_area_studio = "Bereich auswählen und im Studio-Modus aufnehmen." +picker_display_instant = "Bildschirm wählen und im Instant-Modus aufnehmen." +picker_display_screenshot = "Bildschirm wählen und ein Bildschirmfoto davon machen." +picker_display_studio = "Bildschirm wählen und im Studio-Modus aufnehmen." +picker_window_instant = "Fenster wählen und im Instant-Modus aufnehmen." +picker_window_screenshot = "Fenster wählen und ein Bildschirmfoto davon machen." +picker_window_studio = "Fenster wählen und im Studio-Modus aufnehmen." +restart = "Die aktuelle Aufnahme verwerfen und neu beginnen." +screenshot_area_copy = "Bereich auswählen und das Bildschirmfoto in die Zwischenablage kopieren." +screenshot_area_copy_and_open_editor = "Bereich auswählen, das Bildschirmfoto kopieren und den Editor öffnen." +screenshot_area_open_editor = "Bereich auswählen und das Bildschirmfoto im Editor öffnen." +screenshot_area_save = "Bereich auswählen und das Bildschirmfoto in Ihrer Bibliothek speichern, ohne etwas zu öffnen." +screenshot_display_copy = "Bildschirm unter dem Zeiger aufnehmen und das Bildschirmfoto in die Zwischenablage kopieren." +screenshot_display_copy_and_open_editor = "Bildschirm unter dem Zeiger aufnehmen, das Bildschirmfoto kopieren und den Editor öffnen." +screenshot_display_open_editor = "Bildschirm unter dem Zeiger aufnehmen und das Bildschirmfoto im Editor öffnen." +screenshot_display_save = "Bildschirm unter dem Zeiger aufnehmen und das Bildschirmfoto in Ihrer Bibliothek speichern, ohne etwas zu öffnen." +screenshot_window_copy = "Fenster unter dem Zeiger aufnehmen und das Bildschirmfoto in die Zwischenablage kopieren." +screenshot_window_copy_and_open_editor = "Fenster unter dem Zeiger aufnehmen, das Bildschirmfoto kopieren und den Editor öffnen." +screenshot_window_open_editor = "Fenster unter dem Zeiger aufnehmen und das Bildschirmfoto im Editor öffnen." +screenshot_window_save = "Fenster unter dem Zeiger aufnehmen und das Bildschirmfoto in Ihrer Bibliothek speichern, ohne etwas zu öffnen." +stop = "Die laufende Aufnahme stoppen." +studio = "Ausgewählten Bildschirm oder ausgewähltes Fenster im Studio-Modus aufnehmen." +studio_open_editor = "Ausgewählten Bildschirm oder ausgewähltes Fenster im Studio-Modus aufnehmen und beim Stoppen den Editor öffnen." + +[settings.cli] +command = "Befehl" +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." +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 ..." +repair = "Reparieren" +repairing = "Wird repariert ..." +retry = "Erneut versuchen" +target = "Ziel" +title = "Befehlszeile" + +[settings.transcription] +active_title = "Aktive Hinweise" +applied_note = "Diese Hinweise werden angewendet, wenn Sie im Editor Untertitel erstellen." +clear = "Leeren" +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 ..." +terms_description = "Fügen Sie jeweils einen Begriff hinzu, um Tipp- und Formatierungsfehler zu vermeiden." +terms_title = "Gemerkte Begriffe" +title = "Transkription" + +[settings.experimental] +developer = "Entwickler" +developer_description = "Wird nur in Entwicklungs-Builds angezeigt." +muxer = "Muxer in separatem Prozess" +muxer_description = "Führt den Muxer für fragmentiertes MP4 in einem isolierten Unterprozess aus, damit Muxer-Abstürze Ihre Aufnahme nicht beenden. Erfordert das mitgelieferte Programm cap-muxer." +native_app = "Native App verwenden" +native_app_description = "Ausschalten, um diese App zu schließen und die klassische Tauri-App in der Entwicklungsumgebung wieder zu öffnen. Aufnahmen und Einstellungen werden geteilt." +reliability = "Zuverlässigkeit" + +[settings.experimental.switch] +busy = "Schließen Sie Ihre Aufnahme, den Export, Upload, Import oder die Transkription ab, bevor Sie zur klassischen App wechseln." +check_failed = "Es konnte nicht geprüft werden, ob die klassische Cap-App geöffnet wurde: {error}" +data_stays = "Ihre Aufnahmen und Einstellungen bleiben genau dort, wo sie sind." +failed = "Die klassische Cap-App konnte nicht geöffnet werden." +not_found = "Die klassische Cap-App, zu der gewechselt werden soll, wurde nicht gefunden." +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." +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] +app_version = "App-Version" +capture_support = "Aufnahmeunterstützung" +capture_supported = "Bildschirmaufnahme: Unterstützt" +capture_unsupported = "Bildschirmaufnahme: Nicht unterstützt" +community_description = "Haben Sie Fragen, möchten Sie Ideen teilen oder einfach dabei sein? Treten Sie der Cap-Community auf Discord bei." +community_title = "Der Community beitreten" +debug_description = "Laden Sie Ihre Protokolle hoch, damit wir Probleme mit Cap untersuchen können. Es sind keine persönlichen Daten enthalten." +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 ..." +submit = "Feedback senden" +submit_failed = "Feedback konnte nicht gesendet werden" +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 ..." + +[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." +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_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 ..." +show_file = "Datei anzeigen" +stop_recording_first = "Stoppen Sie die laufende Aufnahme, bevor Sie eine Diagnose starten." +title = "Diagnosebericht" +try_again = "Erneut versuchen" +warning_body = "Ein Vollbildfenster blinkt schwarz-weiß, während 1-kHz-Pieptöne über Ihre Lautsprecher laufen, und Cap nimmt das auf und misst, wie weit Bild und Ton auseinandergedriftet sind. Benutzen Sie den Computer während des Tests nicht. Beide Modi zusammen dauern ein paar Minuten." +warning_title = "Der Test belegt Ihren Bildschirm und macht Geräusche." + +[settings.feedback.diagnostic.verdict] +environment_only = "NUR UMGEBUNG" +fail = "FEHLGESCHLAGEN" +inconclusive = "NICHT EINDEUTIG" +pass = "BESTANDEN" +warn = "WARNUNG" + +[settings.sign_in_required] +body = "Bitte melden Sie sich an, um fortzufahren. Alternativ schreiben Sie an hello@cap.so oder treten Sie unserem Discord unter cap.link/discord bei" +title = "Anmeldung erforderlich" + +[settings.changelog] +fetch_failed = "Änderungsprotokoll konnte nicht abgerufen werden" +loading = "Änderungsprotokoll wird geladen ..." +new = "NEU" +version = "Version {version} - {date}" + +[settings.license] +activate = "Lizenz aktivieren" +activating = "Wird aktiviert ..." +active_title = "Desktop-Lizenz" +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." +desktop_name = "Desktop-Lizenz" +expires = "Läuft ab" +explore_description = "Vergleichen Sie aktuelle Preise und alle enthaltenen Leistungen auf unserer Website." +explore_desktop = "Desktop-Lizenz · Kommerzielle Nutzung von Desktop-Rekorder und -Editor." +explore_pro = "Cap Pro · Eine Desktop-Lizenz plus Teilen über die Cloud, KI-Funktionen und Zusammenarbeit." +explore_title = "Tarife entdecken" +free_description = "Lokal aufnehmen und bearbeiten für die private Nutzung. Wählen Sie einen kostenpflichtigen Tarif für die kommerzielle Nutzung oder mehr Cloud-Funktionen." +have_key = "Haben Sie einen Lizenzschlüssel?" +key = "Lizenzschlüssel" +no_instance = "Keine Instanz-ID gefunden" +opens_pricing = "Öffnet cap.so/pricing in Ihrem Browser." +placeholder = "Lizenzschlüssel" +plan_title = "Ihr Tarif" +pro_description = "Ihr Konto umfasst Teilen über die Cloud, Pro-Funktionen und eine Desktop-Lizenz für die kommerzielle Nutzung." +refresh = "Tarif aktualisieren" +refresh_failed = "Ihr Tarif konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut." +save_failed = "Die Lizenz konnte nicht gespeichert werden" +sign_in_hint = "Sie haben bereits Cap Pro? Melden Sie sich über die Seitenleiste mit Ihrem Konto an." +title = "Tarif & Lizenz" +view_pricing = "Tarife & Preise ansehen ↗" + +[settings.integrations] +configure = "Einrichten" +description = "Richten Sie Integrationen ein, um den Funktionsumfang von Cap zu erweitern und Dienste von Drittanbietern anzubinden." +google_drive_description = "Verbinden Sie Google Drive für neue Uploads mit Links zum Teilen. Cap speichert neue Videos in einem privaten Cap-Ordner in Ihrem Drive und stellt sie nach den üblichen Zugriffsprüfungen weiterhin über Cap bereit." +managed = "Von Ihrer Organisation verwaltet" +managed_by = "Von Ihrer Organisation verwaltet: {organization}" +request_timed_out = "Die Anfrage hat zu lange gedauert" +s3_description = "Verbinden Sie Ihren eigenen S3-Bucket für die volle Kontrolle über Ihre Datenspeicherung. Alle neuen Uploads mit Links zum Teilen werden automatisch in Ihren konfigurierten S3-Bucket hochgeladen, sodass Ihre Inhalte vollständig Ihnen gehören und unter Ihrer Kontrolle bleiben. Ideal für Organisationen, die Datenhoheit und eigene Speicherrichtlinien benötigen." +s3_name = "S3-Konfiguration" +title = "Integrationen" +upgrade = "Auf Pro upgraden" + +[settings.integrations.s3] +access_key = "Zugriffsschlüssel-ID" +bucket = "Bucket-Name" +configuration = "Konfiguration" +delete_failed = "S3-Konfiguration konnte nicht gelöscht werden" +deleted = "S3-Konfiguration erfolgreich gelöscht" +endpoint = "Endpunkt" +fetch_failed = "S3-Konfiguration konnte nicht abgerufen werden" +guide = "Anleitung zur Speicherkonfiguration" +guide_intro = "Das Einrichten und Verbinden Ihres Speicher-Buckets mit Cap sollte keine 10 Minuten dauern. Lesen Sie die\u00A0{guide},\u00A0um loszulegen." +other_provider = "Anderer S3-kompatibler Dienst" +provider = "Speicheranbieter" +region = "Region" +remove = "Konfiguration entfernen" +removing = "Wird entfernt ..." +save_failed = "S3-Konfiguration konnte nicht gespeichert werden" +saved = "S3-Konfiguration erfolgreich 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 ..." + +[settings.integrations.gdrive] +active = "Aktiv" +active_for_uploads = "Aktiv für neue Uploads" +cached = "Zwischengespeichert {time}" +connect = "Google Drive verbinden" +connect_failed = "Die Verbindung mit Google Drive konnte nicht gestartet werden" +connected_inactive = "Verbunden, aber nicht aktiv" +connection_description = "Google Drive speichert neue Uploads in einem privaten Cap-Ordner in Ihrem Drive. Bestehende von Cap gehostete und S3-Videos bleiben in ihrem aktuellen Speicher." +connection_title = "Verbindung" +disconnect = "Trennen" +disconnect_failed = "Google Drive konnte nicht getrennt werden" +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 ..." +refresh = "Aktualisieren" +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 ..." +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 ..." +working = "Die Google-Drive-Verbindung funktioniert" +working_for = "Die Google-Drive-Verbindung funktioniert für {email}" + +[settings.automations] +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" +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." +condition_never_matches = "Diese Bedingung trifft beim gewählten Auslöser nie zu." +copy_link = "Link in die Zwischenablage kopieren" +dangerous = "In der Cap-CLI führt diese Automatisierung Befehle aus oder sendet Netzwerkanfragen mit Ihren Berechtigungen. Verwenden Sie nur Werte, denen Sie vertrauen." +delete = "Löschen" +description = "Regeln werden mit der Cap-CLI geteilt. Cap speichert diese Regeln, führt Automatisierungen aber noch nicht aus." +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" +name = "Name" +new = "Neue Automatisierung" +no_presets = "Noch keine Vorgaben. Erstellen Sie zuerst eine im Editor." +only_if = "NUR AUSFÜHREN, WENN" +open_in_browser = "Im Browser öffnen" +retry_save = "Speichern wiederholen" +run_through_shell = "Über die Shell ausführen" +save_failed = "Automatisierungen konnten nicht gespeichert werden. Ihre Änderungen wurden nicht gespeichert." +start_from_scratch = "Neu beginnen" +summary_empty = "{trigger} → noch keine Aktionen" +templates_description = "Mit einem Klick eine fertige Automatisierung hinzufügen. Danach lässt sich alles anpassen." +templates_title = "Vorlagen" +then = "DANN DIES TUN" +title = "Automatisierungen" +when = "WENN DIES PASSIERT" + +[settings.automations.status] +disabled_saved = "Deaktiviert · Gespeichert" +disabled_unsaved = "Deaktiviert · Nicht gespeichert" +enabled_saved = "Aktiviert · Gespeichert" +enabled_unsaved = "Aktiviert · Nicht gespeichert" + +[settings.automations.field] +arguments = "Argumente (durch Leerzeichen getrennt)" +body = "Text" +body_template = "Body-Vorlage (optional)" +compression = "Komprimierung" +destination = "Zielordner (optional, leer = Projektordner)" +filename = "Dateinamenvorlage (optional)" +folder = "Ordner" +format = "Format" +frame_rate = "Bildrate" +method = "Methode" +organization = "Organisations-ID (optional)" +preset = "Vorgabe" +program = "Programm" +resolution = "Auflösung" +source = "Quelle" +title = "Titel" +url = "URL" + +[settings.automations.placeholder] +organization_id = "Organisations-ID" +project_folder = "Projektordner" +window_title = "z. B. Slack" + +[settings.automations.clipboard] +original = "Originalaufnahme" +rendered = "Bearbeitet / gerendert" + +[settings.automations.target] +area = "Bereich" +display = "Bildschirm" +window = "Fenster" + +[settings.automations.compression] +maximum = "Maximal" +potato = "Kartoffel" +social = "Social Media" +web = "Web" + +[settings.automations.trigger.screenshot_taken] +label = "Bei aufgenommenem Bildschirmfoto" +noun = "Bildschirmfoto" +phrase = "Bildschirmfoto aufgenommen" +runs = "Wird bei jedem Bildschirmfoto ausgeführt." + +[settings.automations.trigger.studio_finished] +label = "Bei beendeter 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" +noun = "Instant-Aufnahme" +phrase = "Instant-Aufnahme endet" +runs = "Wird nach jeder Instant-Aufnahme ausgeführt." + +[settings.automations.trigger.recording_started] +label = "Bei gestarteter Aufnahme" +noun = "Aufnahmestart" +phrase = "Aufnahme startet" +runs = "Wird bei jedem Aufnahmestart ausgeführt." + +[settings.automations.trigger.upload_completed] +label = "Bei abgeschlossenem Upload" +noun = "Upload" +phrase = "Upload abgeschlossen" +runs = "Wird nach jedem abgeschlossenen Upload ausgeführt." + +[settings.automations.trigger.video_imported] +label = "Bei importiertem Video" +noun = "Import" +phrase = "Video importiert" +runs = "Wird bei jedem importierten Video ausgeführt." + +[settings.automations.trigger.recording_deleted] +label = "Bei gelöschter Aufnahme" +noun = "Löschen" +phrase = "Aufnahme gelöscht" +runs = "Wird bei jeder gelöschten Aufnahme ausgeführt." + +[settings.automations.action.copy_to_clipboard] +label = "In die Zwischenablage kopieren" +noun = "Zwischenablage" +short = "In die Zwischenablage kopieren" + +[settings.automations.action.save_to_location] +label = "An Speicherort sichern" +noun = "Ordner" +short = "In Ordner speichern" + +[settings.automations.action.export] +label = "Mit Profil exportieren" +noun = "Export" +short = "Exportieren" + +[settings.automations.action.upload] +label = "Hochladen + Link kopieren" +noun = "Upload" +short = "Hochladen & Link kopieren" + +[settings.automations.action.reveal] +label = "Im Dateimanager anzeigen" +noun = "Anzeigen" +short = "Im Dateimanager anzeigen" + +[settings.automations.action.open_file] +label = "Datei öffnen" +noun = "Öffnen" +short = "Datei öffnen" + +[settings.automations.action.ocr] +label = "Text erkennen (OCR) in die Zwischenablage" +noun = "Text" +short = "Text kopieren (OCR)" + +[settings.automations.action.notify] +label = "Mitteilung anzeigen" +noun = "Mitteilung" +short = "Benachrichtigen" + +[settings.automations.action.open_editor] +label = "Editor öffnen" +noun = "Editor" +short = "Editor öffnen" + +[settings.automations.action.skip_editor] +label = "Editor überspringen (ohne Oberfläche)" +noun = "Ohne Editor" +short = "Editor überspringen" + +[settings.automations.action.apply_preset] +label = "Editor-Vorgabe anwenden" +noun = "Vorgabe" +short = "Vorgabe anwenden" + +[settings.automations.action.run_command] +label = "Befehl ausführen" +noun = "Befehl" +short = "Befehl ausführen" + +[settings.automations.action.webhook] +label = "Webhook senden" +noun = "Webhook" +short = "Webhook senden" + +[settings.automations.action.delete_local_files] +label = "Lokale Dateien löschen" +noun = "Löschen" +short = "Lokale Dateien löschen" + +[settings.automations.condition] +capture_target = "Aufnahmeziel ist" +duration_at_least = "Dauer mindestens (Sekunden)" +duration_at_most = "Dauer höchstens (Sekunden)" +organization = "Organisation ist" +recording_mode = "Aufnahmemodus ist" +window_title = "Fenstertitel enthält" + +[settings.automations.template.copy_screenshot] +description = "Bildschirmfoto machen, und es ist sofort zum Einfügen bereit." +name = "Neue Bildschirmfotos automatisch kopieren" + +[settings.automations.template.ocr_screenshot] +description = "Cap liest den Text in Ihrem Bildschirmfoto und kopiert ihn für Sie." +name = "Text aus Bildschirmfotos holen" + +[settings.automations.template.save_screenshot] +description = "Jedes neue Bildschirmfoto direkt in einen Ordner Ihrer Wahl." +name = "Bildschirmfotos in einem Ordner ablegen" + +[settings.automations.template.reveal_screenshot] +description = "Jedes Bildschirmfoto sofort nach der Aufnahme im Finder anzeigen." +name = "Zu jedem neuen Bildschirmfoto springen" + +[settings.automations.template.export_studio] +description = "Ein MP4 rendern, sobald eine Studio-Aufnahme endet." +name = "Nach der Aufnahme automatisch exportieren" + +[settings.automations.template.upload_share] +description = "Aufnahme beenden, und der Link liegt schon in der Zwischenablage." +name = "Hochladen und Link zum Teilen holen" + +[settings.automations.template.notify_upload] +body = "Ihre Aufnahme kann jetzt geteilt werden." +description = "Eine dezente Desktop-Mitteilung, sobald Ihre Aufnahme geteilt werden kann." +name = "Benachrichtigen, wenn ein Upload fertig ist" + +[settings.automations.template.webhook_share] +description = "Den Link zum Teilen an Slack, Discord oder Ihren eigenen Webhook senden." +name = "Slack beim Teilen benachrichtigen" + [main] delete_recording_confirm = "Möchten Sie diese Aufnahme wirklich löschen?" diff --git a/apps/desktop-gpui/locales/en.toml b/apps/desktop-gpui/locales/en.toml index c4415d07108..cb3cbbcffdf 100644 --- a/apps/desktop-gpui/locales/en.toml +++ b/apps/desktop-gpui/locales/en.toml @@ -241,6 +241,697 @@ title = "Privacy" [settings.general.privacy.telemetry] description = "Cap uses anonymous telemetry to improve reliability and fix bugs. We never collect recording contents, window titles, file paths, or personal information." label = "Share anonymous telemetry" +[settings.prompt] +ok = { text = "Ok", context = "The only button of an informational dialog." } + +[settings.sidebar] +account = "Account" +cancel_sign_in = { text = "Cancel Sign In", max_px = 180, size = 13 } +check_for_updates = "Check for updates" +click_to_sign_in = "Click to sign in" +downloading = { text = "Downloading v{version}…", context = "{version} is a version number such as 0.6.2." } +downloading_progress = { text = "Downloading v{version}… {percent}%", context = "{version} is a version number such as 0.6.2; {percent} is a whole number from 0 to 100." } +installing = { text = "Installing v{version}…", context = "{version} is a version number such as 0.6.2." } +previous_versions = "View previous versions" +restart_to_update = { text = "Restart to update to v{version}", context = "{version} is a version number such as 0.6.2." } +sign_in = { text = "Sign In", max_px = 180, size = 13 } +sign_out = { text = "Sign Out", max_px = 180, size = 13 } +signed_in = "Signed in" + +[settings.recordings] +corrupted_body = { text = "The recording failed so this file may have issues in the editor! If your having issues recovering the file please reach out to support!", context = "Shown before opening a failed recording in the editor. The English has a typo (\"your\" for \"you're\"); translate the meaning." } +corrupted_title = "Recording is potentially corrupted" +delete = "Delete" +description = "Manage your recordings and perform actions." +edit = "Edit" +empty_library = "No recordings found" +failed = "Recording failed" +import = "Import" +in_progress = "Recording in progress" +load_more = "Load more" +open_bundle = "Open recording bundle" +open_link = "Open link" +retry_upload = "Retry upload" +show_all = "Show all" +title = "Recordings" + +[settings.recordings.empty] +all = "No recordings" +all_matching = "No matching recordings" +instant = "No instant recordings" +instant_matching = "No matching instant recordings" +studio = "No studio recordings" +studio_matching = "No matching studio recordings" + +[settings.screenshots] +copy = "Copy to clipboard" +delete = "Delete" +description = "Manage your screenshots and perform actions." +empty = "No screenshots" +empty_library = "No screenshots found" +empty_matching = "No matching screenshots" +import = "Import image" +load_more = "Load more" +open_folder = "Open folder" +save_as = "Save as" +title = "Screenshots" + +[settings.quality] +save_failed = "Couldn't save your recording settings. Please try again." + +[settings.quality.studio] +balanced = "Balanced" +balanced_description = "Sharp detail at a sensible file size. Right for most recordings." +compatibility = "Smaller files" +compatibility_description = "Saves disk space. Fine detail can soften, most of all on camera." +description = "Saved to your computer for editing. You choose the export size later, in the editor." +recommended = "Recommended" +title = "Studio recordings" +ultra = "Maximum detail" +ultra_description = "Keeps the most detail for heavy editing. Uses the most disk space." + +[settings.quality.instant] +description = "Uploaded while you record, so the link is ready the moment you stop. For the best quality, use Studio mode: it records at the highest quality your screen allows, and you can export up to 4K." +free_plan = "720p on the free plan. Cap Pro records up to 4K." +max_resolution = "Maximum resolution" +res_1080p = "Sharp text at a practical upload size." +res_1440p = "Extra detail for large, high-resolution screens." +res_4k = "The most detail. Best on a fast connection." +res_720p = "Smallest uploads, so links are ready fastest." +title = "Instant recordings" + +[settings.quality.sharing] +auto_open = "Open share links automatically" +auto_open_description = "Open the link in your browser when an upload finishes." +title = "Sharing" + +[settings.options.studio_quality] +balanced = "Balanced" +compatibility = "Compatibility" +ultra = "Ultra" + +[settings.options.main_window_start] +close = { text = "Close", max_px = 180, size = 12 } +minimise = { text = "Minimise", max_px = 180, size = 12 } + +[settings.options.post_studio] +do_nothing = "Do nothing" +open_editor = "Open editor" +show_cap = "Show Cap" + +[settings.options.post_screenshot] +copy = "Copy to clipboard" +copy_and_open_editor = "Copy and open editor" +do_nothing = "Do nothing" +open_editor = "Open editor" + +[settings.options.post_deletion] +do_nothing = { text = "Do nothing", max_px = 180, size = 12 } +reopen = { text = "Reopen recording window", max_px = 180, size = 12 } + +[settings.options.preview_quality] +full = "Full" +half = "Half" +quarter = "Quarter" + +[settings.shortcuts] +blocked_while_recording = "Shortcuts can't be changed while this recording holds its stop key. Stop recording first." +needs_modifier_macos = "Include ⌘, ⌃ or ⌥ so the shortcut doesn't get in the way of typing." +needs_modifier_other = "Include Ctrl, Alt or Win so the shortcut doesn't get in the way of typing." +record_shortcut = "Record shortcut" +recording_description = "Start a recording without opening Cap first." +recording_hint = "Press the keys you want to use. Esc cancels, Delete removes it." +recording_title = "Recording" +saved = { text = "Saved. Press {keys} in any app to {effect}.", context = "{keys} is the shortcut, such as ⌘⇧4. {effect} is one of the settings.shortcuts.effect messages and completes the sentence." } +saved_moved = { text = "Saved. Press {keys} in any app to {effect}. Removed from {shortcut}.", context = "Like settings.shortcuts.saved; {shortcut} is the label of the shortcut that used these keys before, such as Record an area." } +screenshots_description = "Shortcuts work from any app. Click one to change it." +screenshots_title = "Screenshots" +type_shortcut = "Type shortcut…" +unavailable = "Another app or the system already uses this shortcut. Click to choose a different one." +unsupported_key = "That key can't be used in a shortcut. Try a letter, number or F-key." +while_recording_title = "While recording" + +[settings.shortcuts.after_screenshot] +hint = "For every screenshot, from a shortcut or from Cap" +title = "After a screenshot" + +[settings.shortcuts.after_studio] +hint = "When you stop" +title = "After a Studio recording" + +[settings.shortcuts.after_instant] +hint = "The share link is always copied" +title = "After an Instant recording" + +[settings.shortcuts.outcome] +instant_copy_link = "Copy link only" +instant_copy_link_hint = "Nothing opens" +instant_open_link = "Open link" +instant_open_link_hint = "In your browser" +saved_to_library = "Saved to library" +screenshot_copy = "Copy" +screenshot_copy_and_edit = "Copy and edit" +screenshot_copy_and_edit_hint = "Both at once" +screenshot_copy_hint = "Paste anywhere" +screenshot_do_nothing = "Do nothing" +screenshot_open_editor = "Open editor" +screenshot_open_editor_hint = "Annotate first" +studio_do_nothing = "Do nothing" +studio_open_editor = "Open editor" +studio_open_editor_hint = "Edit right away" +studio_show_cap = "Show Cap" +studio_show_cap_hint = "Back to the recorder" + +[settings.shortcuts.label] +cycle_mode = "Cycle recording mode" +fallback = "Shortcut" +open_picker = "Open recording picker" +pause = "Pause or resume" +record_area = "Record an area" +record_display = "Record a display" +record_window = "Record a window" +restart = "Restart recording" +screenshot_area = "Screenshot an area" +screenshot_display = "Screenshot the display" +screenshot_window = "Screenshot a window" +start_instant = "Start Instant recording" +start_studio = "Start Studio recording" +stop = "Stop recording" + +[settings.shortcuts.effect] +cycle_mode = "switch between Studio, Instant and Screenshot mode" +instant_copy = "record your selected screen or window in Instant mode, then copy its share link when you stop" +instant_copy_and_open = "record your selected screen or window in Instant mode, then copy and open its share link when you stop" +open_picker = "open Cap so you can choose what to record" +pause = "pause the current recording, or resume it" +picker_area_instant = "select an area, then record it in Instant mode" +picker_area_screenshot = "select an area, then take a screenshot of it" +picker_area_studio = "select an area, then record it in Studio mode" +picker_display_instant = "choose a display, then record it in Instant mode" +picker_display_screenshot = "choose a display, then take a screenshot of it" +picker_display_studio = "choose a display, then record it in Studio mode" +picker_window_instant = "choose a window, then record it in Instant mode" +picker_window_screenshot = "choose a window, then take a screenshot of it" +picker_window_studio = "choose a window, then record it in Studio mode" +restart = "throw away the current take and start again" +screenshot_area_copy = "select an area and copy it to your clipboard" +screenshot_area_copy_and_open_editor = "select an area and copy it and open the editor" +screenshot_area_open_editor = { text = "select an area and open it in the editor", context = "Completes settings.shortcuts.saved after \"to\"; written the way that message needs it." } +screenshot_area_save = "select an area and save it to your library without opening anything" +screenshot_display_copy = "capture the display under your pointer and copy it to your clipboard" +screenshot_display_copy_and_open_editor = "capture the display under your pointer and copy it and open the editor" +screenshot_display_open_editor = "capture the display under your pointer and open it in the editor" +screenshot_display_save = "capture the display under your pointer and save it to your library without opening anything" +screenshot_window_copy = "capture the window under your pointer and copy it to your clipboard" +screenshot_window_copy_and_open_editor = "capture the window under your pointer and copy it and open the editor" +screenshot_window_open_editor = "capture the window under your pointer and open it in the editor" +screenshot_window_save = "capture the window under your pointer and save it to your library without opening anything" +stop = "stop the current recording" +studio = "record your selected screen or window in Studio mode" +studio_open_editor = "record your selected screen or window in Studio mode, then open the editor when you stop" + +[settings.shortcuts.sentence] +cycle_mode = "Switch between Studio, Instant and Screenshot mode." +instant_copy = "Record your selected screen or window in Instant mode, then copy its share link when you stop." +instant_copy_and_open = "Record your selected screen or window in Instant mode, then copy and open its share link when you stop." +open_picker = "Open Cap so you can choose what to record." +pause = "Pause the current recording, or resume it." +picker_area_instant = "Select an area, then record it in Instant mode." +picker_area_screenshot = "Select an area, then take a screenshot of it." +picker_area_studio = "Select an area, then record it in Studio mode." +picker_display_instant = "Choose a display, then record it in Instant mode." +picker_display_screenshot = "Choose a display, then take a screenshot of it." +picker_display_studio = "Choose a display, then record it in Studio mode." +picker_window_instant = "Choose a window, then record it in Instant mode." +picker_window_screenshot = "Choose a window, then take a screenshot of it." +picker_window_studio = "Choose a window, then record it in Studio mode." +restart = "Throw away the current take and start again." +screenshot_area_copy = "Select an area and copy it to your clipboard." +screenshot_area_copy_and_open_editor = "Select an area and copy it and open the editor." +screenshot_area_open_editor = "Select an area and open it in the editor." +screenshot_area_save = "Select an area and save it to your library without opening anything." +screenshot_display_copy = "Capture the display under your pointer and copy it to your clipboard." +screenshot_display_copy_and_open_editor = "Capture the display under your pointer and copy it and open the editor." +screenshot_display_open_editor = "Capture the display under your pointer and open it in the editor." +screenshot_display_save = "Capture the display under your pointer and save it to your library without opening anything." +screenshot_window_copy = "Capture the window under your pointer and copy it to your clipboard." +screenshot_window_copy_and_open_editor = "Capture the window under your pointer and copy it and open the editor." +screenshot_window_open_editor = "Capture the window under your pointer and open it in the editor." +screenshot_window_save = "Capture the window under your pointer and save it to your library without opening anything." +stop = "Stop the current recording." +studio = "Record your selected screen or window in Studio mode." +studio_open_editor = "Record your selected screen or window in Studio mode, then open the editor when you stop." + +[settings.cli] +command = "Command" +copy = "Copy" +description = "Install the Cap command for terminals, agents, scripts, and local automation." +install = "Install CLI" +installed = "Installed" +installing = "Installing..." +installs_command = { text = "The desktop app installs a local\u00A0{command}\u00A0command that points back to the bundled CLI.", context = "{command} is the command name cap, shown in a code font. Keep the non-breaking spaces around it." } +load_failed = { text = "Couldn't load CLI status: {error}", context = "{error} is a technical error message, usually in English." } +not_installed = "Not installed" +path_configured = "Added cap to your PATH. Restart your terminal to use it, or run this now:" +path_missing = { text = "Add {path} to your PATH to use cap from a new terminal.", context = "{path} is a folder such as ~/.local/bin." } +remove = "Remove" +removing = "Removing..." +repair = "Repair" +repairing = "Repairing..." +retry = "Retry" +target = "Target" +title = "Command Line" + +[settings.transcription] +active_title = "Active hints" +applied_note = "These hints are applied when you generate captions in the editor." +clear = "Clear" +count = { one = "{count} item", other = "{count} items" } +description = "Add names, spellings, domains, and capitalization preferences that caption generation should keep in mind." +placeholder = "Add a term" +saved = "Saved" +saving = "Saving..." +terms_description = "Add one term at a time to reduce typos and formatting mistakes." +terms_title = "Remembered terms" +title = "Transcription" + +[settings.experimental] +developer = "Developer" +developer_description = "Only shown in development builds." +muxer = "Out-of-process muxer" +muxer_description = "Run the fragmented-MP4 muxer in an isolated subprocess so muxer crashes can't take down your recording. Requires the bundled cap-muxer binary." +native_app = "Use native app" +native_app_description = "Turn off to close this app and reopen the classic Tauri app in the dev harness. Recordings and settings are shared." +reliability = "Reliability" + +[settings.experimental.switch] +busy = "Finish your recording, export, upload, import, or transcription task before switching to the classic app." +check_failed = { text = "Couldn't check whether the classic Cap app opened: {error}", context = "{error} is a technical error message, usually in English." } +data_stays = "Your recordings and settings stay exactly where they are." +failed = "Couldn't open the classic Cap app." +not_found = "Couldn't find the classic Cap app to switch back to." +open_failed = { text = "Couldn't open the classic Cap app: {error}", context = "{error} is a technical error message, usually in English." } +opening = "Opening the classic Cap app" +opening_detail = "This window will close when the classic Cap app is ready." +save_failed = "Couldn't save your app preference. Cap is still open." +switching = "Switching back to the classic Cap app." +timed_out = "The classic app hasn't opened. Check the dev terminal for build errors, then try again." + +[settings.feedback] +app_version = "App Version" +capture_support = "Capture Support" +capture_supported = "Screen Capture: Supported" +capture_unsupported = "Screen Capture: Not Supported" +community_description = "Have questions, want to share ideas, or just hang out? Join the Cap Discord community." +community_title = "Join the Community" +debug_description = "Upload your logs to help us diagnose issues with Cap. No personal information is included." +debug_title = "Debug Information" +description = "Help us improve Cap by submitting feedback or reporting bugs. We'll get right on it." +join_discord = "Join Discord" +operating_system = "Operating System" +placeholder = "Tell us what you think about Cap..." +submit = "Submit Feedback" +submit_failed = "Failed to submit feedback" +submitting = "Submitting..." +system_loading = "Loading system information..." +system_title = "System Information" +thanks = "Thank you for your feedback!" +title = "Feedback" +upload_interrupted = "The upload was interrupted" +upload_logs = "Upload Logs" +upload_sent = "Sent. Thank you -- this helps us find the problem." +uploading = "Uploading..." + +[settings.feedback.diagnostic] +confirm_body = { text = "Cap will take over your screen with a flashing pattern and play loud beeps for about {seconds} seconds per pipeline. Take your headphones off, leave the volume audible, and leave the machine alone until it finishes.", context = "{seconds} is a whole number of seconds." } +confirm_title = "Run diagnostic?" +description = "Records a short flashing/beeping test pattern, measures how far the picture and sound drift apart, and snapshots your displays, cameras, microphones, disk space and recording settings. Nothing is sent anywhere until you choose to send it." +mic_description = "Also record your microphone and check that it hears the beeps in time. Needs speakers the mic can actually hear." +mic_label = "Test microphone" +mode_description = "Which pipeline to test. Both runs the studio leg and then the instant one." +mode_label = "Recording mode" +no_cli = "The Cap command-line tool was not found next to this build, so the report will cover your environment only." +no_cli_sync = "The Cap command-line tool was not found next to this build, so the sync test could not run." +run = "Run Diagnostic" +run_again = "Run Again" +send = "Send to Cap" +sending = "Sending..." +show_file = "Show File" +stop_recording_first = "Stop the current recording before running a diagnostic." +title = "Diagnostic Report" +try_again = "Try Again" +warning_body = "A fullscreen window flashes black and white while 1kHz beeps play through your speakers, and Cap records it and measures how far the picture and the sound have drifted apart. Leave the machine alone while it runs. Both modes together take a couple of minutes." +warning_title = "The test takes over your screen and makes noise." + +[settings.feedback.diagnostic.verdict] +environment_only = "ENVIRONMENT ONLY" +fail = "FAIL" +inconclusive = "INCONCLUSIVE" +pass = "PASS" +warn = "WARN" + +[settings.sign_in_required] +body = "Please sign in to continue. Alternatively, email hello@cap.so or join our Discord at cap.link/discord" +title = "Sign in required" + +[settings.changelog] +fetch_failed = "Failed to fetch changelog" +loading = "Loading changelog..." +new = "NEW" +version = { text = "Version {version} - {date}", context = "{version} is a version number such as 0.6.2; {date} is a date such as 8/17/2026." } + +[settings.license] +activate = "Activate License" +activating = "Activating..." +active_title = "Desktop license" +checking = "Checking…" +deactivate = "Deactivate License" +description = "Your Cap plan and desktop license, in one place." +desktop_description = "Your desktop license covers commercial recording and editing. Cap Pro adds cloud sharing and collaboration features." +desktop_name = "Desktop License" +expires = "Expires" +explore_description = "Compare current pricing and everything included on our website." +explore_desktop = "Desktop License · Commercial use of the desktop recorder and editor." +explore_pro = "Cap Pro · A desktop license, plus cloud sharing, AI features and collaboration." +explore_title = "Explore plans" +free_description = "Record and edit locally for personal use. Choose a paid plan for commercial use or more cloud features." +have_key = "Have a license key?" +key = "License Key" +no_instance = "No instance ID found" +opens_pricing = "Opens cap.so/pricing in your browser." +placeholder = "License key" +plan_title = "Your plan" +pro_description = "Your account includes cloud sharing, Pro features and a desktop license for commercial use." +refresh = "Refresh plan" +refresh_failed = "Couldn't refresh your plan. Please try again." +save_failed = "Failed to save the license" +sign_in_hint = "Already have Cap Pro? Sign in with your account from the sidebar." +title = "Plan & license" +view_pricing = "View plans & pricing ↗" + +[settings.integrations] +configure = "Configure" +description = "Configure integrations to extend Cap's functionality and connect with third-party services." +google_drive_description = "Connect Google Drive for new shareable link uploads. Cap stores new videos in a private Cap folder in your Drive and continues serving them through Cap after normal access checks." +managed = "Managed by your organization" +managed_by = { text = "Managed by your organization: {organization}", context = "{organization} is the name of the user's organization." } +request_timed_out = "The request timed out" +s3_description = "Connect your own S3 bucket for complete control over your data storage. All new shareable link uploads will be automatically uploaded to your configured S3 bucket, ensuring you maintain complete ownership and control over your content. Perfect for organizations requiring data sovereignty and custom storage policies." +s3_name = "S3 Config" +title = "Integrations" +upgrade = "Upgrade to Pro" + +[settings.integrations.s3] +access_key = "Access Key ID" +bucket = "Bucket Name" +configuration = "Configuration" +delete_failed = "Failed to delete S3 config" +deleted = "S3 configuration deleted successfully" +endpoint = "Endpoint" +fetch_failed = "Failed to fetch S3 config" +guide = "Storage Config Guide" +guide_intro = { text = "It should take under 10 minutes to set up and connect your storage bucket to Cap. View the\u00A0{guide}\u00A0to get started.", context = "{guide} is the link text settings.integrations.s3.guide. Keep the non-breaking spaces around it." } +other_provider = "Other S3-Compatible" +provider = "Storage Provider" +region = "Region" +remove = "Remove Config" +removing = "Removing..." +save_failed = "Failed to save S3 config" +saved = "S3 configuration saved successfully" +saving = "Saving..." +secret_key = "Secret Access Key" +test = "Test Connection" +test_failed = "S3 connection test failed. Check your config and network connection." +test_ok = "S3 configuration test successful! Connection is working." +test_timeout = "Connection test timed out after 5 seconds. Please check your endpoint URL and network connection." +testing = "Testing..." + +[settings.integrations.gdrive] +active = "Active" +active_for_uploads = "Active for new uploads" +cached = { text = "Cached {time}", context = "{time} is a date and time such as Aug 17, 2026, 10:30 PM." } +connect = "Connect Google Drive" +connect_failed = "Failed to start Google Drive connection" +connected_inactive = "Connected but not active" +connection_description = "Google Drive stores new uploads in a private Cap folder in your Drive. Existing Cap-hosted and S3 videos keep using their current storage." +connection_title = "Connection" +disconnect = "Disconnect" +disconnect_failed = "Failed to disconnect Google Drive" +disconnected = "Google Drive disconnected" +drive_files = "Drive files" +finish_in_browser = "Finish connecting Google Drive in your browser, then return here and refresh." +not_connected = "Not connected" +opening = "Opening..." +refresh = "Refresh" +refreshing = "Refreshing..." +remaining = "Remaining" +set_active_failed = "Failed to update active storage provider" +storage = "Storage" +test = "Test" +test_failed = "Google Drive connection test failed" +testing = "Testing..." +trash = "Trash" +updated = { text = "Updated {time}", context = "{time} is a date and time such as Aug 17, 2026, 10:30 PM." } +use_drive = "Use Google Drive" +use_s3 = "Use S3" +used = { text = "{usage} used", context = "{usage} is a size such as 2.0 GB." } +used_of = { text = "{usage} of {limit} used", context = "{usage} and {limit} are sizes such as 2.0 GB." } +waiting = "Waiting..." +working = "Google Drive connection is working" +working_for = { text = "Google Drive connection is working for {email}", context = "{email} is the Google account's email address." } + +[settings.automations] +action_no_effect = "This action has no effect for the selected trigger." +add_action = "Add action" +add_condition = "Add condition" +auto_name_empty = { text = "{trigger} automation", context = "{trigger} is a settings.automations.trigger.*.noun." } +browse = "Browse" +check_compatibility = "Check compatibility" +compatibility_checked = "Compatibility checked: this automation doesn't run in Cap yet. Saved rules remain available to the Cap CLI. No actions were run." +condition_never_matches = "This condition never matches for the selected trigger." +copy_link = "Copy link to clipboard" +dangerous = "In the Cap CLI, this automation runs commands or sends network requests with your permissions. Only use values you trust." +delete = "Delete" +description = "Rules are shared with the Cap CLI. Cap saves these rules but does not run automations yet." +empty_description = "Pick a template below to get started in one click, or build your own from scratch." +empty_title = "No automations yet" +enabled = "Enabled" +match_all = "Match all" +match_any = "Match any" +name = "Name" +new = "New automation" +no_presets = "No presets yet — create one in the editor first." +only_if = { text = "ONLY RUN IF", context = "A small all-caps group label above the conditions." } +open_in_browser = "Open in browser" +retry_save = "Retry save" +run_through_shell = "Run through shell" +save_failed = "Failed to save automations. Your changes have not been saved." +start_from_scratch = "Start from scratch" +summary_empty = { text = "{trigger} → no actions yet", context = "{trigger} is a settings.automations.trigger.*.phrase." } +templates_description = "One click to add a ready-made automation. Tweak anything afterwards." +templates_title = "Templates" +then = { text = "THEN DO THIS", context = "A small all-caps group label above the actions." } +title = "Automations" +when = { text = "WHEN THIS HAPPENS", context = "A small all-caps group label above the trigger select." } + +[settings.automations.status] +disabled_saved = "Disabled · Saved" +disabled_unsaved = "Disabled · Not saved" +enabled_saved = "Enabled · Saved" +enabled_unsaved = "Enabled · Not saved" + +[settings.automations.field] +arguments = "Arguments (space-separated)" +body = "Body" +body_template = "Body template (optional)" +compression = "Compression" +destination = "Destination folder (optional, blank = project folder)" +filename = "Filename template (optional)" +folder = "Folder" +format = "Format" +frame_rate = "Frame rate" +method = "Method" +organization = "Organization ID (optional)" +preset = "Preset" +program = "Program" +resolution = "Resolution" +source = "Source" +title = "Title" +url = "URL" + +[settings.automations.placeholder] +organization_id = "Organization ID" +project_folder = "Project folder" +window_title = { text = "e.g. Slack", context = "Placeholder in the window-title condition field; Slack is an app name." } + +[settings.automations.clipboard] +original = "Original capture" +rendered = "Edited / rendered" + +[settings.automations.target] +area = "Area" +display = "Display" +window = "Window" + +[settings.automations.compression] +maximum = "Maximum" +potato = "Potato" +social = "Social" +web = "Web" + +[settings.automations.trigger.screenshot_taken] +label = "On screenshot taken" +noun = "Screenshot" +phrase = "Screenshot taken" +runs = "Runs for every screenshot taken." + +[settings.automations.trigger.studio_finished] +label = "On studio recording finished" +noun = "Studio recording" +phrase = "Studio recording ends" +runs = "Runs for every studio recording ends." + +[settings.automations.trigger.instant_finished] +label = "On instant recording finished" +noun = "Instant recording" +phrase = "Instant recording ends" +runs = "Runs for every instant recording ends." + +[settings.automations.trigger.recording_started] +label = "On recording started" +noun = "Recording start" +phrase = "Recording starts" +runs = "Runs for every recording starts." + +[settings.automations.trigger.upload_completed] +label = "On upload completed" +noun = "Upload" +phrase = "Upload completes" +runs = "Runs for every upload completes." + +[settings.automations.trigger.video_imported] +label = "On video imported" +noun = "Import" +phrase = "Video imported" +runs = "Runs for every video imported." + +[settings.automations.trigger.recording_deleted] +label = "On recording deleted" +noun = "Deletion" +phrase = "Recording deleted" +runs = "Runs for every recording deleted." + +[settings.automations.action.copy_to_clipboard] +label = "Copy to clipboard" +noun = "Clipboard" +short = "Copy to clipboard" + +[settings.automations.action.save_to_location] +label = "Save to location" +noun = "Folder" +short = "Save to folder" + +[settings.automations.action.export] +label = "Export with profile" +noun = "Export" +short = "Export" + +[settings.automations.action.upload] +label = "Upload + copy link" +noun = "Upload" +short = "Upload & copy link" + +[settings.automations.action.reveal] +label = "Reveal in file manager" +noun = "Reveal" +short = "Reveal in file manager" + +[settings.automations.action.open_file] +label = "Open file" +noun = "Open" +short = "Open file" + +[settings.automations.action.ocr] +label = "Recognize text (OCR) to clipboard" +noun = "Text" +short = "Copy text (OCR)" + +[settings.automations.action.notify] +label = "Show notification" +noun = "Notify" +short = "Notify" + +[settings.automations.action.open_editor] +label = "Open editor" +noun = "Editor" +short = "Open editor" + +[settings.automations.action.skip_editor] +label = "Skip editor (headless)" +noun = "Skip editor" +short = "Skip editor" + +[settings.automations.action.apply_preset] +label = "Apply editor preset" +noun = "Preset" +short = "Apply preset" + +[settings.automations.action.run_command] +label = "Run command" +noun = "Command" +short = "Run command" + +[settings.automations.action.webhook] +label = "Send webhook" +noun = "Webhook" +short = "Send webhook" + +[settings.automations.action.delete_local_files] +label = "Delete local files" +noun = "Delete" +short = "Delete local files" + +[settings.automations.condition] +capture_target = "Capture target is" +duration_at_least = "Duration at least (seconds)" +duration_at_most = "Duration at most (seconds)" +organization = "Organization is" +recording_mode = "Recording mode is" +window_title = "Window title contains" + +[settings.automations.template.copy_screenshot] +description = "Snap a screenshot and it's right there, ready to paste." +name = "Auto-copy new screenshots to clipboard" + +[settings.automations.template.ocr_screenshot] +description = "Cap reads the text in your screenshot and copies it for you." +name = "Pull the text out of screenshots" + +[settings.automations.template.save_screenshot] +description = "Send every new screenshot straight to a folder you pick." +name = "Tuck screenshots into a folder" + +[settings.automations.template.reveal_screenshot] +description = "Pop open every screenshot in Finder the moment you take it." +name = "Jump to each new screenshot" + +[settings.automations.template.export_studio] +description = "Render an MP4 the second a Studio recording wraps up." +name = "Auto-export when you finish recording" + +[settings.automations.template.upload_share] +description = "Finish a recording and the link is waiting on your clipboard." +name = "Upload and grab the share link" + +[settings.automations.template.notify_upload] +body = { text = "Your recording is ready to share.", context = "The default text of the notification this template creates; the user can edit it." } +description = "Get a gentle desktop nudge once your recording is shareable." +name = "Ping me when an upload is ready" + +[settings.automations.template.webhook_share] +description = "Send the share link to Slack, Discord, or your own webhook." +name = "Tell Slack when you share something" + [main] delete_recording_confirm = "Are you sure you want to delete this recording?" diff --git a/apps/desktop-gpui/locales/es.toml b/apps/desktop-gpui/locales/es.toml index 00c4c9b74f7..7c7b4a0f363 100644 --- a/apps/desktop-gpui/locales/es.toml +++ b/apps/desktop-gpui/locales/es.toml @@ -239,6 +239,697 @@ title = "Privacidad" [settings.general.privacy.telemetry] description = "Cap usa telemetría anónima para mejorar la fiabilidad y corregir errores. Nunca recopilamos el contenido de las grabaciones, los títulos de las ventanas, las rutas de los archivos ni información personal." label = "Compartir telemetría anónima" +[settings.prompt] +ok = "Aceptar" + +[settings.sidebar] +account = "Cuenta" +cancel_sign_in = "Cancelar acceso" +check_for_updates = "Buscar actualizaciones" +click_to_sign_in = "Haz clic para iniciar sesión" +downloading = "Descargando v{version}…" +downloading_progress = "Descargando v{version}… {percent} %" +installing = "Instalando v{version}…" +previous_versions = "Ver versiones anteriores" +restart_to_update = "Reinicia para actualizar a v{version}" +sign_in = "Iniciar sesión" +sign_out = "Cerrar sesión" +signed_in = "Sesión iniciada" + +[settings.recordings] +corrupted_body = "La grabación falló, así que este archivo podría dar problemas en el editor. Si tienes problemas para recuperar el archivo, contacta con soporte." +corrupted_title = "Es posible que la grabación esté dañada" +delete = "Eliminar" +description = "Gestiona tus grabaciones y realiza acciones." +edit = "Editar" +empty_library = "No se encontraron grabaciones" +failed = "Error en la grabación" +import = "Importar" +in_progress = "Grabación en curso" +load_more = "Cargar más" +open_bundle = "Abrir el paquete de la grabación" +open_link = "Abrir enlace" +retry_upload = "Reintentar la subida" +show_all = "Mostrar todo" +title = "Grabaciones" + +[settings.recordings.empty] +all = "No hay grabaciones" +all_matching = "No hay grabaciones que coincidan" +instant = "No hay grabaciones Instant" +instant_matching = "No hay grabaciones Instant que coincidan" +studio = "No hay grabaciones Studio" +studio_matching = "No hay grabaciones Studio que coincidan" + +[settings.screenshots] +copy = "Copiar al portapapeles" +delete = "Eliminar" +description = "Gestiona tus capturas de pantalla y realiza acciones." +empty = "No hay capturas de pantalla" +empty_library = "No se encontraron capturas de pantalla" +empty_matching = "No hay capturas de pantalla que coincidan" +import = "Importar imagen" +load_more = "Cargar más" +open_folder = "Abrir carpeta" +save_as = "Guardar como" +title = "Capturas de pantalla" + +[settings.quality] +save_failed = "No se pudieron guardar tus ajustes de grabación. Inténtalo de nuevo." + +[settings.quality.studio] +balanced = "Equilibrado" +balanced_description = "Detalle nítido con un tamaño de archivo razonable. Ideal para la mayoría de grabaciones." +compatibility = "Archivos más pequeños" +compatibility_description = "Ahorra espacio en disco. Los detalles finos pueden suavizarse, sobre todo en la cámara." +description = "Se guardan en tu ordenador para editarlas. El tamaño de exportación lo eliges después, en el editor." +recommended = "Recomendado" +title = "Grabaciones Studio" +ultra = "Máximo detalle" +ultra_description = "Conserva el máximo detalle para ediciones intensas. Usa más espacio en disco." + +[settings.quality.instant] +description = "Se suben mientras grabas, así que el enlace está listo en cuanto paras. Para la mejor calidad, usa el modo Studio: graba con la máxima calidad que permite tu pantalla y puedes exportar hasta 4K." +free_plan = "720p en el plan gratuito. Cap Pro graba hasta 4K." +max_resolution = "Resolución máxima" +res_1080p = "Texto nítido con un tamaño de subida práctico." +res_1440p = "Más detalle para pantallas grandes de alta resolución." +res_4k = "El máximo detalle. Mejor con una conexión rápida." +res_720p = "Subidas más pequeñas, así los enlaces están listos antes." +title = "Grabaciones Instant" + +[settings.quality.sharing] +auto_open = "Abrir automáticamente los enlaces para compartir" +auto_open_description = "Abre el enlace en tu navegador cuando termine una subida." +title = "Compartir" + +[settings.options.studio_quality] +balanced = "Equilibrado" +compatibility = "Compatibilidad" +ultra = "Ultra" + +[settings.options.main_window_start] +close = "Cerrar" +minimise = "Minimizar" + +[settings.options.post_studio] +do_nothing = "No hacer nada" +open_editor = "Abrir el editor" +show_cap = "Mostrar Cap" + +[settings.options.post_screenshot] +copy = "Copiar al portapapeles" +copy_and_open_editor = "Copiar y abrir el editor" +do_nothing = "No hacer nada" +open_editor = "Abrir el editor" + +[settings.options.post_deletion] +do_nothing = "No hacer nada" +reopen = "Reabrir la grabadora" + +[settings.options.preview_quality] +full = "Completa" +half = "Mitad" +quarter = "Un cuarto" + +[settings.shortcuts] +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" +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" +saved = "Guardado. Pulsa {keys} en cualquier app para {effect}." +saved_moved = "Guardado. Pulsa {keys} en cualquier app para {effect}. Se quitó de «{shortcut}»." +screenshots_description = "Los atajos funcionan desde cualquier app. Haz clic en uno para cambiarlo." +screenshots_title = "Capturas de pantalla" +type_shortcut = "Escribe el atajo…" +unavailable = "Otra app o el sistema ya usa este atajo. Haz clic para elegir otro." +unsupported_key = "Esa tecla no se puede usar en un atajo. Prueba con una letra, un número o una tecla F." +while_recording_title = "Durante la grabación" + +[settings.shortcuts.after_screenshot] +hint = "Para cada captura, desde un atajo o desde Cap" +title = "Después de una captura" + +[settings.shortcuts.after_studio] +hint = "Al detenerla" +title = "Después de una grabación Studio" + +[settings.shortcuts.after_instant] +hint = "El enlace para compartir siempre se copia" +title = "Después de una grabación Instant" + +[settings.shortcuts.outcome] +instant_copy_link = "Solo copiar el enlace" +instant_copy_link_hint = "No se abre nada" +instant_open_link = "Abrir el enlace" +instant_open_link_hint = "En tu navegador" +saved_to_library = "Se guarda en la biblioteca" +screenshot_copy = "Copiar" +screenshot_copy_and_edit = "Copiar y editar" +screenshot_copy_and_edit_hint = "Las dos cosas a la vez" +screenshot_copy_hint = "Pega donde quieras" +screenshot_do_nothing = "No hacer nada" +screenshot_open_editor = "Abrir el editor" +screenshot_open_editor_hint = "Anotar primero" +studio_do_nothing = "No hacer nada" +studio_open_editor = "Abrir el editor" +studio_open_editor_hint = "Editar enseguida" +studio_show_cap = "Mostrar Cap" +studio_show_cap_hint = "Volver a la grabadora" + +[settings.shortcuts.label] +cycle_mode = "Cambiar el modo de grabación" +fallback = "Atajo" +open_picker = "Abrir el selector de grabación" +pause = "Pausar o reanudar" +record_area = "Grabar un área" +record_display = "Grabar una pantalla" +record_window = "Grabar una ventana" +restart = "Reiniciar la grabación" +screenshot_area = "Capturar un área" +screenshot_display = "Capturar la pantalla" +screenshot_window = "Capturar una ventana" +start_instant = "Iniciar grabación Instant" +start_studio = "Iniciar grabación Studio" +stop = "Detener la grabación" + +[settings.shortcuts.effect] +cycle_mode = "cambiar entre los modos Studio, Instant y Captura de pantalla" +instant_copy = "grabar la pantalla o ventana seleccionada en modo Instant y copiar el enlace para compartir al detenerla" +instant_copy_and_open = "grabar la pantalla o ventana seleccionada en modo Instant y copiar y abrir el enlace para compartir al detenerla" +open_picker = "abrir Cap para elegir qué grabar" +pause = "pausar la grabación actual o reanudarla" +picker_area_instant = "seleccionar un área y luego grabarla en modo Instant" +picker_area_screenshot = "seleccionar un área y luego hacerle una captura de pantalla" +picker_area_studio = "seleccionar un área y luego grabarla en modo Studio" +picker_display_instant = "elegir una pantalla y luego grabarla en modo Instant" +picker_display_screenshot = "elegir una pantalla y luego hacerle una captura de pantalla" +picker_display_studio = "elegir una pantalla y luego grabarla en modo Studio" +picker_window_instant = "elegir una ventana y luego grabarla en modo Instant" +picker_window_screenshot = "elegir una ventana y luego hacerle una captura de pantalla" +picker_window_studio = "elegir una ventana y luego grabarla en modo Studio" +restart = "descartar la toma actual y empezar de nuevo" +screenshot_area_copy = "seleccionar un área y copiar la captura al portapapeles" +screenshot_area_copy_and_open_editor = "seleccionar un área, copiar la captura y abrir el editor" +screenshot_area_open_editor = "seleccionar un área y abrir la captura en el editor" +screenshot_area_save = "seleccionar un área y guardar la captura en tu biblioteca sin abrir nada" +screenshot_display_copy = "capturar la pantalla bajo tu puntero y copiar la captura al portapapeles" +screenshot_display_copy_and_open_editor = "capturar la pantalla bajo tu puntero, copiar la captura y abrir el editor" +screenshot_display_open_editor = "capturar la pantalla bajo tu puntero y abrir la captura en el editor" +screenshot_display_save = "capturar la pantalla bajo tu puntero y guardar la captura en tu biblioteca sin abrir nada" +screenshot_window_copy = "capturar la ventana bajo tu puntero y copiar la captura al portapapeles" +screenshot_window_copy_and_open_editor = "capturar la ventana bajo tu puntero, copiar la captura y abrir el editor" +screenshot_window_open_editor = "capturar la ventana bajo tu puntero y abrir la captura en el editor" +screenshot_window_save = "capturar la ventana bajo tu puntero y guardar la captura en tu biblioteca sin abrir nada" +stop = "detener la grabación actual" +studio = "grabar la pantalla o ventana seleccionada en modo Studio" +studio_open_editor = "grabar la pantalla o ventana seleccionada en modo Studio y abrir el editor al detenerla" + +[settings.shortcuts.sentence] +cycle_mode = "Cambiar entre los modos Studio, Instant y Captura de pantalla." +instant_copy = "Grabar la pantalla o ventana seleccionada en modo Instant y copiar el enlace para compartir al detenerla." +instant_copy_and_open = "Grabar la pantalla o ventana seleccionada en modo Instant y copiar y abrir el enlace para compartir al detenerla." +open_picker = "Abrir Cap para elegir qué grabar." +pause = "Pausar la grabación actual o reanudarla." +picker_area_instant = "Seleccionar un área y luego grabarla en modo Instant." +picker_area_screenshot = "Seleccionar un área y luego hacerle una captura de pantalla." +picker_area_studio = "Seleccionar un área y luego grabarla en modo Studio." +picker_display_instant = "Elegir una pantalla y luego grabarla en modo Instant." +picker_display_screenshot = "Elegir una pantalla y luego hacerle una captura de pantalla." +picker_display_studio = "Elegir una pantalla y luego grabarla en modo Studio." +picker_window_instant = "Elegir una ventana y luego grabarla en modo Instant." +picker_window_screenshot = "Elegir una ventana y luego hacerle una captura de pantalla." +picker_window_studio = "Elegir una ventana y luego grabarla en modo Studio." +restart = "Descartar la toma actual y empezar de nuevo." +screenshot_area_copy = "Seleccionar un área y copiar la captura al portapapeles." +screenshot_area_copy_and_open_editor = "Seleccionar un área, copiar la captura y abrir el editor." +screenshot_area_open_editor = "Seleccionar un área y abrir la captura en el editor." +screenshot_area_save = "Seleccionar un área y guardar la captura en tu biblioteca sin abrir nada." +screenshot_display_copy = "Capturar la pantalla bajo tu puntero y copiar la captura al portapapeles." +screenshot_display_copy_and_open_editor = "Capturar la pantalla bajo tu puntero, copiar la captura y abrir el editor." +screenshot_display_open_editor = "Capturar la pantalla bajo tu puntero y abrir la captura en el editor." +screenshot_display_save = "Capturar la pantalla bajo tu puntero y guardar la captura en tu biblioteca sin abrir nada." +screenshot_window_copy = "Capturar la ventana bajo tu puntero y copiar la captura al portapapeles." +screenshot_window_copy_and_open_editor = "Capturar la ventana bajo tu puntero, copiar la captura y abrir el editor." +screenshot_window_open_editor = "Capturar la ventana bajo tu puntero y abrir la captura en el editor." +screenshot_window_save = "Capturar la ventana bajo tu puntero y guardar la captura en tu biblioteca sin abrir nada." +stop = "Detener la grabación actual." +studio = "Grabar la pantalla o ventana seleccionada en modo Studio." +studio_open_editor = "Grabar la pantalla o ventana seleccionada en modo Studio y abrir el editor al detenerla." + +[settings.cli] +command = "Comando" +copy = "Copiar" +description = "Instala el comando Cap para terminales, agentes, scripts y automatización local." +install = "Instalar la CLI" +installed = "Instalada" +installing = "Instalando..." +installs_command = "La app de escritorio instala un comando local\u00A0{command}\u00A0que apunta a la CLI incluida." +load_failed = "No se pudo cargar el estado de la CLI: {error}" +not_installed = "No instalada" +path_configured = "Se añadió cap a tu PATH. Reinicia la terminal para usarlo o ejecuta esto ahora:" +path_missing = "Añade {path} a tu PATH para usar cap desde una nueva terminal." +remove = "Quitar" +removing = "Quitando..." +repair = "Reparar" +repairing = "Reparando..." +retry = "Reintentar" +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." +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." +placeholder = "Añadir un término" +saved = "Guardado" +saving = "Guardando..." +terms_description = "Añade un término cada vez para reducir errores de escritura y de formato." +terms_title = "Términos guardados" +title = "Transcripción" + +[settings.experimental] +developer = "Desarrollador" +developer_description = "Solo se muestra en las compilaciones de desarrollo." +muxer = "Multiplexor fuera de proceso" +muxer_description = "Ejecuta el multiplexor de MP4 fragmentado en un subproceso aislado para que sus fallos no arruinen tu grabación. Requiere el binario cap-muxer incluido." +native_app = "Usar la app nativa" +native_app_description = "Desactívalo para cerrar esta app y volver a abrir la app Tauri clásica en el entorno de desarrollo. Las grabaciones y los ajustes se comparten." +reliability = "Fiabilidad" + +[settings.experimental.switch] +busy = "Termina la grabación, exportación, subida, importación o transcripción antes de cambiar a la app clásica." +check_failed = "No se pudo comprobar si la app clásica de Cap se abrió: {error}" +data_stays = "Tus grabaciones y ajustes se quedan exactamente donde están." +failed = "No se pudo abrir la app clásica de Cap." +not_found = "No se encontró la app clásica de Cap a la que volver." +open_failed = "No se pudo abrir la app clásica de Cap: {error}" +opening = "Abriendo la app clásica de Cap" +opening_detail = "Esta ventana se cerrará cuando la app clásica de Cap esté lista." +save_failed = "No se pudo guardar tu preferencia de app. Cap sigue abierto." +switching = "Volviendo a la app clásica de Cap." +timed_out = "La app clásica no se ha abierto. Revisa los errores de compilación en la terminal de desarrollo y vuelve a intentarlo." + +[settings.feedback] +app_version = "Versión de la app" +capture_support = "Compatibilidad de captura" +capture_supported = "Captura de pantalla: compatible" +capture_unsupported = "Captura de pantalla: no compatible" +community_description = "¿Tienes preguntas, quieres compartir ideas o simplemente pasar el rato? Únete a la comunidad de Cap en Discord." +community_title = "Únete a la comunidad" +debug_description = "Sube tus registros para ayudarnos a diagnosticar problemas con Cap. No se incluye información personal." +debug_title = "Información de depuración" +description = "Ayúdanos a mejorar Cap enviando comentarios o informando de errores. Nos pondremos con ello enseguida." +join_discord = "Unirse a Discord" +operating_system = "Sistema operativo" +placeholder = "Cuéntanos qué te parece Cap..." +submit = "Enviar comentarios" +submit_failed = "No se pudieron enviar los comentarios" +submitting = "Enviando..." +system_loading = "Cargando información del sistema..." +system_title = "Información del sistema" +thanks = "¡Gracias por tus comentarios!" +title = "Comentarios" +upload_interrupted = "La subida se interrumpió" +upload_logs = "Subir registros" +upload_sent = "Enviado. Gracias, esto nos ayuda a encontrar el problema." +uploading = "Subiendo..." + +[settings.feedback.diagnostic] +confirm_body = "Cap ocupará tu pantalla con un patrón parpadeante y emitirá pitidos fuertes durante unos {seconds} segundos por cada proceso. Quítate los auriculares, deja el volumen audible y no toques el equipo hasta que termine." +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_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." +run = "Ejecutar diagnóstico" +run_again = "Volver a ejecutar" +send = "Enviar a Cap" +sending = "Enviando..." +show_file = "Mostrar archivo" +stop_recording_first = "Detén la grabación actual antes de ejecutar un diagnóstico." +title = "Informe de diagnóstico" +try_again = "Intentar de nuevo" +warning_body = "Una ventana a pantalla completa parpadea en blanco y negro mientras suenan pitidos de 1 kHz por tus altavoces, y Cap lo graba y mide cuánto se han desfasado la imagen y el sonido. No toques el equipo mientras se ejecuta. Los dos modos juntos tardan un par de minutos." +warning_title = "La prueba ocupa tu pantalla y hace ruido." + +[settings.feedback.diagnostic.verdict] +environment_only = "SOLO ENTORNO" +fail = "FALLO" +inconclusive = "NO CONCLUYENTE" +pass = "CORRECTO" +warn = "AVISO" + +[settings.sign_in_required] +body = "Inicia sesión para continuar. También puedes escribir a hello@cap.so o unirte a nuestro Discord en cap.link/discord" +title = "Es necesario iniciar sesión" + +[settings.changelog] +fetch_failed = "No se pudo obtener el registro de cambios" +loading = "Cargando el registro de cambios..." +new = "NUEVO" +version = "Versión {version} - {date}" + +[settings.license] +activate = "Activar licencia" +activating = "Activando..." +active_title = "Licencia de escritorio" +checking = "Comprobando…" +deactivate = "Desactivar licencia" +description = "Tu plan de Cap y tu licencia de escritorio, en un solo lugar." +desktop_description = "Tu licencia de escritorio cubre la grabación y edición comercial. Cap Pro añade compartir en la nube y funciones de colaboración." +desktop_name = "Licencia de escritorio" +expires = "Caduca" +explore_description = "Compara los precios actuales y todo lo que incluye en nuestra web." +explore_desktop = "Licencia de escritorio · Uso comercial de la grabadora y el editor de escritorio." +explore_pro = "Cap Pro · Una licencia de escritorio, más compartir en la nube, funciones de IA y colaboración." +explore_title = "Explorar planes" +free_description = "Graba y edita en local para uso personal. Elige un plan de pago para uso comercial o más funciones en la nube." +have_key = "¿Tienes una clave de licencia?" +key = "Clave de licencia" +no_instance = "No se encontró el ID de instancia" +opens_pricing = "Abre cap.so/pricing en tu navegador." +placeholder = "Clave de licencia" +plan_title = "Tu plan" +pro_description = "Tu cuenta incluye compartir en la nube, funciones Pro y una licencia de escritorio para uso comercial." +refresh = "Actualizar el plan" +refresh_failed = "No se pudo actualizar tu plan. Inténtalo de nuevo." +save_failed = "No se pudo guardar la licencia" +sign_in_hint = "¿Ya tienes Cap Pro? Inicia sesión con tu cuenta desde la barra lateral." +title = "Plan y licencia" +view_pricing = "Ver planes y precios ↗" + +[settings.integrations] +configure = "Configurar" +description = "Configura integraciones para ampliar las funciones de Cap y conectarlo con servicios de terceros." +google_drive_description = "Conecta Google Drive para las nuevas subidas con enlace para compartir. Cap guarda los vídeos nuevos en una carpeta privada de Cap en tu Drive y los sigue sirviendo a través de Cap tras las comprobaciones de acceso habituales." +managed = "Gestionado por tu organización" +managed_by = "Gestionado por tu organización: {organization}" +request_timed_out = "La solicitud agotó el tiempo de espera" +s3_description = "Conecta tu propio bucket de S3 para tener control total sobre el almacenamiento de tus datos. Todas las nuevas subidas con enlace para compartir se subirán automáticamente a tu bucket de S3 configurado, para que conserves la propiedad y el control total de tu contenido. Ideal para organizaciones que necesitan soberanía de datos y políticas de almacenamiento propias." +s3_name = "Configuración de S3" +title = "Integraciones" +upgrade = "Mejorar a Pro" + +[settings.integrations.s3] +access_key = "ID de clave de acceso" +bucket = "Nombre del bucket" +configuration = "Configuración" +delete_failed = "No se pudo eliminar la configuración de S3" +deleted = "La configuración de S3 se eliminó correctamente" +endpoint = "Endpoint" +fetch_failed = "No se pudo obtener la configuración de S3" +guide = "guía de configuración del almacenamiento" +guide_intro = "Configurar y conectar tu bucket de almacenamiento a Cap debería llevar menos de 10 minutos. Consulta la\u00A0{guide}\u00A0para empezar." +other_provider = "Otro compatible con S3" +provider = "Proveedor de almacenamiento" +region = "Región" +remove = "Quitar configuración" +removing = "Quitando..." +save_failed = "No se pudo guardar la configuración de S3" +saved = "La configuración de S3 se guardó correctamente" +saving = "Guardando..." +secret_key = "Clave de acceso secreta" +test = "Probar conexión" +test_failed = "La prueba de conexión de S3 falló. Revisa la configuración y la conexión de red." +test_ok = "¡La prueba de la configuración de S3 fue correcta! La conexión funciona." +test_timeout = "La prueba de conexión agotó el tiempo tras 5 segundos. Revisa la URL del endpoint y la conexión de red." +testing = "Probando..." + +[settings.integrations.gdrive] +active = "Activo" +active_for_uploads = "Activo para las nuevas subidas" +cached = "En caché desde {time}" +connect = "Conectar Google Drive" +connect_failed = "No se pudo iniciar la conexión con Google Drive" +connected_inactive = "Conectado pero no activo" +connection_description = "Google Drive guarda las nuevas subidas en una carpeta privada de Cap en tu Drive. Los vídeos existentes alojados en Cap y en S3 siguen usando su almacenamiento actual." +connection_title = "Conexión" +disconnect = "Desconectar" +disconnect_failed = "No se pudo desconectar Google Drive" +disconnected = "Google Drive desconectado" +drive_files = "Archivos de Drive" +finish_in_browser = "Termina de conectar Google Drive en tu navegador, luego vuelve aquí y actualiza." +not_connected = "No conectado" +opening = "Abriendo..." +refresh = "Actualizar" +refreshing = "Actualizando..." +remaining = "Disponible" +set_active_failed = "No se pudo actualizar el proveedor de almacenamiento activo" +storage = "Almacenamiento" +test = "Probar" +test_failed = "La prueba de conexión de Google Drive falló" +testing = "Probando..." +trash = "Papelera" +updated = "Actualizado el {time}" +use_drive = "Usar Google Drive" +use_s3 = "Usar S3" +used = "{usage} usados" +used_of = "{usage} usados de {limit}" +waiting = "Esperando..." +working = "La conexión de Google Drive funciona" +working_for = "La conexión de Google Drive funciona para {email}" + +[settings.automations] +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}" +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." +condition_never_matches = "Esta condición nunca se cumple con el disparador seleccionado." +copy_link = "Copiar el enlace al portapapeles" +dangerous = "En la CLI de Cap, esta automatización ejecuta comandos o envía solicitudes de red con tus permisos. Usa solo valores de confianza." +delete = "Eliminar" +description = "Las reglas se comparten con la CLI de Cap. Cap guarda estas reglas, pero todavía no ejecuta automatizaciones." +empty_description = "Elige una plantilla de abajo para empezar con un clic o crea la tuya desde cero." +empty_title = "Aún no hay automatizaciones" +enabled = "Activada" +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." +only_if = "SOLO SI" +open_in_browser = "Abrir en el navegador" +retry_save = "Reintentar guardar" +run_through_shell = "Ejecutar a través del shell" +save_failed = "No se pudieron guardar las automatizaciones. Tus cambios no se han guardado." +start_from_scratch = "Empezar desde cero" +summary_empty = "{trigger} → aún no hay acciones" +templates_description = "Un clic para añadir una automatización lista para usar. Ajusta lo que quieras después." +templates_title = "Plantillas" +then = "ENTONCES HACER ESTO" +title = "Automatizaciones" +when = "CUANDO OCURRA ESTO" + +[settings.automations.status] +disabled_saved = "Desactivada · Guardada" +disabled_unsaved = "Desactivada · Sin guardar" +enabled_saved = "Activada · Guardada" +enabled_unsaved = "Activada · Sin guardar" + +[settings.automations.field] +arguments = "Argumentos (separados por espacios)" +body = "Cuerpo" +body_template = "Plantilla del cuerpo (opcional)" +compression = "Compresión" +destination = "Carpeta de destino (opcional, vacío = carpeta del proyecto)" +filename = "Plantilla de nombre de archivo (opcional)" +folder = "Carpeta" +format = "Formato" +frame_rate = "Velocidad de fotogramas" +method = "Método" +organization = "ID de la organización (opcional)" +preset = "Ajuste preestablecido" +program = "Programa" +resolution = "Resolución" +source = "Origen" +title = "Título" +url = "URL" + +[settings.automations.placeholder] +organization_id = "ID de la organización" +project_folder = "Carpeta del proyecto" +window_title = "p. ej., Slack" + +[settings.automations.clipboard] +original = "Captura original" +rendered = "Editada / renderizada" + +[settings.automations.target] +area = "Área" +display = "Pantalla" +window = "Ventana" + +[settings.automations.compression] +maximum = "Máxima" +potato = "Patata" +social = "Redes sociales" +web = "Web" + +[settings.automations.trigger.screenshot_taken] +label = "Al hacer una 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" +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" +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" +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" +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" +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" +phrase = "Grabación eliminada" +runs = "Se ejecuta con cada grabación eliminada." + +[settings.automations.action.copy_to_clipboard] +label = "Copiar al portapapeles" +noun = "Portapapeles" +short = "Copiar al portapapeles" + +[settings.automations.action.save_to_location] +label = "Guardar en una ubicación" +noun = "Carpeta" +short = "Guardar en carpeta" + +[settings.automations.action.export] +label = "Exportar con un perfil" +noun = "Exportación" +short = "Exportar" + +[settings.automations.action.upload] +label = "Subir + copiar enlace" +noun = "Subida" +short = "Subir y copiar enlace" + +[settings.automations.action.reveal] +label = "Mostrar en el gestor de archivos" +noun = "Mostrar" +short = "Mostrar en el gestor de archivos" + +[settings.automations.action.open_file] +label = "Abrir archivo" +noun = "Abrir" +short = "Abrir archivo" + +[settings.automations.action.ocr] +label = "Reconocer texto (OCR) al portapapeles" +noun = "Texto" +short = "Copiar texto (OCR)" + +[settings.automations.action.notify] +label = "Mostrar notificación" +noun = "Aviso" +short = "Notificar" + +[settings.automations.action.open_editor] +label = "Abrir el editor" +noun = "Editor" +short = "Abrir el editor" + +[settings.automations.action.skip_editor] +label = "Omitir el editor (sin interfaz)" +noun = "Sin editor" +short = "Omitir el editor" + +[settings.automations.action.apply_preset] +label = "Aplicar un ajuste del editor" +noun = "Ajuste" +short = "Aplicar ajuste" + +[settings.automations.action.run_command] +label = "Ejecutar comando" +noun = "Comando" +short = "Ejecutar comando" + +[settings.automations.action.webhook] +label = "Enviar webhook" +noun = "Webhook" +short = "Enviar webhook" + +[settings.automations.action.delete_local_files] +label = "Eliminar archivos locales" +noun = "Eliminar" +short = "Eliminar archivos locales" + +[settings.automations.condition] +capture_target = "El objetivo de captura es" +duration_at_least = "Duración mínima (segundos)" +duration_at_most = "Duración máxima (segundos)" +organization = "La organización es" +recording_mode = "El modo de grabación es" +window_title = "El título de la ventana contiene" + +[settings.automations.template.copy_screenshot] +description = "Haz una captura y la tendrás lista para pegar." +name = "Copiar automáticamente las nuevas capturas" + +[settings.automations.template.ocr_screenshot] +description = "Cap lee el texto de tu captura y lo copia por ti." +name = "Sacar el texto de las capturas" + +[settings.automations.template.save_screenshot] +description = "Envía cada captura nueva directamente a la carpeta que elijas." +name = "Guardar las capturas en una carpeta" + +[settings.automations.template.reveal_screenshot] +description = "Muestra cada captura en el Finder en cuanto la hagas." +name = "Ir a cada captura nueva" + +[settings.automations.template.export_studio] +description = "Renderiza un MP4 en cuanto termina una grabación Studio." +name = "Exportar automáticamente al terminar de grabar" + +[settings.automations.template.upload_share] +description = "Termina una grabación y el enlace te espera en el portapapeles." +name = "Subir y obtener el enlace para compartir" + +[settings.automations.template.notify_upload] +body = "Tu grabación está lista para compartir." +description = "Recibe un aviso discreto en el escritorio cuando tu grabación se pueda compartir." +name = "Avisarme cuando una subida esté lista" + +[settings.automations.template.webhook_share] +description = "Envía el enlace para compartir a Slack, Discord o tu propio webhook." +name = "Avisar a Slack cuando compartas algo" + [main] delete_recording_confirm = "¿Seguro que quieres eliminar esta grabación?" diff --git a/apps/desktop-gpui/locales/fr.toml b/apps/desktop-gpui/locales/fr.toml index e70141f4c4e..076fa6d22ec 100644 --- a/apps/desktop-gpui/locales/fr.toml +++ b/apps/desktop-gpui/locales/fr.toml @@ -239,6 +239,697 @@ title = "Confidentialité" [settings.general.privacy.telemetry] description = "Cap utilise une télémétrie anonyme pour améliorer sa fiabilité et corriger des bugs. Nous ne collectons jamais le contenu des enregistrements, les titres de fenêtres, les chemins de fichiers ni aucune information personnelle." label = "Partager la télémétrie anonyme" +[settings.prompt] +ok = "OK" + +[settings.sidebar] +account = "Compte" +cancel_sign_in = "Annuler la connexion" +check_for_updates = "Rechercher des mises à jour" +click_to_sign_in = "Cliquez pour vous connecter" +downloading = "Téléchargement de la v{version}…" +downloading_progress = "Téléchargement de la v{version}… {percent} %" +installing = "Installation de la v{version}…" +previous_versions = "Voir les versions précédentes" +restart_to_update = "Redémarrer pour passer à la v{version}" +sign_in = "Se connecter" +sign_out = "Se déconnecter" +signed_in = "Connecté" + +[settings.recordings] +corrupted_body = "L'enregistrement a échoué, ce fichier risque de poser problème dans l'éditeur ! Si vous n'arrivez pas à récupérer le fichier, contactez le support !" +corrupted_title = "L'enregistrement est peut-être corrompu" +delete = "Supprimer" +description = "Gérez vos enregistrements et effectuez des actions." +edit = "Modifier" +empty_library = "Aucun enregistrement trouvé" +failed = "Échec de l'enregistrement" +import = "Importer" +in_progress = "Enregistrement en cours" +load_more = "Afficher plus" +open_bundle = "Ouvrir le dossier de l'enregistrement" +open_link = "Ouvrir le lien" +retry_upload = "Relancer la mise en ligne" +show_all = "Tout afficher" +title = "Enregistrements" + +[settings.recordings.empty] +all = "Aucun enregistrement" +all_matching = "Aucun enregistrement correspondant" +instant = "Aucun enregistrement Instant" +instant_matching = "Aucun enregistrement Instant correspondant" +studio = "Aucun enregistrement Studio" +studio_matching = "Aucun enregistrement Studio correspondant" + +[settings.screenshots] +copy = "Copier dans le presse-papiers" +delete = "Supprimer" +description = "Gérez vos captures d'écran et effectuez des actions." +empty = "Aucune capture d'écran" +empty_library = "Aucune capture d'écran trouvée" +empty_matching = "Aucune capture d'écran correspondante" +import = "Importer une image" +load_more = "Afficher plus" +open_folder = "Ouvrir le dossier" +save_as = "Enregistrer sous" +title = "Captures d'écran" + +[settings.quality] +save_failed = "Impossible d'enregistrer 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." +recommended = "Recommandé" +title = "Enregistrements Studio" +ultra = "Détail maximal" +ultra_description = "Conserve un maximum de détails pour les retouches poussées. Utilise le plus d'espace disque." + +[settings.quality.instant] +description = "Mis en ligne pendant l'enregistrement, le lien est prêt dès que vous arrêtez. Pour une qualité optimale, utilisez le mode Studio : il enregistre dans la meilleure qualité permise par votre écran et vous pouvez exporter jusqu'en 4K." +free_plan = "720p avec l'offre gratuite. Cap Pro enregistre jusqu'en 4K." +max_resolution = "Résolution maximale" +res_1080p = "Texte net pour une taille de mise en ligne raisonnable." +res_1440p = "Plus de détails pour les grands écrans haute résolution." +res_4k = "Le plus de détails. Idéal avec une connexion rapide." +res_720p = "Mises en ligne les plus légères, les liens sont prêts plus vite." +title = "Enregistrements Instant" + +[settings.quality.sharing] +auto_open = "Ouvrir automatiquement les liens de partage" +auto_open_description = "Ouvre le lien dans votre navigateur à la fin d'une mise en ligne." +title = "Partage" + +[settings.options.studio_quality] +balanced = "Équilibré" +compatibility = "Compatibilité" +ultra = "Ultra" + +[settings.options.main_window_start] +close = "Fermer" +minimise = "Réduire" + +[settings.options.post_studio] +do_nothing = "Ne rien faire" +open_editor = "Ouvrir l'éditeur" +show_cap = "Afficher Cap" + +[settings.options.post_screenshot] +copy = "Copier dans le presse-papiers" +copy_and_open_editor = "Copier et ouvrir l'éditeur" +do_nothing = "Ne rien faire" +open_editor = "Ouvrir l'éditeur" + +[settings.options.post_deletion] +do_nothing = "Ne rien faire" +reopen = "Rouvrir l'enregistreur" + +[settings.options.preview_quality] +full = "Complète" +half = "Moitié" +quarter = "Quart" + +[settings.shortcuts] +blocked_while_recording = "Impossible de modifier les raccourcis tant que cet enregistrement utilise sa touche d'arrêt. Arrêtez d'abord l'enregistrement." +needs_modifier_macos = "Incluez ⌘, ⌃ ou ⌥ pour que le raccourci ne gêne pas la saisie." +needs_modifier_other = "Incluez Ctrl, Alt ou Win pour que le raccourci ne gêne pas la saisie." +record_shortcut = "Définir un raccourci" +recording_description = "Lancez un enregistrement sans ouvrir Cap au préalable." +recording_hint = "Appuyez sur les touches à utiliser. Échap annule, Suppr retire le raccourci." +recording_title = "Enregistrement" +saved = "Enregistré. Appuyez sur {keys} dans n'importe quelle app pour {effect}." +saved_moved = "Enregistré. Appuyez sur {keys} dans n'importe quelle app pour {effect}. Retiré de « {shortcut} »." +screenshots_description = "Les raccourcis fonctionnent depuis n'importe quelle app. Cliquez sur un raccourci pour le modifier." +screenshots_title = "Captures d'écran" +type_shortcut = "Saisissez un raccourci…" +unavailable = "Une autre app ou le système utilise déjà ce raccourci. Cliquez pour en choisir un autre." +unsupported_key = "Cette touche ne peut pas être utilisée dans un raccourci. Essayez une lettre, un chiffre ou une touche F." +while_recording_title = "Pendant l'enregistrement" + +[settings.shortcuts.after_screenshot] +hint = "Pour chaque capture, depuis un raccourci ou depuis Cap" +title = "Après une capture d'écran" + +[settings.shortcuts.after_studio] +hint = "À l'arrêt" +title = "Après un enregistrement Studio" + +[settings.shortcuts.after_instant] +hint = "Le lien de partage est toujours copié" +title = "Après un enregistrement Instant" + +[settings.shortcuts.outcome] +instant_copy_link = "Copier le lien seulement" +instant_copy_link_hint = "Rien ne s'ouvre" +instant_open_link = "Ouvrir le lien" +instant_open_link_hint = "Dans votre navigateur" +saved_to_library = "Enregistré dans la bibliothèque" +screenshot_copy = "Copier" +screenshot_copy_and_edit = "Copier et modifier" +screenshot_copy_and_edit_hint = "Les deux à la fois" +screenshot_copy_hint = "Coller n'importe où" +screenshot_do_nothing = "Ne rien faire" +screenshot_open_editor = "Ouvrir l'éditeur" +screenshot_open_editor_hint = "Annoter d'abord" +studio_do_nothing = "Ne rien faire" +studio_open_editor = "Ouvrir l'éditeur" +studio_open_editor_hint = "Modifier tout de suite" +studio_show_cap = "Afficher Cap" +studio_show_cap_hint = "Retour à l'enregistreur" + +[settings.shortcuts.label] +cycle_mode = "Changer de mode d'enregistrement" +fallback = "Raccourci" +open_picker = "Ouvrir le sélecteur d'enregistrement" +pause = "Pause ou reprise" +record_area = "Enregistrer une zone" +record_display = "Enregistrer un écran" +record_window = "Enregistrer une fenêtre" +restart = "Recommencer l'enregistrement" +screenshot_area = "Capturer une zone" +screenshot_display = "Capturer l'écran" +screenshot_window = "Capturer une fenêtre" +start_instant = "Démarrer un enregistrement Instant" +start_studio = "Démarrer un enregistrement Studio" +stop = "Arrêter l'enregistrement" + +[settings.shortcuts.effect] +cycle_mode = "passer d'un mode à l'autre : Studio, Instant et Capture d'écran" +instant_copy = "enregistrer l'écran ou la fenêtre sélectionnés en mode Instant, puis copier le lien de partage à l'arrêt" +instant_copy_and_open = "enregistrer l'écran ou la fenêtre sélectionnés en mode Instant, puis copier et ouvrir le lien de partage à l'arrêt" +open_picker = "ouvrir Cap pour choisir ce que vous voulez enregistrer" +pause = "mettre en pause l'enregistrement en cours, ou le reprendre" +picker_area_instant = "sélectionner une zone, puis l'enregistrer en mode Instant" +picker_area_screenshot = "sélectionner une zone, puis en faire une capture d'écran" +picker_area_studio = "sélectionner une zone, puis l'enregistrer en mode Studio" +picker_display_instant = "choisir un écran, puis l'enregistrer en mode Instant" +picker_display_screenshot = "choisir un écran, puis en faire une capture d'écran" +picker_display_studio = "choisir un écran, puis l'enregistrer en mode Studio" +picker_window_instant = "choisir une fenêtre, puis l'enregistrer en mode Instant" +picker_window_screenshot = "choisir une fenêtre, puis en faire une capture d'écran" +picker_window_studio = "choisir une fenêtre, puis l'enregistrer en mode Studio" +restart = "abandonner la prise en cours et recommencer" +screenshot_area_copy = "sélectionner une zone et copier la capture dans le presse-papiers" +screenshot_area_copy_and_open_editor = "sélectionner une zone, copier la capture et ouvrir l'éditeur" +screenshot_area_open_editor = "sélectionner une zone et ouvrir la capture dans l'éditeur" +screenshot_area_save = "sélectionner une zone et enregistrer la capture dans votre bibliothèque sans rien ouvrir" +screenshot_display_copy = "capturer l'écran sous votre pointeur et copier la capture dans le presse-papiers" +screenshot_display_copy_and_open_editor = "capturer l'écran sous votre pointeur, copier la capture et ouvrir l'éditeur" +screenshot_display_open_editor = "capturer l'écran sous votre pointeur et ouvrir la capture dans l'éditeur" +screenshot_display_save = "capturer l'écran sous votre pointeur et enregistrer la capture dans votre bibliothèque sans rien ouvrir" +screenshot_window_copy = "capturer la fenêtre sous votre pointeur et copier la capture dans le presse-papiers" +screenshot_window_copy_and_open_editor = "capturer la fenêtre sous votre pointeur, copier la capture et ouvrir l'éditeur" +screenshot_window_open_editor = "capturer la fenêtre sous votre pointeur et ouvrir la capture dans l'éditeur" +screenshot_window_save = "capturer la fenêtre sous votre pointeur et enregistrer la capture dans votre bibliothèque sans rien ouvrir" +stop = "arrêter l'enregistrement en cours" +studio = "enregistrer l'écran ou la fenêtre sélectionnés en mode Studio" +studio_open_editor = "enregistrer l'écran ou la fenêtre sélectionnés en mode Studio, puis ouvrir l'éditeur à l'arrêt" + +[settings.shortcuts.sentence] +cycle_mode = "Passer d'un mode à l'autre : Studio, Instant et Capture d'écran." +instant_copy = "Enregistrer l'écran ou la fenêtre sélectionnés en mode Instant, puis copier le lien de partage à l'arrêt." +instant_copy_and_open = "Enregistrer l'écran ou la fenêtre sélectionnés en mode Instant, puis copier et ouvrir le lien de partage à l'arrêt." +open_picker = "Ouvrir Cap pour choisir ce que vous voulez enregistrer." +pause = "Mettre en pause l'enregistrement en cours, ou le reprendre." +picker_area_instant = "Sélectionner une zone, puis l'enregistrer en mode Instant." +picker_area_screenshot = "Sélectionner une zone, puis en faire une capture d'écran." +picker_area_studio = "Sélectionner une zone, puis l'enregistrer en mode Studio." +picker_display_instant = "Choisir un écran, puis l'enregistrer en mode Instant." +picker_display_screenshot = "Choisir un écran, puis en faire une capture d'écran." +picker_display_studio = "Choisir un écran, puis l'enregistrer en mode Studio." +picker_window_instant = "Choisir une fenêtre, puis l'enregistrer en mode Instant." +picker_window_screenshot = "Choisir une fenêtre, puis en faire une capture d'écran." +picker_window_studio = "Choisir une fenêtre, puis l'enregistrer en mode Studio." +restart = "Abandonner la prise en cours et recommencer." +screenshot_area_copy = "Sélectionner une zone et copier la capture dans le presse-papiers." +screenshot_area_copy_and_open_editor = "Sélectionner une zone, copier la capture et ouvrir l'éditeur." +screenshot_area_open_editor = "Sélectionner une zone et ouvrir la capture dans l'éditeur." +screenshot_area_save = "Sélectionner une zone et enregistrer la capture dans votre bibliothèque sans rien ouvrir." +screenshot_display_copy = "Capturer l'écran sous votre pointeur et copier la capture dans le presse-papiers." +screenshot_display_copy_and_open_editor = "Capturer l'écran sous votre pointeur, copier la capture et ouvrir l'éditeur." +screenshot_display_open_editor = "Capturer l'écran sous votre pointeur et ouvrir la capture dans l'éditeur." +screenshot_display_save = "Capturer l'écran sous votre pointeur et enregistrer la capture dans votre bibliothèque sans rien ouvrir." +screenshot_window_copy = "Capturer la fenêtre sous votre pointeur et copier la capture dans le presse-papiers." +screenshot_window_copy_and_open_editor = "Capturer la fenêtre sous votre pointeur, copier la capture et ouvrir l'éditeur." +screenshot_window_open_editor = "Capturer la fenêtre sous votre pointeur et ouvrir la capture dans l'éditeur." +screenshot_window_save = "Capturer la fenêtre sous votre pointeur et enregistrer la capture dans votre bibliothèque sans rien ouvrir." +stop = "Arrêter l'enregistrement en cours." +studio = "Enregistrer l'écran ou la fenêtre sélectionnés en mode Studio." +studio_open_editor = "Enregistrer l'écran ou la fenêtre sélectionnés en mode Studio, puis ouvrir l'éditeur à l'arrêt." + +[settings.cli] +command = "Commande" +copy = "Copier" +description = "Installez la commande Cap pour les terminaux, les agents, les scripts et l'automatisation locale." +install = "Installer la CLI" +installed = "Installée" +installing = "Installation..." +installs_command = "L'app de bureau installe une commande locale\u00A0{command}\u00A0qui renvoie vers la CLI intégrée." +load_failed = "Impossible de charger l'état de la CLI : {error}" +not_installed = "Non installée" +path_configured = "cap a été ajouté à votre PATH. Redémarrez votre terminal pour l'utiliser, ou exécutez ceci maintenant :" +path_missing = "Ajoutez {path} à votre PATH pour utiliser cap depuis un nouveau terminal." +remove = "Supprimer" +removing = "Suppression..." +repair = "Réparer" +repairing = "Réparation..." +retry = "Réessayer" +target = "Cible" +title = "Ligne de commande" + +[settings.transcription] +active_title = "Indications actives" +applied_note = "Ces indications sont appliquées lorsque vous générez des sous-titres dans l'éditeur." +clear = "Effacer" +count = { one = "{count} élément", many = "{count} d'éléments", other = "{count} éléments" } +description = "Ajoutez les noms, orthographes, domaines et préférences de majuscules que la génération des sous-titres doit prendre en compte." +placeholder = "Ajouter un terme" +saved = "Enregistré" +saving = "Enregistrement..." +terms_description = "Ajoutez un terme à la fois pour limiter les fautes de frappe et de mise en forme." +terms_title = "Termes mémorisés" +title = "Transcription" + +[settings.experimental] +developer = "Développeur" +developer_description = "Affiché uniquement dans les versions de développement." +muxer = "Multiplexeur hors processus" +muxer_description = "Exécute le multiplexeur MP4 fragmenté dans un sous-processus isolé pour que ses plantages ne fassent pas échouer votre enregistrement. Nécessite le binaire cap-muxer fourni." +native_app = "Utiliser l'app native" +native_app_description = "Désactivez pour fermer cette app et rouvrir l'app Tauri classique dans l'environnement de développement. Les enregistrements et les paramètres sont partagés." +reliability = "Fiabilité" + +[settings.experimental.switch] +busy = "Terminez votre enregistrement, export, mise en ligne, import 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." +not_found = "Impossible de trouver l'app Cap classique vers laquelle revenir." +open_failed = "Impossible d'ouvrir l'app Cap classique : {error}" +opening = "Ouverture de l'app Cap classique" +opening_detail = "Cette fenêtre se fermera quand l'app Cap classique sera prête." +save_failed = "Impossible d'enregistrer votre préférence d'app. Cap reste ouvert." +switching = "Retour à l'app Cap classique." +timed_out = "L'app classique ne s'est pas ouverte. Vérifiez les erreurs de compilation dans le terminal de développement, puis réessayez." + +[settings.feedback] +app_version = "Version de l'app" +capture_support = "Prise en charge de la capture" +capture_supported = "Capture d'écran : prise en charge" +capture_unsupported = "Capture d'écran : non prise en charge" +community_description = "Des questions, des idées à partager, ou simplement envie de discuter ? Rejoignez la communauté Discord de Cap." +community_title = "Rejoindre la communauté" +debug_description = "Envoyez vos journaux pour nous aider à diagnostiquer les problèmes de Cap. Aucune information personnelle n'est incluse." +debug_title = "Informations de débogage" +description = "Aidez-nous à améliorer Cap en envoyant vos commentaires ou en signalant des bugs. Nous nous en occupons tout de suite." +join_discord = "Rejoindre Discord" +operating_system = "Système d'exploitation" +placeholder = "Dites-nous ce que vous pensez de Cap..." +submit = "Envoyer les commentaires" +submit_failed = "Échec de l'envoi des commentaires" +submitting = "Envoi..." +system_loading = "Chargement des informations système..." +system_title = "Informations système" +thanks = "Merci pour vos commentaires !" +title = "Commentaires" +upload_interrupted = "L'envoi a été interrompu" +upload_logs = "Envoyer les journaux" +upload_sent = "Envoyé. Merci, cela nous aide à trouver le problème." +uploading = "Envoi..." + +[settings.feedback.diagnostic] +confirm_body = "Cap va occuper votre écran avec un motif clignotant et émettre des bips forts pendant environ {seconds} secondes par chaîne de traitement. Retirez votre casque, laissez le volume audible et ne touchez pas à l'ordinateur jusqu'à la fin." +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_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." +run = "Lancer le diagnostic" +run_again = "Relancer" +send = "Envoyer à Cap" +sending = "Envoi..." +show_file = "Afficher le fichier" +stop_recording_first = "Arrêtez l'enregistrement en cours avant de lancer un diagnostic." +title = "Rapport de diagnostic" +try_again = "Réessayer" +warning_body = "Une fenêtre plein écran clignote en noir et blanc pendant que des bips à 1 kHz sortent de vos haut-parleurs ; Cap l'enregistre et mesure le décalage entre l'image et le son. Ne touchez pas à l'ordinateur pendant le test. Les deux modes prennent quelques minutes au total." +warning_title = "Le test occupe votre écran et fait du bruit." + +[settings.feedback.diagnostic.verdict] +environment_only = "ENVIRONNEMENT SEUL" +fail = "ÉCHEC" +inconclusive = "NON CONCLUANT" +pass = "RÉUSSI" +warn = "AVERTISSEMENT" + +[settings.sign_in_required] +body = "Connectez-vous pour continuer. Vous pouvez aussi écrire à hello@cap.so ou rejoindre notre Discord sur cap.link/discord" +title = "Connexion requise" + +[settings.changelog] +fetch_failed = "Impossible de récupérer les nouveautés" +loading = "Chargement des nouveautés..." +new = "NOUVEAU" +version = "Version {version} - {date}" + +[settings.license] +activate = "Activer la licence" +activating = "Activation..." +active_title = "Licence de bureau" +checking = "Vérification…" +deactivate = "Désactiver la licence" +description = "Votre offre Cap et votre licence de bureau, au même endroit." +desktop_description = "Votre licence de bureau couvre l'enregistrement et le montage à usage commercial. Cap Pro ajoute le partage dans le cloud et les fonctionnalités collaboratives." +desktop_name = "Licence de bureau" +expires = "Expire le" +explore_description = "Comparez les tarifs actuels et tout ce qui est inclus sur notre site." +explore_desktop = "Licence de bureau · Usage commercial de l'enregistreur et de l'éditeur de bureau." +explore_pro = "Cap Pro · Une licence de bureau, plus le partage dans le cloud, les fonctionnalités IA et la collaboration." +explore_title = "Découvrir les offres" +free_description = "Enregistrez et montez en local pour un usage personnel. Choisissez une offre payante pour un usage commercial ou plus de fonctionnalités cloud." +have_key = "Vous avez une clé de licence ?" +key = "Clé de licence" +no_instance = "Aucun identifiant d'instance trouvé" +opens_pricing = "Ouvre cap.so/pricing dans votre navigateur." +placeholder = "Clé de licence" +plan_title = "Votre offre" +pro_description = "Votre compte inclut le partage dans le cloud, les fonctionnalités Pro et une licence de bureau pour un usage commercial." +refresh = "Actualiser l'offre" +refresh_failed = "Impossible d'actualiser votre offre. Veuillez réessayer." +save_failed = "Impossible d'enregistrer la licence" +sign_in_hint = "Vous avez déjà Cap Pro ? Connectez-vous à votre compte depuis la barre latérale." +title = "Offre et licence" +view_pricing = "Voir les offres et tarifs ↗" + +[settings.integrations] +configure = "Configurer" +description = "Configurez des intégrations pour étendre les fonctionnalités de Cap et le connecter à des services tiers." +google_drive_description = "Connectez Google Drive pour les nouvelles mises en ligne avec lien de partage. Cap stocke les nouvelles vidéos dans un dossier Cap privé de votre Drive et continue de les diffuser via Cap après les vérifications d'accès habituelles." +managed = "Géré par votre organisation" +managed_by = "Géré par votre organisation : {organization}" +request_timed_out = "La requête a expiré" +s3_description = "Connectez votre propre bucket S3 pour garder la maîtrise totale du stockage de vos données. Toutes les nouvelles mises en ligne avec lien de partage iront automatiquement dans le bucket S3 configuré, pour que vous restiez propriétaire de votre contenu et en gardiez le contrôle. Idéal pour les organisations qui exigent la souveraineté des données et des politiques de stockage personnalisées." +s3_name = "Configuration S3" +title = "Intégrations" +upgrade = "Passer à Pro" + +[settings.integrations.s3] +access_key = "ID de clé d'accès" +bucket = "Nom du bucket" +configuration = "Configuration" +delete_failed = "Impossible de supprimer la configuration S3" +deleted = "Configuration S3 supprimée" +endpoint = "Point de terminaison" +fetch_failed = "Impossible de récupérer la configuration S3" +guide = "guide de configuration du stockage" +guide_intro = "La configuration et la connexion de votre bucket à Cap devraient prendre moins de 10 minutes. Consultez le\u00A0{guide}\u00A0pour commencer." +other_provider = "Autre compatible S3" +provider = "Fournisseur de stockage" +region = "Région" +remove = "Supprimer la configuration" +removing = "Suppression..." +save_failed = "Impossible d'enregistrer la configuration S3" +saved = "Configuration S3 enregistrée" +saving = "Enregistrement..." +secret_key = "Clé d'accès secrète" +test = "Tester la connexion" +test_failed = "Échec du test de connexion S3. Vérifiez votre configuration et votre connexion réseau." +test_ok = "Test de la configuration S3 réussi ! La connexion fonctionne." +test_timeout = "Le test de connexion a expiré au bout de 5 secondes. Vérifiez l'URL du point de terminaison et votre connexion réseau." +testing = "Test..." + +[settings.integrations.gdrive] +active = "Actif" +active_for_uploads = "Actif pour les nouvelles mises en ligne" +cached = "En cache depuis le {time}" +connect = "Connecter Google Drive" +connect_failed = "Impossible de lancer la connexion à Google Drive" +connected_inactive = "Connecté mais inactif" +connection_description = "Google Drive stocke les nouvelles mises en ligne dans un dossier Cap privé de votre Drive. Les vidéos existantes hébergées par Cap ou sur S3 restent sur leur stockage actuel." +connection_title = "Connexion" +disconnect = "Déconnecter" +disconnect_failed = "Impossible de déconnecter Google Drive" +disconnected = "Google Drive déconnecté" +drive_files = "Fichiers Drive" +finish_in_browser = "Terminez la connexion à Google Drive dans votre navigateur, puis revenez ici et actualisez." +not_connected = "Non connecté" +opening = "Ouverture..." +refresh = "Actualiser" +refreshing = "Actualisation..." +remaining = "Restant" +set_active_failed = "Impossible de changer de fournisseur de stockage actif" +storage = "Stockage" +test = "Tester" +test_failed = "Échec du test de connexion Google Drive" +testing = "Test..." +trash = "Corbeille" +updated = "Mis à jour le {time}" +use_drive = "Utiliser Google Drive" +use_s3 = "Utiliser S3" +used = "{usage} utilisés" +used_of = "{usage} utilisés sur {limit}" +waiting = "En attente..." +working = "La connexion Google Drive fonctionne" +working_for = "La connexion Google Drive fonctionne pour {email}" + +[settings.automations] +action_no_effect = "Cette action n'a aucun effet avec le déclencheur sélectionné." +add_action = "Ajouter une action" +add_condition = "Ajouter une condition" +auto_name_empty = "Automatisation {trigger}" +browse = "Parcourir" +check_compatibility = "Vérifier la compatibilité" +compatibility_checked = "Compatibilité vérifiée : cette automatisation ne s'exécute pas encore dans Cap. Les règles enregistrées restent disponibles pour la CLI Cap. Aucune action n'a été exécutée." +condition_never_matches = "Cette condition ne correspond jamais au déclencheur sélectionné." +copy_link = "Copier le lien dans le presse-papiers" +dangerous = "Dans la CLI Cap, cette automatisation exécute des commandes ou envoie des requêtes réseau avec vos autorisations. N'utilisez que des valeurs de confiance." +delete = "Supprimer" +description = "Les règles sont partagées avec la CLI Cap. Cap enregistre ces règles mais n'exécute pas encore les automatisations." +empty_description = "Choisissez un modèle ci-dessous pour démarrer en un clic, ou créez la vôtre de zéro." +empty_title = "Aucune automatisation pour l'instant" +enabled = "Activée" +match_all = "Toutes les conditions" +match_any = "Au moins une condition" +name = "Nom" +new = "Nouvelle automatisation" +no_presets = "Aucun préréglage pour l'instant : créez-en un d'abord dans l'éditeur." +only_if = "UNIQUEMENT SI" +open_in_browser = "Ouvrir dans le navigateur" +retry_save = "Réessayer l'enregistrement" +run_through_shell = "Exécuter via le shell" +save_failed = "Impossible d'enregistrer les automatisations. Vos modifications n'ont pas été enregistrées." +start_from_scratch = "Partir de zéro" +summary_empty = "{trigger} → aucune action pour l'instant" +templates_description = "Un clic pour ajouter une automatisation toute prête. Ajustez ce que vous voulez ensuite." +templates_title = "Modèles" +then = "ALORS FAIRE CECI" +title = "Automatisations" +when = "QUAND CECI SE PRODUIT" + +[settings.automations.status] +disabled_saved = "Désactivée · Enregistrée" +disabled_unsaved = "Désactivée · Non enregistrée" +enabled_saved = "Activée · Enregistrée" +enabled_unsaved = "Activée · Non enregistrée" + +[settings.automations.field] +arguments = "Arguments (séparés par des espaces)" +body = "Corps" +body_template = "Modèle de corps (facultatif)" +compression = "Compression" +destination = "Dossier de destination (facultatif, vide = dossier du projet)" +filename = "Modèle de nom de fichier (facultatif)" +folder = "Dossier" +format = "Format" +frame_rate = "Fréquence d'images" +method = "Méthode" +organization = "ID de l'organisation (facultatif)" +preset = "Préréglage" +program = "Programme" +resolution = "Résolution" +source = "Source" +title = "Titre" +url = "URL" + +[settings.automations.placeholder] +organization_id = "ID de l'organisation" +project_folder = "Dossier du projet" +window_title = "ex. Slack" + +[settings.automations.clipboard] +original = "Capture d'origine" +rendered = "Modifiée / rendue" + +[settings.automations.target] +area = "Zone" +display = "Écran" +window = "Fenêtre" + +[settings.automations.compression] +maximum = "Maximale" +potato = "Patate" +social = "Réseaux sociaux" +web = "Web" + +[settings.automations.trigger.screenshot_taken] +label = "À chaque capture d'écran" +noun = "capture d'écran" +phrase = "Capture d'écran prise" +runs = "S'exécute à chaque capture d'écran." + +[settings.automations.trigger.studio_finished] +label = "À la fin d'un enregistrement Studio" +noun = "enregistrement Studio" +phrase = "Fin d'un enregistrement Studio" +runs = "S'exécute à la fin de chaque enregistrement Studio." + +[settings.automations.trigger.instant_finished] +label = "À la fin d'un enregistrement Instant" +noun = "enregistrement Instant" +phrase = "Fin d'un enregistrement Instant" +runs = "S'exécute à la fin de chaque enregistrement Instant." + +[settings.automations.trigger.recording_started] +label = "Au début d'un enregistrement" +noun = "début d'enregistrement" +phrase = "Début d'un enregistrement" +runs = "S'exécute au début de chaque enregistrement." + +[settings.automations.trigger.upload_completed] +label = "À la fin d'une mise en ligne" +noun = "mise en ligne" +phrase = "Fin d'une mise en ligne" +runs = "S'exécute à la fin de chaque mise en ligne." + +[settings.automations.trigger.video_imported] +label = "À chaque import de vidéo" +noun = "import" +phrase = "Vidéo importée" +runs = "S'exécute à chaque import de vidéo." + +[settings.automations.trigger.recording_deleted] +label = "À chaque suppression d'enregistrement" +noun = "suppression" +phrase = "Enregistrement supprimé" +runs = "S'exécute à chaque suppression d'enregistrement." + +[settings.automations.action.copy_to_clipboard] +label = "Copier dans le presse-papiers" +noun = "Presse-papiers" +short = "Copier dans le presse-papiers" + +[settings.automations.action.save_to_location] +label = "Enregistrer à un emplacement" +noun = "Dossier" +short = "Enregistrer dans un dossier" + +[settings.automations.action.export] +label = "Exporter avec un profil" +noun = "Export" +short = "Exporter" + +[settings.automations.action.upload] +label = "Mettre en ligne + copier le lien" +noun = "Mise en ligne" +short = "Mettre en ligne et copier le lien" + +[settings.automations.action.reveal] +label = "Afficher dans le gestionnaire de fichiers" +noun = "Affichage" +short = "Afficher dans le gestionnaire de fichiers" + +[settings.automations.action.open_file] +label = "Ouvrir le fichier" +noun = "Ouverture" +short = "Ouvrir le fichier" + +[settings.automations.action.ocr] +label = "Reconnaître le texte (OCR) vers le presse-papiers" +noun = "Texte" +short = "Copier le texte (OCR)" + +[settings.automations.action.notify] +label = "Afficher une notification" +noun = "Notification" +short = "Notifier" + +[settings.automations.action.open_editor] +label = "Ouvrir l'éditeur" +noun = "Éditeur" +short = "Ouvrir l'éditeur" + +[settings.automations.action.skip_editor] +label = "Ignorer l'éditeur (sans interface)" +noun = "Sans éditeur" +short = "Ignorer l'éditeur" + +[settings.automations.action.apply_preset] +label = "Appliquer un préréglage de l'éditeur" +noun = "Préréglage" +short = "Appliquer un préréglage" + +[settings.automations.action.run_command] +label = "Exécuter une commande" +noun = "Commande" +short = "Exécuter une commande" + +[settings.automations.action.webhook] +label = "Envoyer un webhook" +noun = "Webhook" +short = "Envoyer un webhook" + +[settings.automations.action.delete_local_files] +label = "Supprimer les fichiers locaux" +noun = "Suppression" +short = "Supprimer les fichiers locaux" + +[settings.automations.condition] +capture_target = "La cible de capture est" +duration_at_least = "Durée minimale (secondes)" +duration_at_most = "Durée maximale (secondes)" +organization = "L'organisation est" +recording_mode = "Le mode d'enregistrement est" +window_title = "Le titre de la fenêtre contient" + +[settings.automations.template.copy_screenshot] +description = "Prenez une capture, elle est aussitôt prête à coller." +name = "Copier automatiquement les nouvelles captures" + +[settings.automations.template.ocr_screenshot] +description = "Cap lit le texte de votre capture et le copie pour vous." +name = "Extraire le texte des captures" + +[settings.automations.template.save_screenshot] +description = "Envoyez chaque nouvelle capture directement dans le dossier de votre choix." +name = "Ranger les captures dans un dossier" + +[settings.automations.template.reveal_screenshot] +description = "Affichez chaque capture dans le Finder dès que vous la prenez." +name = "Afficher chaque nouvelle capture" + +[settings.automations.template.export_studio] +description = "Générez un MP4 dès la fin d'un enregistrement Studio." +name = "Exporter automatiquement en fin d'enregistrement" + +[settings.automations.template.upload_share] +description = "Terminez un enregistrement et le lien vous attend dans le presse-papiers." +name = "Mettre en ligne et récupérer le lien" + +[settings.automations.template.notify_upload] +body = "Votre enregistrement est prêt à être partagé." +description = "Recevez une discrète notification dès que votre enregistrement peut être partagé." +name = "Me prévenir quand une mise en ligne est prête" + +[settings.automations.template.webhook_share] +description = "Envoyez le lien de partage à Slack, Discord ou votre propre webhook." +name = "Prévenir Slack quand vous partagez" + [main] delete_recording_confirm = "Voulez-vous vraiment supprimer cet enregistrement ?" diff --git a/apps/desktop-gpui/locales/hi.toml b/apps/desktop-gpui/locales/hi.toml index 0ee072f4c59..0cf38822d39 100644 --- a/apps/desktop-gpui/locales/hi.toml +++ b/apps/desktop-gpui/locales/hi.toml @@ -239,6 +239,697 @@ title = "गोपनीयता" [settings.general.privacy.telemetry] description = "Cap विश्वसनीयता बेहतर करने और गड़बड़ियाँ ठीक करने के लिए अनाम टेलीमेट्री का उपयोग करता है। हम रिकॉर्डिंग की सामग्री, विंडो के शीर्षक, फ़ाइल पाथ या निजी जानकारी कभी इकट्ठा नहीं करते।" label = "अनाम टेलीमेट्री साझा करें" +[settings.prompt] +ok = "ठीक है" + +[settings.sidebar] +account = "खाता" +cancel_sign_in = "साइन इन रद्द करें" +check_for_updates = "अपडेट देखें" +click_to_sign_in = "साइन इन करने के लिए क्लिक करें" +downloading = "v{version} डाउनलोड हो रहा है…" +downloading_progress = "v{version} डाउनलोड हो रहा है… {percent}%" +installing = "v{version} इंस्टॉल हो रहा है…" +previous_versions = "पिछले वर्शन देखें" +restart_to_update = "v{version} पर अपडेट करने के लिए रीस्टार्ट करें" +sign_in = "साइन इन करें" +sign_out = "साइन आउट करें" +signed_in = "साइन इन है" + +[settings.recordings] +corrupted_body = "रिकॉर्डिंग विफल रही, इसलिए एडिटर में इस फ़ाइल के साथ समस्याएँ हो सकती हैं! अगर फ़ाइल रिकवर करने में दिक्कत हो, तो कृपया सपोर्ट से संपर्क करें!" +corrupted_title = "रिकॉर्डिंग शायद खराब हो गई है" +delete = "डिलीट करें" +description = "अपनी रिकॉर्डिंग मैनेज करें और उन पर कार्रवाई करें।" +edit = "एडिट करें" +empty_library = "कोई रिकॉर्डिंग नहीं मिली" +failed = "रिकॉर्डिंग विफल रही" +import = "इम्पोर्ट करें" +in_progress = "रिकॉर्डिंग जारी है" +load_more = "और लोड करें" +open_bundle = "रिकॉर्डिंग बंडल खोलें" +open_link = "लिंक खोलें" +retry_upload = "अपलोड फिर से करें" +show_all = "सभी दिखाएँ" +title = "रिकॉर्डिंग" + +[settings.recordings.empty] +all = "कोई रिकॉर्डिंग नहीं" +all_matching = "कोई मिलती-जुलती रिकॉर्डिंग नहीं" +instant = "कोई Instant रिकॉर्डिंग नहीं" +instant_matching = "कोई मिलती-जुलती Instant रिकॉर्डिंग नहीं" +studio = "कोई Studio रिकॉर्डिंग नहीं" +studio_matching = "कोई मिलती-जुलती Studio रिकॉर्डिंग नहीं" + +[settings.screenshots] +copy = "क्लिपबोर्ड पर कॉपी करें" +delete = "डिलीट करें" +description = "अपने स्क्रीनशॉट मैनेज करें और उन पर कार्रवाई करें।" +empty = "कोई स्क्रीनशॉट नहीं" +empty_library = "कोई स्क्रीनशॉट नहीं मिला" +empty_matching = "कोई मिलता-जुलता स्क्रीनशॉट नहीं" +import = "इमेज इम्पोर्ट करें" +load_more = "और लोड करें" +open_folder = "फ़ोल्डर खोलें" +save_as = "इस रूप में सेव करें" +title = "स्क्रीनशॉट" + +[settings.quality] +save_failed = "आपकी रिकॉर्डिंग सेटिंग्स सेव नहीं हो सकीं। कृपया फिर से कोशिश करें।" + +[settings.quality.studio] +balanced = "संतुलित" +balanced_description = "उचित फ़ाइल साइज़ में साफ़ डिटेल। ज़्यादातर रिकॉर्डिंग के लिए सही।" +compatibility = "छोटी फ़ाइलें" +compatibility_description = "डिस्क स्पेस बचाता है। बारीक डिटेल थोड़ी धुंधली हो सकती है, ख़ासकर कैमरा में।" +description = "एडिटिंग के लिए आपके कंप्यूटर पर सेव होती हैं। एक्सपोर्ट का साइज़ आप बाद में एडिटर में चुनते हैं।" +recommended = "सुझाया गया" +title = "Studio रिकॉर्डिंग" +ultra = "अधिकतम डिटेल" +ultra_description = "ज़्यादा एडिटिंग के लिए सबसे ज़्यादा डिटेल रखता है। सबसे ज़्यादा डिस्क स्पेस लेता है।" + +[settings.quality.instant] +description = "रिकॉर्ड करते समय ही अपलोड होती है, इसलिए रोकते ही लिंक तैयार रहता है। सबसे अच्छी क्वालिटी के लिए Studio मोड इस्तेमाल करें: यह आपकी स्क्रीन की अधिकतम क्वालिटी में रिकॉर्ड करता है, और आप 4K तक एक्सपोर्ट कर सकते हैं।" +free_plan = "फ़्री प्लान पर 720p। Cap Pro 4K तक रिकॉर्ड करता है।" +max_resolution = "अधिकतम रेज़ोल्यूशन" +res_1080p = "व्यावहारिक अपलोड साइज़ पर साफ़ टेक्स्ट।" +res_1440p = "बड़ी, हाई-रेज़ोल्यूशन स्क्रीन के लिए ज़्यादा डिटेल।" +res_4k = "सबसे ज़्यादा डिटेल। तेज़ कनेक्शन पर सबसे अच्छा।" +res_720p = "सबसे छोटे अपलोड, इसलिए लिंक सबसे जल्दी तैयार होते हैं।" +title = "Instant रिकॉर्डिंग" + +[settings.quality.sharing] +auto_open = "शेयर लिंक अपने आप खोलें" +auto_open_description = "अपलोड पूरा होने पर लिंक आपके ब्राउज़र में खोलें।" +title = "शेयरिंग" + +[settings.options.studio_quality] +balanced = "संतुलित" +compatibility = "कम्पैटिबिलिटी" +ultra = "अल्ट्रा" + +[settings.options.main_window_start] +close = "बंद करें" +minimise = "छोटा करें" + +[settings.options.post_studio] +do_nothing = "कुछ न करें" +open_editor = "एडिटर खोलें" +show_cap = "Cap दिखाएँ" + +[settings.options.post_screenshot] +copy = "क्लिपबोर्ड पर कॉपी करें" +copy_and_open_editor = "कॉपी करके एडिटर खोलें" +do_nothing = "कुछ न करें" +open_editor = "एडिटर खोलें" + +[settings.options.post_deletion] +do_nothing = "कुछ न करें" +reopen = "रिकॉर्डर फिर से खोलें" + +[settings.options.preview_quality] +full = "पूरा" +half = "आधा" +quarter = "एक-चौथाई" + +[settings.shortcuts] +blocked_while_recording = "जब तक यह रिकॉर्डिंग अपनी स्टॉप की इस्तेमाल कर रही है, शॉर्टकट नहीं बदले जा सकते। पहले रिकॉर्डिंग रोकें।" +needs_modifier_macos = "⌘, ⌃ या ⌥ शामिल करें ताकि शॉर्टकट टाइपिंग में बाधा न बने।" +needs_modifier_other = "Ctrl, Alt या Win शामिल करें ताकि शॉर्टकट टाइपिंग में बाधा न बने।" +record_shortcut = "शॉर्टकट सेट करें" +recording_description = "पहले Cap खोले बिना रिकॉर्डिंग शुरू करें।" +recording_hint = "जिन कीज़ का इस्तेमाल करना है उन्हें दबाएँ। Esc रद्द करता है, Delete इसे हटाता है।" +recording_title = "रिकॉर्डिंग" +saved = "सेव हो गया। किसी भी ऐप में {keys} दबाएँ और {effect}।" +saved_moved = "सेव हो गया। किसी भी ऐप में {keys} दबाएँ और {effect}। “{shortcut}” से हटा दिया गया।" +screenshots_description = "शॉर्टकट किसी भी ऐप में काम करते हैं। बदलने के लिए किसी पर क्लिक करें।" +screenshots_title = "स्क्रीनशॉट" +type_shortcut = "शॉर्टकट दबाएँ…" +unavailable = "कोई दूसरा ऐप या सिस्टम पहले से यह शॉर्टकट इस्तेमाल कर रहा है। कोई दूसरा चुनने के लिए क्लिक करें।" +unsupported_key = "यह की शॉर्टकट में इस्तेमाल नहीं हो सकती। कोई अक्षर, संख्या या F-की आज़माएँ।" +while_recording_title = "रिकॉर्डिंग के दौरान" + +[settings.shortcuts.after_screenshot] +hint = "हर स्क्रीनशॉट के लिए, शॉर्टकट से या Cap से" +title = "स्क्रीनशॉट के बाद" + +[settings.shortcuts.after_studio] +hint = "रोकने पर" +title = "Studio रिकॉर्डिंग के बाद" + +[settings.shortcuts.after_instant] +hint = "शेयर लिंक हमेशा कॉपी होता है" +title = "Instant रिकॉर्डिंग के बाद" + +[settings.shortcuts.outcome] +instant_copy_link = "सिर्फ़ लिंक कॉपी करें" +instant_copy_link_hint = "कुछ नहीं खुलता" +instant_open_link = "लिंक खोलें" +instant_open_link_hint = "आपके ब्राउज़र में" +saved_to_library = "लाइब्रेरी में सेव" +screenshot_copy = "कॉपी करें" +screenshot_copy_and_edit = "कॉपी और एडिट" +screenshot_copy_and_edit_hint = "दोनों एक साथ" +screenshot_copy_hint = "कहीं भी पेस्ट करें" +screenshot_do_nothing = "कुछ न करें" +screenshot_open_editor = "एडिटर खोलें" +screenshot_open_editor_hint = "पहले एनोटेट करें" +studio_do_nothing = "कुछ न करें" +studio_open_editor = "एडिटर खोलें" +studio_open_editor_hint = "तुरंत एडिट करें" +studio_show_cap = "Cap दिखाएँ" +studio_show_cap_hint = "रिकॉर्डर पर वापस" + +[settings.shortcuts.label] +cycle_mode = "रिकॉर्डिंग मोड बदलें" +fallback = "शॉर्टकट" +open_picker = "रिकॉर्डिंग पिकर खोलें" +pause = "पॉज़ या फिर से शुरू करें" +record_area = "क्षेत्र रिकॉर्ड करें" +record_display = "डिस्प्ले रिकॉर्ड करें" +record_window = "विंडो रिकॉर्ड करें" +restart = "रिकॉर्डिंग फिर से शुरू करें" +screenshot_area = "क्षेत्र का स्क्रीनशॉट" +screenshot_display = "डिस्प्ले का स्क्रीनशॉट" +screenshot_window = "विंडो का स्क्रीनशॉट" +start_instant = "Instant रिकॉर्डिंग शुरू करें" +start_studio = "Studio रिकॉर्डिंग शुरू करें" +stop = "रिकॉर्डिंग रोकें" + +[settings.shortcuts.effect] +cycle_mode = "Studio, Instant और स्क्रीनशॉट मोड के बीच बदलें" +instant_copy = "चुनी गई स्क्रीन या विंडो को Instant मोड में रिकॉर्ड करें, फिर रोकने पर शेयर लिंक कॉपी करें" +instant_copy_and_open = "चुनी गई स्क्रीन या विंडो को Instant मोड में रिकॉर्ड करें, फिर रोकने पर शेयर लिंक कॉपी करके खोलें" +open_picker = "Cap खोलकर चुनें कि क्या रिकॉर्ड करना है" +pause = "मौजूदा रिकॉर्डिंग पॉज़ करें या फिर से शुरू करें" +picker_area_instant = "क्षेत्र चुनकर उसे Instant मोड में रिकॉर्ड करें" +picker_area_screenshot = "क्षेत्र चुनकर उसका स्क्रीनशॉट लें" +picker_area_studio = "क्षेत्र चुनकर उसे Studio मोड में रिकॉर्ड करें" +picker_display_instant = "डिस्प्ले चुनकर उसे Instant मोड में रिकॉर्ड करें" +picker_display_screenshot = "डिस्प्ले चुनकर उसका स्क्रीनशॉट लें" +picker_display_studio = "डिस्प्ले चुनकर उसे Studio मोड में रिकॉर्ड करें" +picker_window_instant = "विंडो चुनकर उसे Instant मोड में रिकॉर्ड करें" +picker_window_screenshot = "विंडो चुनकर उसका स्क्रीनशॉट लें" +picker_window_studio = "विंडो चुनकर उसे Studio मोड में रिकॉर्ड करें" +restart = "मौजूदा टेक हटाकर फिर से शुरू करें" +screenshot_area_copy = "क्षेत्र चुनकर स्क्रीनशॉट को क्लिपबोर्ड पर कॉपी करें" +screenshot_area_copy_and_open_editor = "क्षेत्र चुनकर स्क्रीनशॉट कॉपी करें और एडिटर खोलें" +screenshot_area_open_editor = "क्षेत्र चुनकर स्क्रीनशॉट को एडिटर में खोलें" +screenshot_area_save = "क्षेत्र चुनकर कुछ खोले बिना स्क्रीनशॉट को अपनी लाइब्रेरी में सेव करें" +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 = "पॉइंटर के नीचे की विंडो कैप्चर करके कुछ खोले बिना स्क्रीनशॉट को अपनी लाइब्रेरी में सेव करें" +stop = "मौजूदा रिकॉर्डिंग रोकें" +studio = "चुनी गई स्क्रीन या विंडो को Studio मोड में रिकॉर्ड करें" +studio_open_editor = "चुनी गई स्क्रीन या विंडो को Studio मोड में रिकॉर्ड करें, फिर रोकने पर एडिटर खोलें" + +[settings.shortcuts.sentence] +cycle_mode = "Studio, Instant और स्क्रीनशॉट मोड के बीच बदलें।" +instant_copy = "चुनी गई स्क्रीन या विंडो को Instant मोड में रिकॉर्ड करें, फिर रोकने पर शेयर लिंक कॉपी करें।" +instant_copy_and_open = "चुनी गई स्क्रीन या विंडो को Instant मोड में रिकॉर्ड करें, फिर रोकने पर शेयर लिंक कॉपी करके खोलें।" +open_picker = "Cap खोलकर चुनें कि क्या रिकॉर्ड करना है।" +pause = "मौजूदा रिकॉर्डिंग पॉज़ करें या फिर से शुरू करें।" +picker_area_instant = "क्षेत्र चुनकर उसे Instant मोड में रिकॉर्ड करें।" +picker_area_screenshot = "क्षेत्र चुनकर उसका स्क्रीनशॉट लें।" +picker_area_studio = "क्षेत्र चुनकर उसे Studio मोड में रिकॉर्ड करें।" +picker_display_instant = "डिस्प्ले चुनकर उसे Instant मोड में रिकॉर्ड करें।" +picker_display_screenshot = "डिस्प्ले चुनकर उसका स्क्रीनशॉट लें।" +picker_display_studio = "डिस्प्ले चुनकर उसे Studio मोड में रिकॉर्ड करें।" +picker_window_instant = "विंडो चुनकर उसे Instant मोड में रिकॉर्ड करें।" +picker_window_screenshot = "विंडो चुनकर उसका स्क्रीनशॉट लें।" +picker_window_studio = "विंडो चुनकर उसे Studio मोड में रिकॉर्ड करें।" +restart = "मौजूदा टेक हटाकर फिर से शुरू करें।" +screenshot_area_copy = "क्षेत्र चुनकर स्क्रीनशॉट को क्लिपबोर्ड पर कॉपी करें।" +screenshot_area_copy_and_open_editor = "क्षेत्र चुनकर स्क्रीनशॉट कॉपी करें और एडिटर खोलें।" +screenshot_area_open_editor = "क्षेत्र चुनकर स्क्रीनशॉट को एडिटर में खोलें।" +screenshot_area_save = "क्षेत्र चुनकर कुछ खोले बिना स्क्रीनशॉट को अपनी लाइब्रेरी में सेव करें।" +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 = "पॉइंटर के नीचे की विंडो कैप्चर करके कुछ खोले बिना स्क्रीनशॉट को अपनी लाइब्रेरी में सेव करें।" +stop = "मौजूदा रिकॉर्डिंग रोकें।" +studio = "चुनी गई स्क्रीन या विंडो को Studio मोड में रिकॉर्ड करें।" +studio_open_editor = "चुनी गई स्क्रीन या विंडो को Studio मोड में रिकॉर्ड करें, फिर रोकने पर एडिटर खोलें।" + +[settings.cli] +command = "कमांड" +copy = "कॉपी करें" +description = "टर्मिनल, एजेंट, स्क्रिप्ट और लोकल ऑटोमेशन के लिए Cap कमांड इंस्टॉल करें।" +install = "CLI इंस्टॉल करें" +installed = "इंस्टॉल है" +installing = "इंस्टॉल हो रहा है..." +installs_command = "डेस्कटॉप ऐप एक लोकल\u00A0{command}\u00A0कमांड इंस्टॉल करता है जो बंडल की गई CLI की ओर इशारा करता है।" +load_failed = "CLI की स्थिति लोड नहीं हो सकी: {error}" +not_installed = "इंस्टॉल नहीं है" +path_configured = "cap को आपके PATH में जोड़ दिया गया है। इस्तेमाल करने के लिए टर्मिनल रीस्टार्ट करें, या अभी यह चलाएँ:" +path_missing = "नए टर्मिनल से cap इस्तेमाल करने के लिए {path} को अपने PATH में जोड़ें।" +remove = "हटाएँ" +removing = "हटाया जा रहा है..." +repair = "ठीक करें" +repairing = "ठीक किया जा रहा है..." +retry = "फिर से कोशिश करें" +target = "टारगेट" +title = "कमांड लाइन" + +[settings.transcription] +active_title = "सक्रिय संकेत" +applied_note = "जब आप एडिटर में कैप्शन बनाते हैं, तब ये संकेत लागू होते हैं।" +clear = "साफ़ करें" +count = { one = "{count} आइटम", other = "{count} आइटम" } +description = "वे नाम, स्पेलिंग, डोमेन और कैपिटलाइज़ेशन की पसंद जोड़ें जिनका कैप्शन बनाते समय ध्यान रखा जाना चाहिए।" +placeholder = "शब्द जोड़ें" +saved = "सेव हो गया" +saving = "सेव हो रहा है..." +terms_description = "टाइपो और फ़ॉर्मैटिंग की गलतियाँ कम करने के लिए एक बार में एक शब्द जोड़ें।" +terms_title = "याद रखे गए शब्द" +title = "ट्रांसक्रिप्शन" + +[settings.experimental] +developer = "डेवलपर" +developer_description = "सिर्फ़ डेवलपमेंट बिल्ड में दिखता है।" +muxer = "अलग प्रोसेस में मक्सर" +muxer_description = "फ़्रैगमेंटेड-MP4 मक्सर को अलग सबप्रोसेस में चलाता है ताकि मक्सर क्रैश होने से आपकी रिकॉर्डिंग न रुके। इसके लिए बंडल की गई cap-muxer बाइनरी ज़रूरी है।" +native_app = "नेटिव ऐप इस्तेमाल करें" +native_app_description = "बंद करने पर यह ऐप बंद होगा और डेवलपमेंट हार्नेस में क्लासिक Tauri ऐप फिर से खुलेगा। रिकॉर्डिंग और सेटिंग्स साझा रहती हैं।" +reliability = "विश्वसनीयता" + +[settings.experimental.switch] +busy = "क्लासिक ऐप पर जाने से पहले अपनी रिकॉर्डिंग, एक्सपोर्ट, अपलोड, इम्पोर्ट या ट्रांसक्रिप्शन का काम पूरा करें।" +check_failed = "यह जाँच नहीं हो सकी कि क्लासिक Cap ऐप खुला या नहीं: {error}" +data_stays = "आपकी रिकॉर्डिंग और सेटिंग्स ठीक वहीं रहती हैं जहाँ वे हैं।" +failed = "क्लासिक Cap ऐप नहीं खुल सका।" +not_found = "वापस जाने के लिए क्लासिक Cap ऐप नहीं मिला।" +open_failed = "क्लासिक Cap ऐप नहीं खुल सका: {error}" +opening = "क्लासिक Cap ऐप खुल रहा है" +opening_detail = "क्लासिक Cap ऐप तैयार होने पर यह विंडो बंद हो जाएगी।" +save_failed = "आपकी ऐप पसंद सेव नहीं हो सकी। Cap अभी भी खुला है।" +switching = "क्लासिक Cap ऐप पर वापस जा रहे हैं।" +timed_out = "क्लासिक ऐप नहीं खुला। डेव टर्मिनल में बिल्ड की गड़बड़ियाँ देखें, फिर से कोशिश करें।" + +[settings.feedback] +app_version = "ऐप वर्शन" +capture_support = "कैप्चर सपोर्ट" +capture_supported = "स्क्रीन कैप्चर: सपोर्टेड" +capture_unsupported = "स्क्रीन कैप्चर: सपोर्टेड नहीं" +community_description = "कोई सवाल है, आइडिया शेयर करना है, या बस बातचीत करनी है? Cap की Discord कम्युनिटी से जुड़ें।" +community_title = "कम्युनिटी से जुड़ें" +debug_description = "Cap की समस्याएँ पहचानने में हमारी मदद के लिए अपने लॉग अपलोड करें। इसमें कोई निजी जानकारी शामिल नहीं होती।" +debug_title = "डीबग जानकारी" +description = "फ़ीडबैक भेजकर या बग रिपोर्ट करके Cap को बेहतर बनाने में हमारी मदद करें। हम तुरंत इस पर काम करेंगे।" +join_discord = "Discord से जुड़ें" +operating_system = "ऑपरेटिंग सिस्टम" +placeholder = "हमें बताएँ कि आप Cap के बारे में क्या सोचते हैं..." +submit = "फ़ीडबैक सबमिट करें" +submit_failed = "फ़ीडबैक सबमिट नहीं हो सका" +submitting = "सबमिट हो रहा है..." +system_loading = "सिस्टम जानकारी लोड हो रही है..." +system_title = "सिस्टम जानकारी" +thanks = "आपके फ़ीडबैक के लिए धन्यवाद!" +title = "फ़ीडबैक" +upload_interrupted = "अपलोड बीच में रुक गया" +upload_logs = "लॉग अपलोड करें" +upload_sent = "भेज दिया गया। धन्यवाद, इससे हमें समस्या ढूँढने में मदद मिलती है।" +uploading = "अपलोड हो रहा है..." + +[settings.feedback.diagnostic] +confirm_body = "Cap आपकी स्क्रीन पर चमकता पैटर्न दिखाएगा और हर पाइपलाइन के लिए लगभग {seconds} सेकंड तक तेज़ बीप बजाएगा। हेडफ़ोन उतार दें, आवाज़ सुनाई देने लायक रखें, और पूरा होने तक कंप्यूटर को न छुएँ।" +confirm_title = "डायग्नोस्टिक चलाएँ?" +description = "एक छोटा चमकता/बीप करता टेस्ट पैटर्न रिकॉर्ड करता है, मापता है कि तस्वीर और आवाज़ कितनी अलग हो जाती हैं, और आपके डिस्प्ले, कैमरा, माइक्रोफ़ोन, डिस्क स्पेस और रिकॉर्डिंग सेटिंग्स का स्नैपशॉट लेता है। जब तक आप भेजना न चुनें, कुछ भी कहीं नहीं भेजा जाता।" +mic_description = "आपका माइक्रोफ़ोन भी रिकॉर्ड करता है और जाँचता है कि वह बीप को सही समय पर सुनता है या नहीं। ऐसे स्पीकर चाहिए जिन्हें माइक सच में सुन सके।" +mic_label = "माइक्रोफ़ोन टेस्ट करें" +mode_description = "कौन-सी पाइपलाइन टेस्ट करनी है। दोनों चुनने पर पहले Studio और फिर Instant हिस्सा चलता है।" +mode_label = "रिकॉर्डिंग मोड" +no_cli = "इस बिल्ड के साथ Cap कमांड-लाइन टूल नहीं मिला, इसलिए रिपोर्ट में सिर्फ़ आपके एनवायरनमेंट की जानकारी होगी।" +no_cli_sync = "इस बिल्ड के साथ Cap कमांड-लाइन टूल नहीं मिला, इसलिए सिंक टेस्ट नहीं चल सका।" +run = "डायग्नोस्टिक चलाएँ" +run_again = "फिर से चलाएँ" +send = "Cap को भेजें" +sending = "भेजा जा रहा है..." +show_file = "फ़ाइल दिखाएँ" +stop_recording_first = "डायग्नोस्टिक चलाने से पहले मौजूदा रिकॉर्डिंग रोकें।" +title = "डायग्नोस्टिक रिपोर्ट" +try_again = "फिर से कोशिश करें" +warning_body = "एक फ़ुलस्क्रीन विंडो काले और सफ़ेद रंग में चमकती है जबकि आपके स्पीकर से 1kHz की बीप बजती है, और Cap इसे रिकॉर्ड करके मापता है कि तस्वीर और आवाज़ कितनी अलग हो गई हैं। चलते समय कंप्यूटर को न छुएँ। दोनों मोड मिलाकर कुछ मिनट लगते हैं।" +warning_title = "टेस्ट आपकी पूरी स्क्रीन लेता है और आवाज़ करता है।" + +[settings.feedback.diagnostic.verdict] +environment_only = "सिर्फ़ एनवायरनमेंट" +fail = "फ़ेल" +inconclusive = "अनिर्णायक" +pass = "पास" +warn = "चेतावनी" + +[settings.sign_in_required] +body = "जारी रखने के लिए कृपया साइन इन करें। या hello@cap.so पर ईमेल करें या cap.link/discord पर हमारे Discord से जुड़ें" +title = "साइन इन ज़रूरी है" + +[settings.changelog] +fetch_failed = "चेंजलॉग नहीं मिल सका" +loading = "चेंजलॉग लोड हो रहा है..." +new = "नया" +version = "वर्शन {version} - {date}" + +[settings.license] +activate = "लाइसेंस सक्रिय करें" +activating = "सक्रिय हो रहा है..." +active_title = "डेस्कटॉप लाइसेंस" +checking = "जाँच हो रही है…" +deactivate = "लाइसेंस निष्क्रिय करें" +description = "आपका Cap प्लान और डेस्कटॉप लाइसेंस, एक ही जगह।" +desktop_description = "आपका डेस्कटॉप लाइसेंस कमर्शियल रिकॉर्डिंग और एडिटिंग को कवर करता है। Cap Pro इसमें क्लाउड शेयरिंग और कोलैबोरेशन फ़ीचर जोड़ता है।" +desktop_name = "डेस्कटॉप लाइसेंस" +expires = "समाप्ति" +explore_description = "हमारी वेबसाइट पर मौजूदा कीमतें और उनमें शामिल सब कुछ तुलना करें।" +explore_desktop = "डेस्कटॉप लाइसेंस · डेस्कटॉप रिकॉर्डर और एडिटर का कमर्शियल इस्तेमाल।" +explore_pro = "Cap Pro · डेस्कटॉप लाइसेंस, साथ में क्लाउड शेयरिंग, AI फ़ीचर और कोलैबोरेशन।" +explore_title = "प्लान देखें" +free_description = "निजी इस्तेमाल के लिए लोकल रूप से रिकॉर्ड और एडिट करें। कमर्शियल इस्तेमाल या ज़्यादा क्लाउड फ़ीचर के लिए पेड प्लान चुनें।" +have_key = "क्या आपके पास लाइसेंस की है?" +key = "लाइसेंस की" +no_instance = "कोई इंस्टेंस ID नहीं मिली" +opens_pricing = "आपके ब्राउज़र में cap.so/pricing खोलता है।" +placeholder = "लाइसेंस की" +plan_title = "आपका प्लान" +pro_description = "आपके खाते में क्लाउड शेयरिंग, Pro फ़ीचर और कमर्शियल इस्तेमाल के लिए डेस्कटॉप लाइसेंस शामिल है।" +refresh = "प्लान रीफ़्रेश करें" +refresh_failed = "आपका प्लान रीफ़्रेश नहीं हो सका। कृपया फिर से कोशिश करें।" +save_failed = "लाइसेंस सेव नहीं हो सका" +sign_in_hint = "पहले से Cap Pro है? साइडबार से अपने खाते से साइन इन करें।" +title = "प्लान और लाइसेंस" +view_pricing = "प्लान और कीमतें देखें ↗" + +[settings.integrations] +configure = "कॉन्फ़िगर करें" +description = "Cap की क्षमताएँ बढ़ाने और थर्ड-पार्टी सेवाओं से जुड़ने के लिए इंटीग्रेशन कॉन्फ़िगर करें।" +google_drive_description = "नए शेयर करने लायक लिंक वाले अपलोड के लिए Google Drive कनेक्ट करें। Cap नए वीडियो आपके Drive में एक निजी Cap फ़ोल्डर में रखता है और सामान्य एक्सेस जाँच के बाद उन्हें Cap के ज़रिए दिखाता रहता है।" +managed = "आपके संगठन द्वारा मैनेज किया जाता है" +managed_by = "आपके संगठन द्वारा मैनेज किया जाता है: {organization}" +request_timed_out = "अनुरोध का समय समाप्त हो गया" +s3_description = "अपने डेटा स्टोरेज पर पूरा नियंत्रण पाने के लिए अपना S3 बकेट कनेक्ट करें। सभी नए शेयर करने लायक लिंक वाले अपलोड अपने आप आपके कॉन्फ़िगर किए गए S3 बकेट में अपलोड होंगे, ताकि आपकी सामग्री पर पूरा स्वामित्व और नियंत्रण आपके पास रहे। डेटा संप्रभुता और कस्टम स्टोरेज नीतियों की ज़रूरत वाले संगठनों के लिए बढ़िया।" +s3_name = "S3 कॉन्फ़िग" +title = "इंटीग्रेशन" +upgrade = "Pro में अपग्रेड करें" + +[settings.integrations.s3] +access_key = "एक्सेस की ID" +bucket = "बकेट का नाम" +configuration = "कॉन्फ़िगरेशन" +delete_failed = "S3 कॉन्फ़िग डिलीट नहीं हो सका" +deleted = "S3 कॉन्फ़िगरेशन डिलीट हो गया" +endpoint = "एंडपॉइंट" +fetch_failed = "S3 कॉन्फ़िग नहीं मिल सका" +guide = "स्टोरेज कॉन्फ़िग गाइड" +guide_intro = "अपना स्टोरेज बकेट सेट अप करके Cap से कनेक्ट करने में 10 मिनट से कम लगने चाहिए। शुरू करने के लिए\u00A0{guide}\u00A0देखें।" +other_provider = "अन्य S3-कम्पैटिबल" +provider = "स्टोरेज प्रोवाइडर" +region = "रीजन" +remove = "कॉन्फ़िग हटाएँ" +removing = "हटाया जा रहा है..." +save_failed = "S3 कॉन्फ़िग सेव नहीं हो सका" +saved = "S3 कॉन्फ़िगरेशन सेव हो गया" +saving = "सेव हो रहा है..." +secret_key = "सीक्रेट एक्सेस की" +test = "कनेक्शन टेस्ट करें" +test_failed = "S3 कनेक्शन टेस्ट विफल रहा। अपना कॉन्फ़िग और नेटवर्क कनेक्शन जाँचें।" +test_ok = "S3 कॉन्फ़िगरेशन टेस्ट सफल रहा! कनेक्शन काम कर रहा है।" +test_timeout = "कनेक्शन टेस्ट 5 सेकंड बाद टाइम आउट हो गया। कृपया अपना एंडपॉइंट URL और नेटवर्क कनेक्शन जाँचें।" +testing = "टेस्ट हो रहा है..." + +[settings.integrations.gdrive] +active = "सक्रिय" +active_for_uploads = "नए अपलोड के लिए सक्रिय" +cached = "{time} का कैश" +connect = "Google Drive कनेक्ट करें" +connect_failed = "Google Drive कनेक्शन शुरू नहीं हो सका" +connected_inactive = "कनेक्टेड है पर सक्रिय नहीं" +connection_description = "Google Drive नए अपलोड आपके Drive में एक निजी Cap फ़ोल्डर में रखता है। Cap पर होस्ट किए गए और S3 के मौजूदा वीडियो अपने मौजूदा स्टोरेज का ही इस्तेमाल करते रहते हैं।" +connection_title = "कनेक्शन" +disconnect = "डिस्कनेक्ट करें" +disconnect_failed = "Google Drive डिस्कनेक्ट नहीं हो सका" +disconnected = "Google Drive डिस्कनेक्ट हो गया" +drive_files = "Drive फ़ाइलें" +finish_in_browser = "अपने ब्राउज़र में Google Drive कनेक्ट करना पूरा करें, फिर यहाँ लौटकर रीफ़्रेश करें।" +not_connected = "कनेक्ट नहीं है" +opening = "खुल रहा है..." +refresh = "रीफ़्रेश करें" +refreshing = "रीफ़्रेश हो रहा है..." +remaining = "बाकी" +set_active_failed = "सक्रिय स्टोरेज प्रोवाइडर अपडेट नहीं हो सका" +storage = "स्टोरेज" +test = "टेस्ट करें" +test_failed = "Google Drive कनेक्शन टेस्ट विफल रहा" +testing = "टेस्ट हो रहा है..." +trash = "ट्रैश" +updated = "{time} को अपडेट हुआ" +use_drive = "Google Drive इस्तेमाल करें" +use_s3 = "S3 इस्तेमाल करें" +used = "{usage} इस्तेमाल हुआ" +used_of = "{limit} में से {usage} इस्तेमाल हुआ" +waiting = "इंतज़ार हो रहा है..." +working = "Google Drive कनेक्शन काम कर रहा है" +working_for = "{email} के लिए Google Drive कनेक्शन काम कर रहा है" + +[settings.automations] +action_no_effect = "चुने गए ट्रिगर के लिए इस कार्रवाई का कोई असर नहीं होता।" +add_action = "कार्रवाई जोड़ें" +add_condition = "शर्त जोड़ें" +auto_name_empty = "{trigger} ऑटोमेशन" +browse = "ब्राउज़ करें" +check_compatibility = "कम्पैटिबिलिटी जाँचें" +compatibility_checked = "कम्पैटिबिलिटी जाँची गई: यह ऑटोमेशन अभी Cap में नहीं चलता। सेव किए गए नियम Cap CLI के लिए उपलब्ध रहते हैं। कोई कार्रवाई नहीं चलाई गई।" +condition_never_matches = "चुने गए ट्रिगर के लिए यह शर्त कभी पूरी नहीं होती।" +copy_link = "लिंक क्लिपबोर्ड पर कॉपी करें" +dangerous = "Cap CLI में यह ऑटोमेशन आपकी अनुमतियों से कमांड चलाता है या नेटवर्क अनुरोध भेजता है। सिर्फ़ भरोसेमंद वैल्यू इस्तेमाल करें।" +delete = "डिलीट करें" +description = "नियम Cap CLI के साथ साझा होते हैं। Cap इन नियमों को सेव करता है, लेकिन अभी ऑटोमेशन नहीं चलाता।" +empty_description = "एक क्लिक में शुरू करने के लिए नीचे कोई टेम्पलेट चुनें, या शुरुआत से अपना बनाएँ।" +empty_title = "अभी कोई ऑटोमेशन नहीं" +enabled = "चालू" +match_all = "सभी मिलें" +match_any = "कोई भी मिले" +name = "नाम" +new = "नया ऑटोमेशन" +no_presets = "अभी कोई प्रीसेट नहीं है, पहले एडिटर में एक बनाएँ।" +only_if = "सिर्फ़ तब चलाएँ जब" +open_in_browser = "ब्राउज़र में खोलें" +retry_save = "फिर से सेव करें" +run_through_shell = "शेल के ज़रिए चलाएँ" +save_failed = "ऑटोमेशन सेव नहीं हो सके। आपके बदलाव सेव नहीं हुए हैं।" +start_from_scratch = "शुरुआत से बनाएँ" +summary_empty = "{trigger} → अभी कोई कार्रवाई नहीं" +templates_description = "एक क्लिक में तैयार ऑटोमेशन जोड़ें। बाद में कुछ भी बदलें।" +templates_title = "टेम्पलेट" +then = "फिर यह करें" +title = "ऑटोमेशन" +when = "जब यह हो" + +[settings.automations.status] +disabled_saved = "बंद · सेव है" +disabled_unsaved = "बंद · सेव नहीं है" +enabled_saved = "चालू · सेव है" +enabled_unsaved = "चालू · सेव नहीं है" + +[settings.automations.field] +arguments = "आर्ग्युमेंट (स्पेस से अलग)" +body = "मुख्य भाग" +body_template = "बॉडी टेम्पलेट (वैकल्पिक)" +compression = "कम्प्रेशन" +destination = "डेस्टिनेशन फ़ोल्डर (वैकल्पिक, खाली = प्रोजेक्ट फ़ोल्डर)" +filename = "फ़ाइल नाम टेम्पलेट (वैकल्पिक)" +folder = "फ़ोल्डर" +format = "फ़ॉर्मैट" +frame_rate = "फ़्रेम रेट" +method = "मेथड" +organization = "संगठन ID (वैकल्पिक)" +preset = "प्रीसेट" +program = "प्रोग्राम" +resolution = "रेज़ोल्यूशन" +source = "सोर्स" +title = "शीर्षक" +url = "URL" + +[settings.automations.placeholder] +organization_id = "संगठन ID" +project_folder = "प्रोजेक्ट फ़ोल्डर" +window_title = "जैसे Slack" + +[settings.automations.clipboard] +original = "ओरिजिनल कैप्चर" +rendered = "एडिट की गई / रेंडर की गई" + +[settings.automations.target] +area = "क्षेत्र" +display = "डिस्प्ले" +window = "विंडो" + +[settings.automations.compression] +maximum = "अधिकतम" +potato = "सबसे कम" +social = "सोशल" +web = "वेब" + +[settings.automations.trigger.screenshot_taken] +label = "स्क्रीनशॉट लेने पर" +noun = "स्क्रीनशॉट" +phrase = "स्क्रीनशॉट लिया गया" +runs = "हर स्क्रीनशॉट पर चलता है।" + +[settings.automations.trigger.studio_finished] +label = "Studio रिकॉर्डिंग खत्म होने पर" +noun = "Studio रिकॉर्डिंग" +phrase = "Studio रिकॉर्डिंग खत्म हुई" +runs = "हर Studio रिकॉर्डिंग खत्म होने पर चलता है।" + +[settings.automations.trigger.instant_finished] +label = "Instant रिकॉर्डिंग खत्म होने पर" +noun = "Instant रिकॉर्डिंग" +phrase = "Instant रिकॉर्डिंग खत्म हुई" +runs = "हर Instant रिकॉर्डिंग खत्म होने पर चलता है।" + +[settings.automations.trigger.recording_started] +label = "रिकॉर्डिंग शुरू होने पर" +noun = "रिकॉर्डिंग शुरू" +phrase = "रिकॉर्डिंग शुरू हुई" +runs = "हर रिकॉर्डिंग शुरू होने पर चलता है।" + +[settings.automations.trigger.upload_completed] +label = "अपलोड पूरा होने पर" +noun = "अपलोड" +phrase = "अपलोड पूरा हुआ" +runs = "हर अपलोड पूरा होने पर चलता है।" + +[settings.automations.trigger.video_imported] +label = "वीडियो इम्पोर्ट होने पर" +noun = "इम्पोर्ट" +phrase = "वीडियो इम्पोर्ट हुआ" +runs = "हर वीडियो इम्पोर्ट होने पर चलता है।" + +[settings.automations.trigger.recording_deleted] +label = "रिकॉर्डिंग डिलीट होने पर" +noun = "डिलीट" +phrase = "रिकॉर्डिंग डिलीट हुई" +runs = "हर रिकॉर्डिंग डिलीट होने पर चलता है।" + +[settings.automations.action.copy_to_clipboard] +label = "क्लिपबोर्ड पर कॉपी करें" +noun = "क्लिपबोर्ड" +short = "क्लिपबोर्ड पर कॉपी" + +[settings.automations.action.save_to_location] +label = "किसी जगह पर सेव करें" +noun = "फ़ोल्डर" +short = "फ़ोल्डर में सेव" + +[settings.automations.action.export] +label = "प्रोफ़ाइल के साथ एक्सपोर्ट करें" +noun = "एक्सपोर्ट" +short = "एक्सपोर्ट" + +[settings.automations.action.upload] +label = "अपलोड + लिंक कॉपी करें" +noun = "अपलोड" +short = "अपलोड और लिंक कॉपी" + +[settings.automations.action.reveal] +label = "फ़ाइल मैनेजर में दिखाएँ" +noun = "दिखाएँ" +short = "फ़ाइल मैनेजर में दिखाएँ" + +[settings.automations.action.open_file] +label = "फ़ाइल खोलें" +noun = "खोलें" +short = "फ़ाइल खोलें" + +[settings.automations.action.ocr] +label = "टेक्स्ट पहचानकर (OCR) क्लिपबोर्ड पर" +noun = "टेक्स्ट" +short = "टेक्स्ट कॉपी (OCR)" + +[settings.automations.action.notify] +label = "नोटिफ़िकेशन दिखाएँ" +noun = "सूचना" +short = "सूचित करें" + +[settings.automations.action.open_editor] +label = "एडिटर खोलें" +noun = "एडिटर" +short = "एडिटर खोलें" + +[settings.automations.action.skip_editor] +label = "एडिटर छोड़ें (हेडलेस)" +noun = "एडिटर छोड़ें" +short = "एडिटर छोड़ें" + +[settings.automations.action.apply_preset] +label = "एडिटर प्रीसेट लागू करें" +noun = "प्रीसेट" +short = "प्रीसेट लागू करें" + +[settings.automations.action.run_command] +label = "कमांड चलाएँ" +noun = "कमांड" +short = "कमांड चलाएँ" + +[settings.automations.action.webhook] +label = "वेबहुक भेजें" +noun = "वेबहुक" +short = "वेबहुक भेजें" + +[settings.automations.action.delete_local_files] +label = "लोकल फ़ाइलें डिलीट करें" +noun = "डिलीट" +short = "लोकल फ़ाइलें डिलीट करें" + +[settings.automations.condition] +capture_target = "कैप्चर टारगेट है" +duration_at_least = "अवधि कम से कम (सेकंड)" +duration_at_most = "अवधि ज़्यादा से ज़्यादा (सेकंड)" +organization = "संगठन है" +recording_mode = "रिकॉर्डिंग मोड है" +window_title = "विंडो के शीर्षक में शामिल है" + +[settings.automations.template.copy_screenshot] +description = "स्क्रीनशॉट लें और वह तुरंत पेस्ट करने के लिए तैयार है।" +name = "नए स्क्रीनशॉट अपने आप क्लिपबोर्ड पर कॉपी करें" + +[settings.automations.template.ocr_screenshot] +description = "Cap आपके स्क्रीनशॉट का टेक्स्ट पढ़कर आपके लिए कॉपी कर देता है।" +name = "स्क्रीनशॉट से टेक्स्ट निकालें" + +[settings.automations.template.save_screenshot] +description = "हर नया स्क्रीनशॉट सीधे आपके चुने फ़ोल्डर में भेजें।" +name = "स्क्रीनशॉट एक फ़ोल्डर में रखें" + +[settings.automations.template.reveal_screenshot] +description = "स्क्रीनशॉट लेते ही उसे Finder में खोलें।" +name = "हर नए स्क्रीनशॉट पर जाएँ" + +[settings.automations.template.export_studio] +description = "Studio रिकॉर्डिंग खत्म होते ही MP4 रेंडर करें।" +name = "रिकॉर्डिंग खत्म होते ही अपने आप एक्सपोर्ट करें" + +[settings.automations.template.upload_share] +description = "रिकॉर्डिंग खत्म करें और लिंक आपके क्लिपबोर्ड पर तैयार मिलेगा।" +name = "अपलोड करें और शेयर लिंक पाएँ" + +[settings.automations.template.notify_upload] +body = "आपकी रिकॉर्डिंग शेयर करने के लिए तैयार है।" +description = "रिकॉर्डिंग शेयर करने लायक होते ही डेस्कटॉप पर हल्का-सा संकेत पाएँ।" +name = "अपलोड तैयार होने पर मुझे बताएँ" + +[settings.automations.template.webhook_share] +description = "शेयर लिंक Slack, Discord या अपने वेबहुक पर भेजें।" +name = "कुछ शेयर करने पर Slack को बताएँ" + [main] delete_recording_confirm = "क्या आप वाकई यह रिकॉर्डिंग हटाना चाहते हैं?" diff --git a/apps/desktop-gpui/locales/ja.toml b/apps/desktop-gpui/locales/ja.toml index 44dd3f99308..c4b801794d6 100644 --- a/apps/desktop-gpui/locales/ja.toml +++ b/apps/desktop-gpui/locales/ja.toml @@ -239,6 +239,697 @@ title = "プライバシー" [settings.general.privacy.telemetry] description = "Capは信頼性の向上と不具合の修正のために匿名のテレメトリを使用します。録画の内容、ウィンドウのタイトル、ファイルパス、個人情報を収集することはありません。" label = "匿名のテレメトリを共有" +[settings.prompt] +ok = "OK" + +[settings.sidebar] +account = "アカウント" +cancel_sign_in = "サインインをキャンセル" +check_for_updates = "アップデートを確認" +click_to_sign_in = "クリックしてサインイン" +downloading = "v{version}をダウンロード中…" +downloading_progress = "v{version}をダウンロード中… {percent}%" +installing = "v{version}をインストール中…" +previous_versions = "以前のバージョンを表示" +restart_to_update = "再起動してv{version}にアップデート" +sign_in = "サインイン" +sign_out = "サインアウト" +signed_in = "サインイン済み" + +[settings.recordings] +corrupted_body = "録画に失敗したため、このファイルはエディタで問題が起きる可能性があります。ファイルを復元できない場合は、サポートにお問い合わせください。" +corrupted_title = "録画が破損している可能性があります" +delete = "削除" +description = "録画を管理し、操作を行います。" +edit = "編集" +empty_library = "録画が見つかりません" +failed = "録画に失敗しました" +import = "読み込む" +in_progress = "録画中" +load_more = "さらに読み込む" +open_bundle = "録画バンドルを開く" +open_link = "リンクを開く" +retry_upload = "アップロードを再試行" +show_all = "すべて表示" +title = "録画" + +[settings.recordings.empty] +all = "録画はありません" +all_matching = "一致する録画はありません" +instant = "Instant録画はありません" +instant_matching = "一致するInstant録画はありません" +studio = "Studio録画はありません" +studio_matching = "一致するStudio録画はありません" + +[settings.screenshots] +copy = "クリップボードにコピー" +delete = "削除" +description = "スクリーンショットを管理し、操作を行います。" +empty = "スクリーンショットはありません" +empty_library = "スクリーンショットが見つかりません" +empty_matching = "一致するスクリーンショットはありません" +import = "画像を読み込む" +load_more = "さらに読み込む" +open_folder = "フォルダを開く" +save_as = "名前を付けて保存" +title = "スクリーンショット" + +[settings.quality] +save_failed = "録画設定を保存できませんでした。もう一度お試しください。" + +[settings.quality.studio] +balanced = "バランス" +balanced_description = "適度なファイルサイズで鮮明なディテール。ほとんどの録画に最適です。" +compatibility = "小さいファイル" +compatibility_description = "ディスク容量を節約します。細部がやわらかくなることがあり、特にカメラ映像で目立ちます。" +description = "編集用にコンピュータへ保存されます。書き出しサイズは後でエディタで選びます。" +recommended = "推奨" +title = "Studio録画" +ultra = "最大限のディテール" +ultra_description = "本格的な編集のためにディテールを最大限残します。ディスク容量を最も多く使います。" + +[settings.quality.instant] +description = "録画しながらアップロードされるため、停止した瞬間にリンクが使えます。最高画質が必要な場合はStudioモードを使ってください。画面が許す最高画質で録画し、最大4Kで書き出せます。" +free_plan = "無料プランでは720pです。Cap Proでは最大4Kで録画できます。" +max_resolution = "最大解像度" +res_1080p = "実用的なアップロードサイズで文字がくっきり。" +res_1440p = "大きな高解像度画面向けのより細かいディテール。" +res_4k = "最も細かいディテール。高速な回線向きです。" +res_720p = "アップロードが最も小さく、リンクが最も早く使えます。" +title = "Instant録画" + +[settings.quality.sharing] +auto_open = "共有リンクを自動で開く" +auto_open_description = "アップロードが完了したらブラウザでリンクを開きます。" +title = "共有" + +[settings.options.studio_quality] +balanced = "バランス" +compatibility = "互換性重視" +ultra = "最高" + +[settings.options.main_window_start] +close = "閉じる" +minimise = "最小化" + +[settings.options.post_studio] +do_nothing = "何もしない" +open_editor = "エディタを開く" +show_cap = "Capを表示" + +[settings.options.post_screenshot] +copy = "クリップボードにコピー" +copy_and_open_editor = "コピーしてエディタを開く" +do_nothing = "何もしない" +open_editor = "エディタを開く" + +[settings.options.post_deletion] +do_nothing = "何もしない" +reopen = "録画ウィンドウを再表示" + +[settings.options.preview_quality] +full = "フル" +half = "1/2" +quarter = "1/4" + +[settings.shortcuts] +blocked_while_recording = "録画が停止キーを使用している間はショートカットを変更できません。先に録画を停止してください。" +needs_modifier_macos = "入力の邪魔にならないよう、⌘、⌃、⌥のいずれかを含めてください。" +needs_modifier_other = "入力の邪魔にならないよう、Ctrl、Alt、Winのいずれかを含めてください。" +record_shortcut = "ショートカットを記録" +recording_description = "Capを開かずに録画を開始できます。" +recording_hint = "使いたいキーを押してください。Escでキャンセル、Deleteで削除します。" +recording_title = "録画" +saved = "保存しました。どのアプリでも{keys}を押すと{effect}。" +saved_moved = "保存しました。どのアプリでも{keys}を押すと{effect}。「{shortcut}」からは削除しました。" +screenshots_description = "ショートカットはどのアプリからでも使えます。クリックすると変更できます。" +screenshots_title = "スクリーンショット" +type_shortcut = "ショートカットを入力…" +unavailable = "このショートカットは別のアプリまたはシステムがすでに使用しています。クリックして別のものを選んでください。" +unsupported_key = "このキーはショートカットに使えません。文字、数字、ファンクションキーを試してください。" +while_recording_title = "録画中" + +[settings.shortcuts.after_screenshot] +hint = "ショートカットでもCapからでも、すべてのスクリーンショットに適用" +title = "スクリーンショットの後" + +[settings.shortcuts.after_studio] +hint = "停止したとき" +title = "Studio録画の後" + +[settings.shortcuts.after_instant] +hint = "共有リンクは常にコピーされます" +title = "Instant録画の後" + +[settings.shortcuts.outcome] +instant_copy_link = "リンクのコピーのみ" +instant_copy_link_hint = "何も開きません" +instant_open_link = "リンクを開く" +instant_open_link_hint = "ブラウザで" +saved_to_library = "ライブラリに保存" +screenshot_copy = "コピー" +screenshot_copy_and_edit = "コピーして編集" +screenshot_copy_and_edit_hint = "両方を同時に" +screenshot_copy_hint = "どこにでも貼り付け" +screenshot_do_nothing = "何もしない" +screenshot_open_editor = "エディタを開く" +screenshot_open_editor_hint = "先に注釈を付ける" +studio_do_nothing = "何もしない" +studio_open_editor = "エディタを開く" +studio_open_editor_hint = "すぐに編集" +studio_show_cap = "Capを表示" +studio_show_cap_hint = "レコーダーに戻る" + +[settings.shortcuts.label] +cycle_mode = "録画モードを切り替え" +fallback = "ショートカット" +open_picker = "録画ピッカーを開く" +pause = "一時停止または再開" +record_area = "範囲を録画" +record_display = "ディスプレイを録画" +record_window = "ウィンドウを録画" +restart = "録画をやり直す" +screenshot_area = "範囲のスクリーンショット" +screenshot_display = "ディスプレイのスクリーンショット" +screenshot_window = "ウィンドウのスクリーンショット" +start_instant = "Instant録画を開始" +start_studio = "Studio録画を開始" +stop = "録画を停止" + +[settings.shortcuts.effect] +cycle_mode = "Studio、Instant、スクリーンショットの各モードを切り替えます" +instant_copy = "選択した画面またはウィンドウをInstantモードで録画し、停止すると共有リンクをコピーします" +instant_copy_and_open = "選択した画面またはウィンドウをInstantモードで録画し、停止すると共有リンクをコピーして開きます" +open_picker = "Capを開いて録画する対象を選びます" +pause = "現在の録画を一時停止または再開します" +picker_area_instant = "範囲を選択してInstantモードで録画します" +picker_area_screenshot = "範囲を選択してスクリーンショットを撮ります" +picker_area_studio = "範囲を選択してStudioモードで録画します" +picker_display_instant = "ディスプレイを選んでInstantモードで録画します" +picker_display_screenshot = "ディスプレイを選んでスクリーンショットを撮ります" +picker_display_studio = "ディスプレイを選んでStudioモードで録画します" +picker_window_instant = "ウィンドウを選んでInstantモードで録画します" +picker_window_screenshot = "ウィンドウを選んでスクリーンショットを撮ります" +picker_window_studio = "ウィンドウを選んでStudioモードで録画します" +restart = "現在のテイクを破棄してやり直します" +screenshot_area_copy = "範囲を選択してクリップボードにコピーします" +screenshot_area_copy_and_open_editor = "範囲を選択してコピーし、エディタを開きます" +screenshot_area_open_editor = "範囲を選択してエディタで開きます" +screenshot_area_save = "範囲を選択し、何も開かずにライブラリに保存します" +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 = "ポインタの下のウィンドウをキャプチャし、何も開かずにライブラリに保存します" +stop = "現在の録画を停止します" +studio = "選択した画面またはウィンドウをStudioモードで録画します" +studio_open_editor = "選択した画面またはウィンドウをStudioモードで録画し、停止するとエディタを開きます" + +[settings.shortcuts.sentence] +cycle_mode = "Studio、Instant、スクリーンショットの各モードを切り替えます。" +instant_copy = "選択した画面またはウィンドウをInstantモードで録画し、停止すると共有リンクをコピーします。" +instant_copy_and_open = "選択した画面またはウィンドウをInstantモードで録画し、停止すると共有リンクをコピーして開きます。" +open_picker = "Capを開いて録画する対象を選びます。" +pause = "現在の録画を一時停止または再開します。" +picker_area_instant = "範囲を選択してInstantモードで録画します。" +picker_area_screenshot = "範囲を選択してスクリーンショットを撮ります。" +picker_area_studio = "範囲を選択してStudioモードで録画します。" +picker_display_instant = "ディスプレイを選んでInstantモードで録画します。" +picker_display_screenshot = "ディスプレイを選んでスクリーンショットを撮ります。" +picker_display_studio = "ディスプレイを選んでStudioモードで録画します。" +picker_window_instant = "ウィンドウを選んでInstantモードで録画します。" +picker_window_screenshot = "ウィンドウを選んでスクリーンショットを撮ります。" +picker_window_studio = "ウィンドウを選んでStudioモードで録画します。" +restart = "現在のテイクを破棄してやり直します。" +screenshot_area_copy = "範囲を選択してクリップボードにコピーします。" +screenshot_area_copy_and_open_editor = "範囲を選択してコピーし、エディタを開きます。" +screenshot_area_open_editor = "範囲を選択してエディタで開きます。" +screenshot_area_save = "範囲を選択し、何も開かずにライブラリに保存します。" +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 = "ポインタの下のウィンドウをキャプチャし、何も開かずにライブラリに保存します。" +stop = "現在の録画を停止します。" +studio = "選択した画面またはウィンドウをStudioモードで録画します。" +studio_open_editor = "選択した画面またはウィンドウをStudioモードで録画し、停止するとエディタを開きます。" + +[settings.cli] +command = "コマンド" +copy = "コピー" +description = "ターミナル、エージェント、スクリプト、ローカル自動化で使えるCapコマンドをインストールします。" +install = "CLIをインストール" +installed = "インストール済み" +installing = "インストール中..." +installs_command = "デスクトップアプリは、同梱のCLIを指すローカルの\u00A0{command}\u00A0コマンドをインストールします。" +load_failed = "CLIの状態を読み込めませんでした: {error}" +not_installed = "未インストール" +path_configured = "capをPATHに追加しました。使うにはターミナルを再起動するか、今すぐ次を実行してください:" +path_missing = "新しいターミナルでcapを使うには、{path}をPATHに追加してください。" +remove = "削除" +removing = "削除中..." +repair = "修復" +repairing = "修復中..." +retry = "再試行" +target = "参照先" +title = "コマンドライン" + +[settings.transcription] +active_title = "有効なヒント" +applied_note = "これらのヒントは、エディタで字幕を生成するときに適用されます。" +clear = "クリア" +count = { other = "{count}件" } +description = "字幕の生成時に考慮してほしい名前、表記、ドメイン、大文字小文字の使い分けを追加します。" +placeholder = "用語を追加" +saved = "保存しました" +saving = "保存中..." +terms_description = "誤字や書式の誤りを減らすため、用語は1つずつ追加してください。" +terms_title = "登録した用語" +title = "文字起こし" + +[settings.experimental] +developer = "開発者" +developer_description = "開発ビルドでのみ表示されます。" +muxer = "別プロセスのマルチプレクサ" +muxer_description = "フラグメント化MP4のマルチプレクサを独立したサブプロセスで実行し、マルチプレクサがクラッシュしても録画が失われないようにします。同梱のcap-muxerバイナリが必要です。" +native_app = "ネイティブアプリを使う" +native_app_description = "オフにするとこのアプリを閉じ、開発用ハーネスで従来のTauriアプリを開き直します。録画と設定は共有されます。" +reliability = "信頼性" + +[settings.experimental.switch] +busy = "従来のアプリに切り替える前に、録画、書き出し、アップロード、読み込み、文字起こしの作業を完了してください。" +check_failed = "従来のCapアプリが開いたかどうか確認できませんでした: {error}" +data_stays = "録画と設定はそのまま残ります。" +failed = "従来のCapアプリを開けませんでした。" +not_found = "切り替え先の従来のCapアプリが見つかりませんでした。" +open_failed = "従来のCapアプリを開けませんでした: {error}" +opening = "従来のCapアプリを開いています" +opening_detail = "従来のCapアプリの準備ができると、このウィンドウは閉じます。" +save_failed = "アプリの設定を保存できませんでした。Capは開いたままです。" +switching = "従来のCapアプリに戻しています。" +timed_out = "従来のアプリが開きません。開発用ターミナルでビルドエラーを確認してから、もう一度お試しください。" + +[settings.feedback] +app_version = "アプリのバージョン" +capture_support = "キャプチャ対応" +capture_supported = "画面キャプチャ: 対応" +capture_unsupported = "画面キャプチャ: 非対応" +community_description = "質問やアイデアの共有、ちょっとした雑談に。CapのDiscordコミュニティに参加しましょう。" +community_title = "コミュニティに参加" +debug_description = "ログをアップロードして、Capの問題の診断にご協力ください。個人情報は含まれません。" +debug_title = "デバッグ情報" +description = "フィードバックやバグ報告を送って、Capの改善にご協力ください。すぐに対応します。" +join_discord = "Discordに参加" +operating_system = "OS" +placeholder = "Capについてのご意見をお聞かせください..." +submit = "フィードバックを送信" +submit_failed = "フィードバックを送信できませんでした" +submitting = "送信中..." +system_loading = "システム情報を読み込み中..." +system_title = "システム情報" +thanks = "フィードバックをありがとうございます。" +title = "フィードバック" +upload_interrupted = "アップロードが中断されました" +upload_logs = "ログをアップロード" +upload_sent = "送信しました。ありがとうございます。問題の特定に役立てます。" +uploading = "アップロード中..." + +[settings.feedback.diagnostic] +confirm_body = "Capは画面全体に点滅パターンを表示し、処理ごとに約{seconds}秒間大きなビープ音を鳴らします。ヘッドフォンを外し、音が聞こえる音量にして、終わるまでコンピュータに触れないでください。" +confirm_title = "診断を実行しますか?" +description = "点滅とビープ音の短いテストパターンを録画し、映像と音声のずれを測定して、ディスプレイ、カメラ、マイク、ディスク容量、録画設定の状態を記録します。送信を選ぶまで、何も送信されません。" +mic_description = "マイクも録音し、ビープ音を正しいタイミングで拾えているか確認します。マイクに実際に聞こえるスピーカーが必要です。" +mic_label = "マイクをテスト" +mode_description = "テストする処理を選びます。両方を選ぶとStudioを実行してからInstantを実行します。" +mode_label = "録画モード" +no_cli = "このビルドの横にCapコマンドラインツールが見つからないため、レポートには環境情報のみが含まれます。" +no_cli_sync = "このビルドの横にCapコマンドラインツールが見つからないため、同期テストを実行できませんでした。" +run = "診断を実行" +run_again = "もう一度実行" +send = "Capに送信" +sending = "送信中..." +show_file = "ファイルを表示" +stop_recording_first = "診断を実行する前に、現在の録画を停止してください。" +title = "診断レポート" +try_again = "再試行" +warning_body = "全画面のウィンドウが白黒に点滅し、スピーカーから1kHzのビープ音が鳴ります。Capはそれを録画し、映像と音声のずれを測定します。実行中はコンピュータに触れないでください。両方のモードで数分かかります。" +warning_title = "テスト中は画面全体が使われ、音が鳴ります。" + +[settings.feedback.diagnostic.verdict] +environment_only = "環境情報のみ" +fail = "不合格" +inconclusive = "判定不能" +pass = "合格" +warn = "警告" + +[settings.sign_in_required] +body = "続けるにはサインインしてください。または、hello@cap.soにメールするか、cap.link/discordからDiscordに参加してください" +title = "サインインが必要です" + +[settings.changelog] +fetch_failed = "変更履歴を取得できませんでした" +loading = "変更履歴を読み込み中..." +new = "NEW" +version = "バージョン{version} - {date}" + +[settings.license] +activate = "ライセンスを有効化" +activating = "有効化中..." +active_title = "デスクトップライセンス" +checking = "確認中…" +deactivate = "ライセンスを無効化" +description = "Capのプランとデスクトップライセンスをまとめて確認できます。" +desktop_description = "デスクトップライセンスで商用の録画と編集ができます。Cap Proではさらにクラウド共有とコラボレーション機能が使えます。" +desktop_name = "デスクトップライセンス" +expires = "有効期限" +explore_description = "現在の料金と含まれる内容をウェブサイトで比較できます。" +explore_desktop = "デスクトップライセンス · デスクトップのレコーダーとエディタを商用利用できます。" +explore_pro = "Cap Pro · デスクトップライセンスに加え、クラウド共有、AI機能、コラボレーション。" +explore_title = "プランを見る" +free_description = "個人利用ならローカルで録画と編集ができます。商用利用やより多くのクラウド機能には有料プランを選んでください。" +have_key = "ライセンスキーをお持ちですか?" +key = "ライセンスキー" +no_instance = "インスタンスIDが見つかりません" +opens_pricing = "ブラウザでcap.so/pricingを開きます。" +placeholder = "ライセンスキー" +plan_title = "ご利用中のプラン" +pro_description = "お使いのアカウントには、クラウド共有、Pro機能、商用利用できるデスクトップライセンスが含まれます。" +refresh = "プランを更新" +refresh_failed = "プランを更新できませんでした。もう一度お試しください。" +save_failed = "ライセンスを保存できませんでした" +sign_in_hint = "すでにCap Proをお持ちですか?サイドバーからアカウントでサインインしてください。" +title = "プランとライセンス" +view_pricing = "プランと料金を見る ↗" + +[settings.integrations] +configure = "設定" +description = "連携を設定して、Capの機能を拡張し、外部サービスと接続します。" +google_drive_description = "Google Driveを接続すると、新しい共有リンクの動画がアップロードされます。Capは新しい動画をDrive内の非公開のCapフォルダに保存し、通常のアクセス確認の後にCap経由で配信し続けます。" +managed = "組織が管理しています" +managed_by = "組織が管理しています: {organization}" +request_timed_out = "リクエストがタイムアウトしました" +s3_description = "独自のS3バケットを接続して、データの保存を完全に管理できます。新しい共有リンクのアップロードはすべて設定したS3バケットに自動でアップロードされ、コンテンツの所有権と管理を完全に保てます。データ主権や独自の保存ポリシーが必要な組織に最適です。" +s3_name = "S3設定" +title = "連携" +upgrade = "Proにアップグレード" + +[settings.integrations.s3] +access_key = "アクセスキーID" +bucket = "バケット名" +configuration = "設定" +delete_failed = "S3設定を削除できませんでした" +deleted = "S3設定を削除しました" +endpoint = "エンドポイント" +fetch_failed = "S3設定を取得できませんでした" +guide = "ストレージ設定ガイド" +guide_intro = "ストレージバケットの設定とCapへの接続は10分以内で完了します。始めるには\u00A0{guide}\u00A0をご覧ください。" +other_provider = "その他のS3互換" +provider = "ストレージプロバイダ" +region = "リージョン" +remove = "設定を削除" +removing = "削除中..." +save_failed = "S3設定を保存できませんでした" +saved = "S3設定を保存しました" +saving = "保存中..." +secret_key = "シークレットアクセスキー" +test = "接続をテスト" +test_failed = "S3の接続テストに失敗しました。設定とネットワーク接続を確認してください。" +test_ok = "S3設定のテストに成功しました。接続は正常です。" +test_timeout = "接続テストが5秒でタイムアウトしました。エンドポイントのURLとネットワーク接続を確認してください。" +testing = "テスト中..." + +[settings.integrations.gdrive] +active = "使用中" +active_for_uploads = "新しいアップロードに使用中" +cached = "{time}時点のキャッシュ" +connect = "Google Driveを接続" +connect_failed = "Google Driveの接続を開始できませんでした" +connected_inactive = "接続済み(未使用)" +connection_description = "Google Driveは新しいアップロードをDrive内の非公開のCapフォルダに保存します。既存のCapホストの動画とS3の動画は、現在の保存先を使い続けます。" +connection_title = "接続" +disconnect = "接続を解除" +disconnect_failed = "Google Driveの接続を解除できませんでした" +disconnected = "Google Driveの接続を解除しました" +drive_files = "Driveのファイル" +finish_in_browser = "ブラウザでGoogle Driveの接続を完了してから、ここに戻って更新してください。" +not_connected = "未接続" +opening = "開いています..." +refresh = "更新" +refreshing = "更新中..." +remaining = "残り" +set_active_failed = "使用するストレージプロバイダを更新できませんでした" +storage = "ストレージ" +test = "テスト" +test_failed = "Google Driveの接続テストに失敗しました" +testing = "テスト中..." +trash = "ゴミ箱" +updated = "{time}に更新" +use_drive = "Google Driveを使う" +use_s3 = "S3を使う" +used = "{usage}使用" +used_of = "{limit}中{usage}使用" +waiting = "待機中..." +working = "Google Driveの接続は正常です" +working_for = "{email}のGoogle Drive接続は正常です" + +[settings.automations] +action_no_effect = "このアクションは選択したトリガーでは効果がありません。" +add_action = "アクションを追加" +add_condition = "条件を追加" +auto_name_empty = "{trigger}のオートメーション" +browse = "参照" +check_compatibility = "互換性を確認" +compatibility_checked = "互換性を確認しました: このオートメーションはまだCapでは実行されません。保存したルールはCap CLIで引き続き使えます。アクションは実行されていません。" +condition_never_matches = "この条件は選択したトリガーでは一致しません。" +copy_link = "リンクをクリップボードにコピー" +dangerous = "Cap CLIでは、このオートメーションはあなたの権限でコマンドを実行したりネットワークリクエストを送信したりします。信頼できる値だけを使ってください。" +delete = "削除" +description = "ルールはCap CLIと共有されます。Capはこれらのルールを保存しますが、オートメーションはまだ実行しません。" +empty_description = "下のテンプレートを選べばワンクリックで始められます。一から自分で作ることもできます。" +empty_title = "オートメーションはまだありません" +enabled = "有効" +match_all = "すべて一致" +match_any = "いずれか一致" +name = "名前" +new = "新しいオートメーション" +no_presets = "プリセットがまだありません。先にエディタで作成してください。" +only_if = "実行する条件" +open_in_browser = "ブラウザで開く" +retry_save = "保存を再試行" +run_through_shell = "シェル経由で実行" +save_failed = "オートメーションを保存できませんでした。変更は保存されていません。" +start_from_scratch = "一から作成" +summary_empty = "{trigger} → アクションはまだありません" +templates_description = "ワンクリックで既製のオートメーションを追加できます。後から自由に調整できます。" +templates_title = "テンプレート" +then = "実行する内容" +title = "オートメーション" +when = "このとき" + +[settings.automations.status] +disabled_saved = "無効 · 保存済み" +disabled_unsaved = "無効 · 未保存" +enabled_saved = "有効 · 保存済み" +enabled_unsaved = "有効 · 未保存" + +[settings.automations.field] +arguments = "引数(スペース区切り)" +body = "本文" +body_template = "本文テンプレート(任意)" +compression = "圧縮" +destination = "保存先フォルダ(任意、空欄 = プロジェクトフォルダ)" +filename = "ファイル名テンプレート(任意)" +folder = "フォルダ" +format = "形式" +frame_rate = "フレームレート" +method = "メソッド" +organization = "組織ID(任意)" +preset = "プリセット" +program = "プログラム" +resolution = "解像度" +source = "ソース" +title = "タイトル" +url = "URL" + +[settings.automations.placeholder] +organization_id = "組織ID" +project_folder = "プロジェクトフォルダ" +window_title = "例: Slack" + +[settings.automations.clipboard] +original = "元のキャプチャ" +rendered = "編集済み / レンダリング済み" + +[settings.automations.target] +area = "範囲" +display = "ディスプレイ" +window = "ウィンドウ" + +[settings.automations.compression] +maximum = "最大" +potato = "最小" +social = "ソーシャル" +web = "Web" + +[settings.automations.trigger.screenshot_taken] +label = "スクリーンショットを撮ったとき" +noun = "スクリーンショット" +phrase = "スクリーンショット撮影" +runs = "スクリーンショットを撮るたびに実行されます。" + +[settings.automations.trigger.studio_finished] +label = "Studio録画が終了したとき" +noun = "Studio録画" +phrase = "Studio録画の終了" +runs = "Studio録画が終了するたびに実行されます。" + +[settings.automations.trigger.instant_finished] +label = "Instant録画が終了したとき" +noun = "Instant録画" +phrase = "Instant録画の終了" +runs = "Instant録画が終了するたびに実行されます。" + +[settings.automations.trigger.recording_started] +label = "録画を開始したとき" +noun = "録画開始" +phrase = "録画の開始" +runs = "録画を開始するたびに実行されます。" + +[settings.automations.trigger.upload_completed] +label = "アップロードが完了したとき" +noun = "アップロード" +phrase = "アップロードの完了" +runs = "アップロードが完了するたびに実行されます。" + +[settings.automations.trigger.video_imported] +label = "動画を読み込んだとき" +noun = "読み込み" +phrase = "動画の読み込み" +runs = "動画を読み込むたびに実行されます。" + +[settings.automations.trigger.recording_deleted] +label = "録画を削除したとき" +noun = "削除" +phrase = "録画の削除" +runs = "録画を削除するたびに実行されます。" + +[settings.automations.action.copy_to_clipboard] +label = "クリップボードにコピー" +noun = "クリップボード" +short = "クリップボードにコピー" + +[settings.automations.action.save_to_location] +label = "指定の場所に保存" +noun = "フォルダ" +short = "フォルダに保存" + +[settings.automations.action.export] +label = "プロファイルで書き出し" +noun = "書き出し" +short = "書き出し" + +[settings.automations.action.upload] +label = "アップロードしてリンクをコピー" +noun = "アップロード" +short = "アップロードしてリンクをコピー" + +[settings.automations.action.reveal] +label = "ファイルマネージャで表示" +noun = "表示" +short = "ファイルマネージャで表示" + +[settings.automations.action.open_file] +label = "ファイルを開く" +noun = "開く" +short = "ファイルを開く" + +[settings.automations.action.ocr] +label = "文字認識(OCR)してクリップボードへ" +noun = "テキスト" +short = "文字をコピー(OCR)" + +[settings.automations.action.notify] +label = "通知を表示" +noun = "通知" +short = "通知" + +[settings.automations.action.open_editor] +label = "エディタを開く" +noun = "エディタ" +short = "エディタを開く" + +[settings.automations.action.skip_editor] +label = "エディタを省略(ヘッドレス)" +noun = "エディタ省略" +short = "エディタを省略" + +[settings.automations.action.apply_preset] +label = "エディタのプリセットを適用" +noun = "プリセット" +short = "プリセットを適用" + +[settings.automations.action.run_command] +label = "コマンドを実行" +noun = "コマンド" +short = "コマンドを実行" + +[settings.automations.action.webhook] +label = "Webhookを送信" +noun = "Webhook" +short = "Webhookを送信" + +[settings.automations.action.delete_local_files] +label = "ローカルファイルを削除" +noun = "削除" +short = "ローカルファイルを削除" + +[settings.automations.condition] +capture_target = "キャプチャ対象が次と一致" +duration_at_least = "長さが次以上(秒)" +duration_at_most = "長さが次以下(秒)" +organization = "組織が次と一致" +recording_mode = "録画モードが次と一致" +window_title = "ウィンドウタイトルに次を含む" + +[settings.automations.template.copy_screenshot] +description = "スクリーンショットを撮ったら、すぐに貼り付けられます。" +name = "新しいスクリーンショットを自動でクリップボードにコピー" + +[settings.automations.template.ocr_screenshot] +description = "Capがスクリーンショット内の文字を読み取り、コピーします。" +name = "スクリーンショットから文字を取り出す" + +[settings.automations.template.save_screenshot] +description = "新しいスクリーンショットを、選んだフォルダに直接送ります。" +name = "スクリーンショットをフォルダにまとめる" + +[settings.automations.template.reveal_screenshot] +description = "スクリーンショットを撮った瞬間にFinderで表示します。" +name = "新しいスクリーンショットをすぐに表示" + +[settings.automations.template.export_studio] +description = "Studio録画が終わった瞬間にMP4を書き出します。" +name = "録画終了時に自動で書き出す" + +[settings.automations.template.upload_share] +description = "録画を終えると、リンクがクリップボードに入っています。" +name = "アップロードして共有リンクを取得" + +[settings.automations.template.notify_upload] +body = "録画を共有できるようになりました。" +description = "録画を共有できるようになったら、デスクトップでそっとお知らせします。" +name = "アップロードの準備ができたら通知" + +[settings.automations.template.webhook_share] +description = "共有リンクをSlack、Discord、または独自のWebhookに送ります。" +name = "共有したらSlackに知らせる" + [main] delete_recording_confirm = "この録画を削除してもよろしいですか?" diff --git a/apps/desktop-gpui/locales/zh-CN.toml b/apps/desktop-gpui/locales/zh-CN.toml index faaf888950e..7eb32c274a6 100644 --- a/apps/desktop-gpui/locales/zh-CN.toml +++ b/apps/desktop-gpui/locales/zh-CN.toml @@ -239,6 +239,697 @@ title = "隐私" [settings.general.privacy.telemetry] description = "Cap 使用匿名遥测数据来提升稳定性并修复问题。我们绝不会收集录制内容、窗口标题、文件路径或任何个人信息。" label = "共享匿名遥测数据" +[settings.prompt] +ok = "好" + +[settings.sidebar] +account = "账户" +cancel_sign_in = "取消登录" +check_for_updates = "检查更新" +click_to_sign_in = "点击登录" +downloading = "正在下载 v{version}…" +downloading_progress = "正在下载 v{version}… {percent}%" +installing = "正在安装 v{version}…" +previous_versions = "查看以前的版本" +restart_to_update = "重新启动以更新到 v{version}" +sign_in = "登录" +sign_out = "退出登录" +signed_in = "已登录" + +[settings.recordings] +corrupted_body = "录制失败,因此此文件在编辑器中可能会出现问题!如果你在恢复文件时遇到问题,请联系支持团队!" +corrupted_title = "录制可能已损坏" +delete = "删除" +description = "管理你的录制并执行操作。" +edit = "编辑" +empty_library = "未找到录制" +failed = "录制失败" +import = "导入" +in_progress = "正在录制" +load_more = "加载更多" +open_bundle = "打开录制文件包" +open_link = "打开链接" +retry_upload = "重新上传" +show_all = "全部显示" +title = "录制" + +[settings.recordings.empty] +all = "没有录制" +all_matching = "没有匹配的录制" +instant = "没有 Instant 录制" +instant_matching = "没有匹配的 Instant 录制" +studio = "没有 Studio 录制" +studio_matching = "没有匹配的 Studio 录制" + +[settings.screenshots] +copy = "复制到剪贴板" +delete = "删除" +description = "管理你的截图并执行操作。" +empty = "没有截图" +empty_library = "未找到截图" +empty_matching = "没有匹配的截图" +import = "导入图片" +load_more = "加载更多" +open_folder = "打开文件夹" +save_as = "另存为" +title = "截图" + +[settings.quality] +save_failed = "无法保存你的录制设置,请重试。" + +[settings.quality.studio] +balanced = "均衡" +balanced_description = "细节清晰,文件大小适中。适合大多数录制。" +compatibility = "更小的文件" +compatibility_description = "节省磁盘空间。细节可能变得柔和,摄像头画面尤其明显。" +description = "保存到你的电脑上以便编辑。导出尺寸稍后在编辑器中选择。" +recommended = "推荐" +title = "Studio 录制" +ultra = "最高细节" +ultra_description = "保留最多细节,适合大量编辑。占用的磁盘空间最多。" + +[settings.quality.instant] +description = "录制时同步上传,停止录制时链接即可使用。如需最佳画质,请使用 Studio 模式:它会以屏幕支持的最高画质录制,并可导出最高 4K。" +free_plan = "免费版为 720p。Cap Pro 最高可录制 4K。" +max_resolution = "最高分辨率" +res_1080p = "文字清晰,上传体积适中。" +res_1440p = "为高分辨率大屏幕提供更多细节。" +res_4k = "细节最丰富。最适合高速网络。" +res_720p = "上传体积最小,链接最快可用。" +title = "Instant 录制" + +[settings.quality.sharing] +auto_open = "自动打开分享链接" +auto_open_description = "上传完成后在浏览器中打开链接。" +title = "分享" + +[settings.options.studio_quality] +balanced = "均衡" +compatibility = "兼容" +ultra = "超高" + +[settings.options.main_window_start] +close = "关闭" +minimise = "最小化" + +[settings.options.post_studio] +do_nothing = "不执行任何操作" +open_editor = "打开编辑器" +show_cap = "显示 Cap" + +[settings.options.post_screenshot] +copy = "复制到剪贴板" +copy_and_open_editor = "复制并打开编辑器" +do_nothing = "不执行任何操作" +open_editor = "打开编辑器" + +[settings.options.post_deletion] +do_nothing = "不执行任何操作" +reopen = "重新打开录制窗口" + +[settings.options.preview_quality] +full = "完整" +half = "一半" +quarter = "四分之一" + +[settings.shortcuts] +blocked_while_recording = "当前录制占用了停止键,无法更改快捷键。请先停止录制。" +needs_modifier_macos = "请包含 ⌘、⌃ 或 ⌥,以免快捷键干扰输入。" +needs_modifier_other = "请包含 Ctrl、Alt 或 Win,以免快捷键干扰输入。" +record_shortcut = "录制快捷键" +recording_description = "无需先打开 Cap 即可开始录制。" +recording_hint = "按下你想使用的按键。按 Esc 取消,按 Delete 移除。" +recording_title = "录制" +saved = "已保存。在任意应用中按 {keys} 即可{effect}。" +saved_moved = "已保存。在任意应用中按 {keys} 即可{effect}。已从“{shortcut}”中移除。" +screenshots_description = "快捷键在任何应用中都可用。点击即可更改。" +screenshots_title = "截图" +type_shortcut = "输入快捷键…" +unavailable = "其他应用或系统已在使用此快捷键。点击以选择其他快捷键。" +unsupported_key = "该按键不能用于快捷键。请尝试字母、数字或功能键。" +while_recording_title = "录制期间" + +[settings.shortcuts.after_screenshot] +hint = "适用于每张截图,无论来自快捷键还是 Cap" +title = "截图之后" + +[settings.shortcuts.after_studio] +hint = "停止录制时" +title = "Studio 录制之后" + +[settings.shortcuts.after_instant] +hint = "分享链接总会被复制" +title = "Instant 录制之后" + +[settings.shortcuts.outcome] +instant_copy_link = "仅复制链接" +instant_copy_link_hint = "不打开任何内容" +instant_open_link = "打开链接" +instant_open_link_hint = "在浏览器中" +saved_to_library = "保存到资料库" +screenshot_copy = "复制" +screenshot_copy_and_edit = "复制并编辑" +screenshot_copy_and_edit_hint = "两者兼顾" +screenshot_copy_hint = "随处粘贴" +screenshot_do_nothing = "不执行任何操作" +screenshot_open_editor = "打开编辑器" +screenshot_open_editor_hint = "先添加标注" +studio_do_nothing = "不执行任何操作" +studio_open_editor = "打开编辑器" +studio_open_editor_hint = "立即编辑" +studio_show_cap = "显示 Cap" +studio_show_cap_hint = "回到录制器" + +[settings.shortcuts.label] +cycle_mode = "切换录制模式" +fallback = "快捷键" +open_picker = "打开录制选择器" +pause = "暂停或继续" +record_area = "录制区域" +record_display = "录制屏幕" +record_window = "录制窗口" +restart = "重新开始录制" +screenshot_area = "截取区域" +screenshot_display = "截取屏幕" +screenshot_window = "截取窗口" +start_instant = "开始 Instant 录制" +start_studio = "开始 Studio 录制" +stop = "停止录制" + +[settings.shortcuts.effect] +cycle_mode = "在 Studio、Instant 和截图模式之间切换" +instant_copy = "以 Instant 模式录制所选屏幕或窗口,停止后复制分享链接" +instant_copy_and_open = "以 Instant 模式录制所选屏幕或窗口,停止后复制并打开分享链接" +open_picker = "打开 Cap 以选择要录制的内容" +pause = "暂停当前录制,或继续录制" +picker_area_instant = "选择一个区域,然后以 Instant 模式录制" +picker_area_screenshot = "选择一个区域,然后截图" +picker_area_studio = "选择一个区域,然后以 Studio 模式录制" +picker_display_instant = "选择一个屏幕,然后以 Instant 模式录制" +picker_display_screenshot = "选择一个屏幕,然后截图" +picker_display_studio = "选择一个屏幕,然后以 Studio 模式录制" +picker_window_instant = "选择一个窗口,然后以 Instant 模式录制" +picker_window_screenshot = "选择一个窗口,然后截图" +picker_window_studio = "选择一个窗口,然后以 Studio 模式录制" +restart = "丢弃当前这段并重新开始" +screenshot_area_copy = "选择一个区域并复制到剪贴板" +screenshot_area_copy_and_open_editor = "选择一个区域,复制后打开编辑器" +screenshot_area_open_editor = "选择一个区域并在编辑器中打开" +screenshot_area_save = "选择一个区域并保存到你的资料库,不打开任何内容" +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 = "截取指针下方的窗口并保存到你的资料库,不打开任何内容" +stop = "停止当前录制" +studio = "以 Studio 模式录制所选屏幕或窗口" +studio_open_editor = "以 Studio 模式录制所选屏幕或窗口,停止后打开编辑器" + +[settings.shortcuts.sentence] +cycle_mode = "在 Studio、Instant 和截图模式之间切换。" +instant_copy = "以 Instant 模式录制所选屏幕或窗口,停止后复制分享链接。" +instant_copy_and_open = "以 Instant 模式录制所选屏幕或窗口,停止后复制并打开分享链接。" +open_picker = "打开 Cap 以选择要录制的内容。" +pause = "暂停当前录制,或继续录制。" +picker_area_instant = "选择一个区域,然后以 Instant 模式录制。" +picker_area_screenshot = "选择一个区域,然后截图。" +picker_area_studio = "选择一个区域,然后以 Studio 模式录制。" +picker_display_instant = "选择一个屏幕,然后以 Instant 模式录制。" +picker_display_screenshot = "选择一个屏幕,然后截图。" +picker_display_studio = "选择一个屏幕,然后以 Studio 模式录制。" +picker_window_instant = "选择一个窗口,然后以 Instant 模式录制。" +picker_window_screenshot = "选择一个窗口,然后截图。" +picker_window_studio = "选择一个窗口,然后以 Studio 模式录制。" +restart = "丢弃当前这段并重新开始。" +screenshot_area_copy = "选择一个区域并复制到剪贴板。" +screenshot_area_copy_and_open_editor = "选择一个区域,复制后打开编辑器。" +screenshot_area_open_editor = "选择一个区域并在编辑器中打开。" +screenshot_area_save = "选择一个区域并保存到你的资料库,不打开任何内容。" +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 = "截取指针下方的窗口并保存到你的资料库,不打开任何内容。" +stop = "停止当前录制。" +studio = "以 Studio 模式录制所选屏幕或窗口。" +studio_open_editor = "以 Studio 模式录制所选屏幕或窗口,停止后打开编辑器。" + +[settings.cli] +command = "命令" +copy = "复制" +description = "安装 Cap 命令,供终端、智能体、脚本和本地自动化使用。" +install = "安装 CLI" +installed = "已安装" +installing = "正在安装..." +installs_command = "桌面应用会安装一个本地\u00A0{command}\u00A0命令,它指向内置的 CLI。" +load_failed = "无法加载 CLI 状态:{error}" +not_installed = "未安装" +path_configured = "已将 cap 添加到你的 PATH。重新启动终端即可使用,或现在运行:" +path_missing = "将 {path} 添加到你的 PATH,即可在新终端中使用 cap。" +remove = "移除" +removing = "正在移除..." +repair = "修复" +repairing = "正在修复..." +retry = "重试" +target = "目标" +title = "命令行" + +[settings.transcription] +active_title = "已启用的提示" +applied_note = "在编辑器中生成字幕时会应用这些提示。" +clear = "清除" +count = { other = "{count} 项" } +description = "添加生成字幕时需要注意的名称、拼写、域名和大小写偏好。" +placeholder = "添加词语" +saved = "已保存" +saving = "正在保存..." +terms_description = "每次添加一个词语,以减少错别字和格式错误。" +terms_title = "记住的词语" +title = "转写" + +[settings.experimental] +developer = "开发者" +developer_description = "仅在开发版本中显示。" +muxer = "进程外混流器" +muxer_description = "在独立的子进程中运行分段 MP4 混流器,混流器崩溃时不会影响你的录制。需要内置的 cap-muxer 程序。" +native_app = "使用原生应用" +native_app_description = "关闭后会退出此应用,并在开发环境中重新打开经典的 Tauri 应用。录制和设置是共享的。" +reliability = "可靠性" + +[settings.experimental.switch] +busy = "请先完成录制、导出、上传、导入或转写任务,再切换到经典版应用。" +check_failed = "无法确认经典版 Cap 应用是否已打开:{error}" +data_stays = "你的录制和设置会原样保留。" +failed = "无法打开经典版 Cap 应用。" +not_found = "找不到可以切换回去的经典版 Cap 应用。" +open_failed = "无法打开经典版 Cap 应用:{error}" +opening = "正在打开经典版 Cap 应用" +opening_detail = "经典版 Cap 应用准备就绪后,此窗口将关闭。" +save_failed = "无法保存你的应用偏好设置。Cap 仍保持打开。" +switching = "正在切换回经典版 Cap 应用。" +timed_out = "经典版应用尚未打开。请在开发终端中检查构建错误,然后重试。" + +[settings.feedback] +app_version = "应用版本" +capture_support = "捕获支持" +capture_supported = "屏幕捕获:支持" +capture_unsupported = "屏幕捕获:不支持" +community_description = "有问题、想分享想法,或者只是想聊聊?加入 Cap 的 Discord 社区吧。" +community_title = "加入社区" +debug_description = "上传你的日志,帮助我们诊断 Cap 的问题。不包含任何个人信息。" +debug_title = "调试信息" +description = "提交反馈或报告问题,帮助我们改进 Cap。我们会尽快处理。" +join_discord = "加入 Discord" +operating_system = "操作系统" +placeholder = "告诉我们你对 Cap 的看法..." +submit = "提交反馈" +submit_failed = "提交反馈失败" +submitting = "正在提交..." +system_loading = "正在加载系统信息..." +system_title = "系统信息" +thanks = "感谢你的反馈!" +title = "反馈" +upload_interrupted = "上传被中断" +upload_logs = "上传日志" +upload_sent = "已发送。谢谢,这能帮助我们找到问题。" +uploading = "正在上传..." + +[settings.feedback.diagnostic] +confirm_body = "Cap 将以闪烁图样占据你的屏幕,并在每条处理流程中播放约 {seconds} 秒的响亮提示音。请摘下耳机,保持音量可闻,并在完成前不要操作电脑。" +confirm_title = "运行诊断?" +description = "录制一段闪烁并发出提示音的简短测试图样,测量画面与声音的偏移程度,并记录你的显示器、摄像头、麦克风、磁盘空间和录制设置。在你选择发送之前,不会发送任何内容。" +mic_description = "同时录制你的麦克风,检查它是否及时听到提示音。需要麦克风能实际听到的扬声器。" +mic_label = "测试麦克风" +mode_description = "要测试的处理流程。“两者”会先运行 Studio 部分,再运行 Instant 部分。" +mode_label = "录制模式" +no_cli = "未在此版本旁找到 Cap 命令行工具,因此报告只会包含你的环境信息。" +no_cli_sync = "未在此版本旁找到 Cap 命令行工具,因此无法运行同步测试。" +run = "运行诊断" +run_again = "再次运行" +send = "发送给 Cap" +sending = "正在发送..." +show_file = "显示文件" +stop_recording_first = "请先停止当前录制,再运行诊断。" +title = "诊断报告" +try_again = "重试" +warning_body = "一个全屏窗口会黑白闪烁,同时扬声器播放 1kHz 提示音,Cap 会录制下来并测量画面与声音的偏移程度。运行期间请不要操作电脑。两种模式加起来大约需要几分钟。" +warning_title = "测试会占据你的屏幕并发出声音。" + +[settings.feedback.diagnostic.verdict] +environment_only = "仅环境信息" +fail = "未通过" +inconclusive = "无法确定" +pass = "通过" +warn = "警告" + +[settings.sign_in_required] +body = "请登录以继续。你也可以发送邮件至 hello@cap.so,或通过 cap.link/discord 加入我们的 Discord" +title = "需要登录" + +[settings.changelog] +fetch_failed = "获取更新日志失败" +loading = "正在加载更新日志..." +new = "新" +version = "版本 {version} - {date}" + +[settings.license] +activate = "激活许可证" +activating = "正在激活..." +active_title = "桌面许可证" +checking = "正在检查…" +deactivate = "停用许可证" +description = "你的 Cap 套餐和桌面许可证,集中在一处。" +desktop_description = "你的桌面许可证涵盖商用录制和编辑。Cap Pro 还提供云端分享和协作功能。" +desktop_name = "桌面许可证" +expires = "到期时间" +explore_description = "在我们的网站上比较当前价格和所含的全部内容。" +explore_desktop = "桌面许可证 · 可商用桌面录制器和编辑器。" +explore_pro = "Cap Pro · 桌面许可证,另含云端分享、AI 功能和协作。" +explore_title = "了解套餐" +free_description = "可在本地录制和编辑,仅限个人使用。如需商用或更多云端功能,请选择付费套餐。" +have_key = "已有许可证密钥?" +key = "许可证密钥" +no_instance = "未找到实例 ID" +opens_pricing = "在浏览器中打开 cap.so/pricing。" +placeholder = "许可证密钥" +plan_title = "你的套餐" +pro_description = "你的账户包含云端分享、Pro 功能以及可商用的桌面许可证。" +refresh = "刷新套餐" +refresh_failed = "无法刷新你的套餐,请重试。" +save_failed = "保存许可证失败" +sign_in_hint = "已有 Cap Pro?请在侧边栏中登录你的账户。" +title = "套餐与许可证" +view_pricing = "查看套餐和价格 ↗" + +[settings.integrations] +configure = "配置" +description = "配置集成以扩展 Cap 的功能,并连接第三方服务。" +google_drive_description = "连接 Google Drive,用于上传新的分享链接视频。Cap 会将新视频存放在你 Drive 中的私有 Cap 文件夹,并在常规访问检查后继续通过 Cap 提供。" +managed = "由你的组织管理" +managed_by = "由你的组织管理:{organization}" +request_timed_out = "请求超时" +s3_description = "连接你自己的 S3 存储桶,完全掌控数据存储。所有新的分享链接上传都会自动上传到你配置的 S3 存储桶,确保你对内容拥有完整的所有权和控制权。非常适合需要数据主权和自定义存储策略的组织。" +s3_name = "S3 配置" +title = "集成" +upgrade = "升级到 Pro" + +[settings.integrations.s3] +access_key = "访问密钥 ID" +bucket = "存储桶名称" +configuration = "配置" +delete_failed = "删除 S3 配置失败" +deleted = "S3 配置已成功删除" +endpoint = "端点" +fetch_failed = "获取 S3 配置失败" +guide = "存储配置指南" +guide_intro = "设置存储桶并将其连接到 Cap 应该不到 10 分钟。查看\u00A0{guide}\u00A0即可开始。" +other_provider = "其他兼容 S3 的服务" +provider = "存储服务商" +region = "区域" +remove = "移除配置" +removing = "正在移除..." +save_failed = "保存 S3 配置失败" +saved = "S3 配置已成功保存" +saving = "正在保存..." +secret_key = "秘密访问密钥" +test = "测试连接" +test_failed = "S3 连接测试失败。请检查你的配置和网络连接。" +test_ok = "S3 配置测试成功!连接正常。" +test_timeout = "连接测试在 5 秒后超时。请检查你的端点 URL 和网络连接。" +testing = "正在测试..." + +[settings.integrations.gdrive] +active = "已启用" +active_for_uploads = "已用于新的上传" +cached = "缓存于 {time}" +connect = "连接 Google Drive" +connect_failed = "无法开始连接 Google Drive" +connected_inactive = "已连接但未启用" +connection_description = "Google Drive 会将新的上传内容存放在你 Drive 中的私有 Cap 文件夹。已有的 Cap 托管视频和 S3 视频继续使用当前存储。" +connection_title = "连接" +disconnect = "断开连接" +disconnect_failed = "无法断开 Google Drive 连接" +disconnected = "已断开 Google Drive 连接" +drive_files = "Drive 文件" +finish_in_browser = "请在浏览器中完成 Google Drive 的连接,然后回到这里刷新。" +not_connected = "未连接" +opening = "正在打开..." +refresh = "刷新" +refreshing = "正在刷新..." +remaining = "剩余" +set_active_failed = "无法更新当前使用的存储服务商" +storage = "存储空间" +test = "测试" +test_failed = "Google Drive 连接测试失败" +testing = "正在测试..." +trash = "回收站" +updated = "更新于 {time}" +use_drive = "使用 Google Drive" +use_s3 = "使用 S3" +used = "已使用 {usage}" +used_of = "已使用 {usage},共 {limit}" +waiting = "正在等待..." +working = "Google Drive 连接正常" +working_for = "{email} 的 Google Drive 连接正常" + +[settings.automations] +action_no_effect = "对所选触发器而言,此操作没有效果。" +add_action = "添加操作" +add_condition = "添加条件" +auto_name_empty = "{trigger}自动化" +browse = "浏览" +check_compatibility = "检查兼容性" +compatibility_checked = "已检查兼容性:此自动化暂时还不会在 Cap 中运行。已保存的规则仍可供 Cap CLI 使用。未执行任何操作。" +condition_never_matches = "对所选触发器而言,此条件永远不会满足。" +copy_link = "将链接复制到剪贴板" +dangerous = "在 Cap CLI 中,此自动化会以你的权限运行命令或发送网络请求。请只使用你信任的值。" +delete = "删除" +description = "规则与 Cap CLI 共享。Cap 会保存这些规则,但暂时还不会运行自动化。" +empty_description = "选择下方的模板一键开始,或从头创建你自己的自动化。" +empty_title = "还没有自动化" +enabled = "已启用" +match_all = "全部满足" +match_any = "满足任一" +name = "名称" +new = "新建自动化" +no_presets = "还没有预设,请先在编辑器中创建一个。" +only_if = "仅在以下情况下运行" +open_in_browser = "在浏览器中打开" +retry_save = "重新保存" +run_through_shell = "通过 shell 运行" +save_failed = "保存自动化失败。你的更改尚未保存。" +start_from_scratch = "从头开始" +summary_empty = "{trigger} → 还没有操作" +templates_description = "一键添加现成的自动化,之后可随意调整。" +templates_title = "模板" +then = "然后执行" +title = "自动化" +when = "当发生以下情况时" + +[settings.automations.status] +disabled_saved = "已停用 · 已保存" +disabled_unsaved = "已停用 · 未保存" +enabled_saved = "已启用 · 已保存" +enabled_unsaved = "已启用 · 未保存" + +[settings.automations.field] +arguments = "参数(以空格分隔)" +body = "正文" +body_template = "正文模板(可选)" +compression = "压缩" +destination = "目标文件夹(可选,留空 = 项目文件夹)" +filename = "文件名模板(可选)" +folder = "文件夹" +format = "格式" +frame_rate = "帧率" +method = "方法" +organization = "组织 ID(可选)" +preset = "预设" +program = "程序" +resolution = "分辨率" +source = "来源" +title = "标题" +url = "URL" + +[settings.automations.placeholder] +organization_id = "组织 ID" +project_folder = "项目文件夹" +window_title = "例如 Slack" + +[settings.automations.clipboard] +original = "原始捕获内容" +rendered = "已编辑 / 已渲染" + +[settings.automations.target] +area = "区域" +display = "屏幕" +window = "窗口" + +[settings.automations.compression] +maximum = "最高" +potato = "极低画质" +social = "社交" +web = "网页" + +[settings.automations.trigger.screenshot_taken] +label = "截图完成时" +noun = "截图" +phrase = "截图完成" +runs = "每次截图都会运行。" + +[settings.automations.trigger.studio_finished] +label = "Studio 录制结束时" +noun = "Studio 录制" +phrase = "Studio 录制结束" +runs = "每次 Studio 录制结束时都会运行。" + +[settings.automations.trigger.instant_finished] +label = "Instant 录制结束时" +noun = "Instant 录制" +phrase = "Instant 录制结束" +runs = "每次 Instant 录制结束时都会运行。" + +[settings.automations.trigger.recording_started] +label = "录制开始时" +noun = "录制开始" +phrase = "录制开始" +runs = "每次开始录制时都会运行。" + +[settings.automations.trigger.upload_completed] +label = "上传完成时" +noun = "上传" +phrase = "上传完成" +runs = "每次上传完成时都会运行。" + +[settings.automations.trigger.video_imported] +label = "导入视频时" +noun = "导入" +phrase = "视频已导入" +runs = "每次导入视频时都会运行。" + +[settings.automations.trigger.recording_deleted] +label = "删除录制时" +noun = "删除" +phrase = "录制已删除" +runs = "每次删除录制时都会运行。" + +[settings.automations.action.copy_to_clipboard] +label = "复制到剪贴板" +noun = "剪贴板" +short = "复制到剪贴板" + +[settings.automations.action.save_to_location] +label = "保存到指定位置" +noun = "文件夹" +short = "保存到文件夹" + +[settings.automations.action.export] +label = "按配置导出" +noun = "导出" +short = "导出" + +[settings.automations.action.upload] +label = "上传并复制链接" +noun = "上传" +short = "上传并复制链接" + +[settings.automations.action.reveal] +label = "在文件管理器中显示" +noun = "显示" +short = "在文件管理器中显示" + +[settings.automations.action.open_file] +label = "打开文件" +noun = "打开" +short = "打开文件" + +[settings.automations.action.ocr] +label = "识别文字(OCR)到剪贴板" +noun = "文字" +short = "复制文字(OCR)" + +[settings.automations.action.notify] +label = "显示通知" +noun = "通知" +short = "通知" + +[settings.automations.action.open_editor] +label = "打开编辑器" +noun = "编辑器" +short = "打开编辑器" + +[settings.automations.action.skip_editor] +label = "跳过编辑器(无界面)" +noun = "跳过编辑器" +short = "跳过编辑器" + +[settings.automations.action.apply_preset] +label = "应用编辑器预设" +noun = "预设" +short = "应用预设" + +[settings.automations.action.run_command] +label = "运行命令" +noun = "命令" +short = "运行命令" + +[settings.automations.action.webhook] +label = "发送 Webhook" +noun = "Webhook" +short = "发送 Webhook" + +[settings.automations.action.delete_local_files] +label = "删除本地文件" +noun = "删除" +short = "删除本地文件" + +[settings.automations.condition] +capture_target = "捕获对象为" +duration_at_least = "时长至少(秒)" +duration_at_most = "时长至多(秒)" +organization = "组织为" +recording_mode = "录制模式为" +window_title = "窗口标题包含" + +[settings.automations.template.copy_screenshot] +description = "截图后即可直接粘贴。" +name = "自动将新截图复制到剪贴板" + +[settings.automations.template.ocr_screenshot] +description = "Cap 会识别截图中的文字并替你复制。" +name = "从截图中提取文字" + +[settings.automations.template.save_screenshot] +description = "将每张新截图直接保存到你选择的文件夹。" +name = "把截图收进文件夹" + +[settings.automations.template.reveal_screenshot] +description = "每张截图一拍下就在 Finder 中显示。" +name = "跳转到每张新截图" + +[settings.automations.template.export_studio] +description = "Studio 录制一结束就渲染出 MP4。" +name = "录制结束后自动导出" + +[settings.automations.template.upload_share] +description = "录制完成后,链接已在剪贴板中等你。" +name = "上传并获取分享链接" + +[settings.automations.template.notify_upload] +body = "你的录制已可以分享。" +description = "录制可以分享时,桌面会轻轻提醒你。" +name = "上传就绪时提醒我" + +[settings.automations.template.webhook_share] +description = "将分享链接发送到 Slack、Discord 或你自己的 Webhook。" +name = "分享内容时通知 Slack" + [main] delete_recording_confirm = "确定要删除这段录制吗?" diff --git a/apps/desktop-gpui/src/i18n/mod.rs b/apps/desktop-gpui/src/i18n/mod.rs index d7d487d1a50..39723798d47 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 settings_surface_tests; #[cfg(test)] mod surface_tests; #[cfg(test)] diff --git a/apps/desktop-gpui/src/i18n/settings_surface_tests.rs b/apps/desktop-gpui/src/i18n/settings_surface_tests.rs new file mode 100644 index 00000000000..acb8ebd1b58 --- /dev/null +++ b/apps/desktop-gpui/src/i18n/settings_surface_tests.rs @@ -0,0 +1,1607 @@ +use super::*; + +/// Every key the settings surfaces own. The shortcut effects are pinned +/// separately, against every combination of settings that builds them, in +/// `settings_pages`'s `shortcut_effects_match_the_sentences_they_replaced`. +const PREFIXES: &[&str] = &[ + "settings.automations.", + "settings.changelog.", + "settings.cli.", + "settings.experimental.", + "settings.feedback.", + "settings.integrations.", + "settings.license.", + "settings.options.", + "settings.prompt.", + "settings.quality.", + "settings.recordings.", + "settings.screenshots.", + "settings.shortcuts.", + "settings.sidebar.", + "settings.sign_in_required.", + "settings.transcription.", +]; + +const EFFECT_PREFIXES: &[&str] = &["settings.shortcuts.effect.", "settings.shortcuts.sentence."]; + +pub(super) fn is_settings_surface_key(key: &str) -> bool { + PREFIXES.iter().any(|prefix| key.starts_with(prefix)) +} + +const FORMATTED: &[&str] = &[ + "settings.sidebar.downloading", + "settings.sidebar.downloading_progress", + "settings.sidebar.installing", + "settings.sidebar.restart_to_update", + "settings.shortcuts.saved", + "settings.shortcuts.saved_moved", + "settings.cli.load_failed", + "settings.cli.installs_command", + "settings.cli.path_missing", + "settings.transcription.count", + "settings.experimental.switch.open_failed", + "settings.experimental.switch.check_failed", + "settings.feedback.diagnostic.confirm_body", + "settings.changelog.version", + "settings.integrations.managed_by", + "settings.integrations.s3.guide_intro", + "settings.integrations.gdrive.working_for", + "settings.integrations.gdrive.used_of", + "settings.integrations.gdrive.used", + "settings.integrations.gdrive.cached", + "settings.integrations.gdrive.updated", + "settings.automations.summary_empty", + "settings.automations.auto_name_empty", +]; + +#[test] +fn settings_surfaces_match_the_literals_they_replaced() { + let pairs: &[(&str, &str)] = &[ + (tr::settings::prompt::ok(), "Ok"), + (tr::settings::sidebar::account(), "Account"), + ( + tr::settings::sidebar::click_to_sign_in(), + "Click to sign in", + ), + (tr::settings::sidebar::signed_in(), "Signed in"), + (tr::settings::sidebar::sign_in(), "Sign In"), + (tr::settings::sidebar::sign_out(), "Sign Out"), + (tr::settings::sidebar::cancel_sign_in(), "Cancel Sign In"), + ( + tr::settings::sidebar::check_for_updates(), + "Check for updates", + ), + ( + tr::settings::sidebar::previous_versions(), + "View previous versions", + ), + (tr::settings::recordings::title(), "Recordings"), + ( + tr::settings::recordings::description(), + "Manage your recordings and perform actions.", + ), + (tr::settings::recordings::import(), "Import"), + ( + tr::settings::recordings::empty_library(), + "No recordings found", + ), + (tr::settings::recordings::show_all(), "Show all"), + (tr::settings::recordings::empty::all(), "No recordings"), + ( + tr::settings::recordings::empty::all_matching(), + "No matching recordings", + ), + ( + tr::settings::recordings::empty::instant(), + "No instant recordings", + ), + ( + tr::settings::recordings::empty::instant_matching(), + "No matching instant recordings", + ), + ( + tr::settings::recordings::empty::studio(), + "No studio recordings", + ), + (tr::settings::recordings::load_more(), "Load more"), + ( + tr::settings::recordings::in_progress(), + "Recording in progress", + ), + (tr::settings::recordings::failed(), "Recording failed"), + (tr::settings::recordings::open_link(), "Open link"), + (tr::settings::recordings::edit(), "Edit"), + (tr::settings::recordings::retry_upload(), "Retry upload"), + ( + tr::settings::recordings::open_bundle(), + "Open recording bundle", + ), + (tr::settings::recordings::delete(), "Delete"), + ( + tr::settings::recordings::corrupted_title(), + "Recording is potentially corrupted", + ), + ( + tr::settings::recordings::corrupted_body(), + "The recording failed so this file may have issues in the editor! If your having issues recovering the file please reach out to support!", + ), + (tr::settings::screenshots::title(), "Screenshots"), + ( + tr::settings::screenshots::description(), + "Manage your screenshots and perform actions.", + ), + (tr::settings::screenshots::import(), "Import image"), + ( + tr::settings::screenshots::empty_library(), + "No screenshots found", + ), + (tr::settings::screenshots::empty(), "No screenshots"), + ( + tr::settings::screenshots::empty_matching(), + "No matching screenshots", + ), + (tr::settings::screenshots::load_more(), "Load more"), + (tr::settings::screenshots::copy(), "Copy to clipboard"), + (tr::settings::screenshots::save_as(), "Save as"), + (tr::settings::screenshots::open_folder(), "Open folder"), + (tr::settings::screenshots::delete(), "Delete"), + ( + tr::settings::quality::save_failed(), + "Couldn't save your recording settings. Please try again.", + ), + (tr::settings::quality::studio::title(), "Studio recordings"), + ( + tr::settings::quality::studio::description(), + "Saved to your computer for editing. You choose the export size later, in the editor.", + ), + (tr::settings::quality::studio::recommended(), "Recommended"), + (tr::settings::quality::studio::balanced(), "Balanced"), + ( + tr::settings::quality::studio::balanced_description(), + "Sharp detail at a sensible file size. Right for most recordings.", + ), + ( + tr::settings::quality::studio::compatibility(), + "Smaller files", + ), + ( + tr::settings::quality::studio::compatibility_description(), + "Saves disk space. Fine detail can soften, most of all on camera.", + ), + (tr::settings::quality::studio::ultra(), "Maximum detail"), + ( + tr::settings::quality::studio::ultra_description(), + "Keeps the most detail for heavy editing. Uses the most disk space.", + ), + ( + tr::settings::quality::instant::title(), + "Instant recordings", + ), + ( + tr::settings::quality::instant::description(), + "Uploaded while you record, so the link is ready the moment you stop. For the best quality, use Studio mode: it records at the highest quality your screen allows, and you can export up to 4K.", + ), + ( + tr::settings::quality::instant::max_resolution(), + "Maximum resolution", + ), + ( + tr::settings::quality::instant::free_plan(), + "720p on the free plan. Cap Pro records up to 4K.", + ), + ( + tr::settings::quality::instant::res_720p(), + "Smallest uploads, so links are ready fastest.", + ), + ( + tr::settings::quality::instant::res_1080p(), + "Sharp text at a practical upload size.", + ), + ( + tr::settings::quality::instant::res_1440p(), + "Extra detail for large, high-resolution screens.", + ), + ( + tr::settings::quality::instant::res_4k(), + "The most detail. Best on a fast connection.", + ), + (tr::settings::quality::sharing::title(), "Sharing"), + ( + tr::settings::quality::sharing::auto_open(), + "Open share links automatically", + ), + ( + tr::settings::quality::sharing::auto_open_description(), + "Open the link in your browser when an upload finishes.", + ), + ( + tr::settings::options::studio_quality::compatibility(), + "Compatibility", + ), + ( + tr::settings::options::studio_quality::balanced(), + "Balanced", + ), + (tr::settings::options::studio_quality::ultra(), "Ultra"), + (tr::settings::options::main_window_start::close(), "Close"), + ( + tr::settings::options::main_window_start::minimise(), + "Minimise", + ), + ( + tr::settings::options::post_studio::open_editor(), + "Open editor", + ), + (tr::settings::options::post_studio::show_cap(), "Show Cap"), + ( + tr::settings::options::post_studio::do_nothing(), + "Do nothing", + ), + ( + tr::settings::options::post_screenshot::open_editor(), + "Open editor", + ), + ( + tr::settings::options::post_screenshot::copy(), + "Copy to clipboard", + ), + ( + tr::settings::options::post_screenshot::copy_and_open_editor(), + "Copy and open editor", + ), + ( + tr::settings::options::post_screenshot::do_nothing(), + "Do nothing", + ), + ( + tr::settings::options::post_deletion::do_nothing(), + "Do nothing", + ), + ( + tr::settings::options::post_deletion::reopen(), + "Reopen recording window", + ), + (tr::settings::options::preview_quality::quarter(), "Quarter"), + (tr::settings::options::preview_quality::half(), "Half"), + (tr::settings::options::preview_quality::full(), "Full"), + (tr::settings::shortcuts::screenshots_title(), "Screenshots"), + ( + tr::settings::shortcuts::screenshots_description(), + "Shortcuts work from any app. Click one to change it.", + ), + (tr::settings::shortcuts::recording_title(), "Recording"), + ( + tr::settings::shortcuts::recording_description(), + "Start a recording without opening Cap first.", + ), + ( + tr::settings::shortcuts::while_recording_title(), + "While recording", + ), + ( + tr::settings::shortcuts::after_screenshot::title(), + "After a screenshot", + ), + ( + tr::settings::shortcuts::after_screenshot::hint(), + "For every screenshot, from a shortcut or from Cap", + ), + ( + tr::settings::shortcuts::after_studio::title(), + "After a Studio recording", + ), + ( + tr::settings::shortcuts::after_studio::hint(), + "When you stop", + ), + ( + tr::settings::shortcuts::after_instant::title(), + "After an Instant recording", + ), + ( + tr::settings::shortcuts::after_instant::hint(), + "The share link is always copied", + ), + ( + tr::settings::shortcuts::outcome::screenshot_open_editor(), + "Open editor", + ), + ( + tr::settings::shortcuts::outcome::screenshot_open_editor_hint(), + "Annotate first", + ), + (tr::settings::shortcuts::outcome::screenshot_copy(), "Copy"), + ( + tr::settings::shortcuts::outcome::screenshot_copy_hint(), + "Paste anywhere", + ), + ( + tr::settings::shortcuts::outcome::screenshot_copy_and_edit(), + "Copy and edit", + ), + ( + tr::settings::shortcuts::outcome::screenshot_copy_and_edit_hint(), + "Both at once", + ), + ( + tr::settings::shortcuts::outcome::screenshot_do_nothing(), + "Do nothing", + ), + ( + tr::settings::shortcuts::outcome::saved_to_library(), + "Saved to library", + ), + ( + tr::settings::shortcuts::outcome::studio_open_editor(), + "Open editor", + ), + ( + tr::settings::shortcuts::outcome::studio_open_editor_hint(), + "Edit right away", + ), + ( + tr::settings::shortcuts::outcome::studio_show_cap(), + "Show Cap", + ), + ( + tr::settings::shortcuts::outcome::studio_show_cap_hint(), + "Back to the recorder", + ), + ( + tr::settings::shortcuts::outcome::studio_do_nothing(), + "Do nothing", + ), + ( + tr::settings::shortcuts::outcome::instant_open_link(), + "Open link", + ), + ( + tr::settings::shortcuts::outcome::instant_open_link_hint(), + "In your browser", + ), + ( + tr::settings::shortcuts::outcome::instant_copy_link(), + "Copy link only", + ), + ( + tr::settings::shortcuts::outcome::instant_copy_link_hint(), + "Nothing opens", + ), + ( + tr::settings::shortcuts::label::screenshot_area(), + "Screenshot an area", + ), + ( + tr::settings::shortcuts::label::screenshot_window(), + "Screenshot a window", + ), + ( + tr::settings::shortcuts::label::screenshot_display(), + "Screenshot the display", + ), + ( + tr::settings::shortcuts::label::start_studio(), + "Start Studio recording", + ), + ( + tr::settings::shortcuts::label::start_instant(), + "Start Instant recording", + ), + ( + tr::settings::shortcuts::label::record_area(), + "Record an area", + ), + ( + tr::settings::shortcuts::label::record_window(), + "Record a window", + ), + ( + tr::settings::shortcuts::label::record_display(), + "Record a display", + ), + ( + tr::settings::shortcuts::label::open_picker(), + "Open recording picker", + ), + ( + tr::settings::shortcuts::label::cycle_mode(), + "Cycle recording mode", + ), + (tr::settings::shortcuts::label::stop(), "Stop recording"), + (tr::settings::shortcuts::label::pause(), "Pause or resume"), + ( + tr::settings::shortcuts::label::restart(), + "Restart recording", + ), + (tr::settings::shortcuts::label::fallback(), "Shortcut"), + ( + tr::settings::shortcuts::needs_modifier_macos(), + "Include ⌘, ⌃ or ⌥ so the shortcut doesn't get in the way of typing.", + ), + ( + tr::settings::shortcuts::needs_modifier_other(), + "Include Ctrl, Alt or Win so the shortcut doesn't get in the way of typing.", + ), + ( + tr::settings::shortcuts::unsupported_key(), + "That key can't be used in a shortcut. Try a letter, number or F-key.", + ), + ( + tr::settings::shortcuts::recording_hint(), + "Press the keys you want to use. Esc cancels, Delete removes it.", + ), + ( + tr::settings::shortcuts::blocked_while_recording(), + "Shortcuts can't be changed while this recording holds its stop key. Stop recording first.", + ), + ( + tr::settings::shortcuts::unavailable(), + "Another app or the system already uses this shortcut. Click to choose a different one.", + ), + (tr::settings::shortcuts::type_shortcut(), "Type shortcut…"), + ( + tr::settings::shortcuts::record_shortcut(), + "Record shortcut", + ), + (tr::settings::cli::title(), "Command Line"), + ( + tr::settings::cli::description(), + "Install the Cap command for terminals, agents, scripts, and local automation.", + ), + (tr::settings::cli::retry(), "Retry"), + (tr::settings::cli::repairing(), "Repairing..."), + (tr::settings::cli::installing(), "Installing..."), + (tr::settings::cli::repair(), "Repair"), + (tr::settings::cli::install(), "Install CLI"), + (tr::settings::cli::installed(), "Installed"), + (tr::settings::cli::not_installed(), "Not installed"), + (tr::settings::cli::removing(), "Removing..."), + (tr::settings::cli::remove(), "Remove"), + (tr::settings::cli::command(), "Command"), + (tr::settings::cli::target(), "Target"), + ( + tr::settings::cli::path_configured(), + "Added cap to your PATH. Restart your terminal to use it, or run this now:", + ), + (tr::settings::cli::copy(), "Copy"), + (tr::settings::transcription::title(), "Transcription"), + ( + tr::settings::transcription::description(), + "Add names, spellings, domains, and capitalization preferences that caption generation should keep in mind.", + ), + ( + tr::settings::transcription::terms_title(), + "Remembered terms", + ), + ( + tr::settings::transcription::terms_description(), + "Add one term at a time to reduce typos and formatting mistakes.", + ), + (tr::settings::transcription::clear(), "Clear"), + (tr::settings::transcription::saving(), "Saving..."), + (tr::settings::transcription::saved(), "Saved"), + (tr::settings::transcription::placeholder(), "Add a term"), + ( + tr::settings::transcription::applied_note(), + "These hints are applied when you generate captions in the editor.", + ), + (tr::settings::transcription::active_title(), "Active hints"), + (tr::settings::experimental::reliability(), "Reliability"), + (tr::settings::experimental::muxer(), "Out-of-process muxer"), + ( + tr::settings::experimental::muxer_description(), + "Run the fragmented-MP4 muxer in an isolated subprocess so muxer crashes can't take down your recording. Requires the bundled cap-muxer binary.", + ), + (tr::settings::experimental::developer(), "Developer"), + ( + tr::settings::experimental::developer_description(), + "Only shown in development builds.", + ), + (tr::settings::experimental::native_app(), "Use native app"), + ( + tr::settings::experimental::native_app_description(), + "Turn off to close this app and reopen the classic Tauri app in the dev harness. Recordings and settings are shared.", + ), + ( + tr::settings::experimental::switch::switching(), + "Switching back to the classic Cap app.", + ), + ( + tr::settings::experimental::switch::data_stays(), + "Your recordings and settings stay exactly where they are.", + ), + ( + tr::settings::experimental::switch::opening(), + "Opening the classic Cap app", + ), + ( + tr::settings::experimental::switch::opening_detail(), + "This window will close when the classic Cap app is ready.", + ), + ( + tr::settings::experimental::switch::busy(), + "Finish your recording, export, upload, import, or transcription task before switching to the classic app.", + ), + ( + tr::settings::experimental::switch::not_found(), + "Couldn't find the classic Cap app to switch back to.", + ), + ( + tr::settings::experimental::switch::save_failed(), + "Couldn't save your app preference. Cap is still open.", + ), + ( + tr::settings::experimental::switch::timed_out(), + "The classic app hasn't opened. Check the dev terminal for build errors, then try again.", + ), + ( + tr::settings::experimental::switch::failed(), + "Couldn't open the classic Cap app.", + ), + (tr::settings::feedback::title(), "Feedback"), + ( + tr::settings::feedback::description(), + "Help us improve Cap by submitting feedback or reporting bugs. We'll get right on it.", + ), + ( + tr::settings::feedback::placeholder(), + "Tell us what you think about Cap...", + ), + ( + tr::settings::feedback::submit_failed(), + "Failed to submit feedback", + ), + ( + tr::settings::feedback::thanks(), + "Thank you for your feedback!", + ), + (tr::settings::feedback::submitting(), "Submitting..."), + (tr::settings::feedback::submit(), "Submit Feedback"), + ( + tr::settings::feedback::community_title(), + "Join the Community", + ), + ( + tr::settings::feedback::community_description(), + "Have questions, want to share ideas, or just hang out? Join the Cap Discord community.", + ), + (tr::settings::feedback::join_discord(), "Join Discord"), + (tr::settings::feedback::debug_title(), "Debug Information"), + ( + tr::settings::feedback::debug_description(), + "Upload your logs to help us diagnose issues with Cap. No personal information is included.", + ), + (tr::settings::feedback::uploading(), "Uploading..."), + (tr::settings::feedback::upload_logs(), "Upload Logs"), + ( + tr::settings::feedback::upload_sent(), + "Sent. Thank you -- this helps us find the problem.", + ), + ( + tr::settings::feedback::upload_interrupted(), + "The upload was interrupted", + ), + (tr::settings::feedback::system_title(), "System Information"), + ( + tr::settings::feedback::system_loading(), + "Loading system information...", + ), + ( + tr::settings::feedback::operating_system(), + "Operating System", + ), + (tr::settings::feedback::capture_support(), "Capture Support"), + (tr::settings::feedback::app_version(), "App Version"), + ( + tr::settings::feedback::diagnostic::title(), + "Diagnostic Report", + ), + ( + tr::settings::feedback::diagnostic::description(), + "Records a short flashing/beeping test pattern, measures how far the picture and sound drift apart, and snapshots your displays, cameras, microphones, disk space and recording settings. Nothing is sent anywhere until you choose to send it.", + ), + ( + tr::settings::feedback::diagnostic::stop_recording_first(), + "Stop the current recording before running a diagnostic.", + ), + ( + tr::settings::feedback::diagnostic::confirm_title(), + "Run diagnostic?", + ), + (tr::settings::feedback::diagnostic::run(), "Run Diagnostic"), + ( + tr::settings::feedback::diagnostic::no_cli(), + "The Cap command-line tool was not found next to this build, so the report will cover your environment only.", + ), + ( + tr::settings::feedback::diagnostic::no_cli_sync(), + "The Cap command-line tool was not found next to this build, so the sync test could not run.", + ), + ( + tr::settings::feedback::diagnostic::warning_title(), + "The test takes over your screen and makes noise.", + ), + ( + tr::settings::feedback::diagnostic::warning_body(), + "A fullscreen window flashes black and white while 1kHz beeps play through your speakers, and Cap records it and measures how far the picture and the sound have drifted apart. Leave the machine alone while it runs. Both modes together take a couple of minutes.", + ), + ( + tr::settings::feedback::diagnostic::mode_label(), + "Recording mode", + ), + ( + tr::settings::feedback::diagnostic::mode_description(), + "Which pipeline to test. Both runs the studio leg and then the instant one.", + ), + ( + tr::settings::feedback::diagnostic::mic_label(), + "Test microphone", + ), + ( + tr::settings::feedback::diagnostic::mic_description(), + "Also record your microphone and check that it hears the beeps in time. Needs speakers the mic can actually hear.", + ), + (tr::settings::feedback::diagnostic::sending(), "Sending..."), + (tr::settings::feedback::diagnostic::send(), "Send to Cap"), + (tr::settings::feedback::diagnostic::show_file(), "Show File"), + (tr::settings::feedback::diagnostic::run_again(), "Run Again"), + (tr::settings::feedback::diagnostic::try_again(), "Try Again"), + (tr::settings::feedback::diagnostic::verdict::pass(), "PASS"), + (tr::settings::feedback::diagnostic::verdict::warn(), "WARN"), + (tr::settings::feedback::diagnostic::verdict::fail(), "FAIL"), + ( + tr::settings::feedback::diagnostic::verdict::inconclusive(), + "INCONCLUSIVE", + ), + ( + tr::settings::feedback::diagnostic::verdict::environment_only(), + "ENVIRONMENT ONLY", + ), + (tr::settings::sign_in_required::title(), "Sign in required"), + ( + tr::settings::sign_in_required::body(), + "Please sign in to continue. Alternatively, email hello@cap.so or join our Discord at cap.link/discord", + ), + (tr::settings::changelog::loading(), "Loading changelog..."), + ( + tr::settings::changelog::fetch_failed(), + "Failed to fetch changelog", + ), + (tr::settings::changelog::new(), "NEW"), + (tr::settings::license::title(), "Plan & license"), + ( + tr::settings::license::description(), + "Your Cap plan and desktop license, in one place.", + ), + (tr::settings::license::plan_title(), "Your plan"), + ( + tr::settings::license::pro_description(), + "Your account includes cloud sharing, Pro features and a desktop license for commercial use.", + ), + (tr::settings::license::desktop_name(), "Desktop License"), + ( + tr::settings::license::desktop_description(), + "Your desktop license covers commercial recording and editing. Cap Pro adds cloud sharing and collaboration features.", + ), + ( + tr::settings::license::free_description(), + "Record and edit locally for personal use. Choose a paid plan for commercial use or more cloud features.", + ), + (tr::settings::license::checking(), "Checking…"), + (tr::settings::license::refresh(), "Refresh plan"), + ( + tr::settings::license::refresh_failed(), + "Couldn't refresh your plan. Please try again.", + ), + ( + tr::settings::license::sign_in_hint(), + "Already have Cap Pro? Sign in with your account from the sidebar.", + ), + (tr::settings::license::explore_title(), "Explore plans"), + ( + tr::settings::license::explore_description(), + "Compare current pricing and everything included on our website.", + ), + ( + tr::settings::license::explore_desktop(), + "Desktop License · Commercial use of the desktop recorder and editor.", + ), + ( + tr::settings::license::explore_pro(), + "Cap Pro · A desktop license, plus cloud sharing, AI features and collaboration.", + ), + ( + tr::settings::license::view_pricing(), + "View plans & pricing ↗", + ), + ( + tr::settings::license::opens_pricing(), + "Opens cap.so/pricing in your browser.", + ), + (tr::settings::license::active_title(), "Desktop license"), + (tr::settings::license::key(), "License Key"), + (tr::settings::license::expires(), "Expires"), + (tr::settings::license::deactivate(), "Deactivate License"), + (tr::settings::license::have_key(), "Have a license key?"), + (tr::settings::license::placeholder(), "License key"), + (tr::settings::license::activating(), "Activating..."), + (tr::settings::license::activate(), "Activate License"), + (tr::settings::license::no_instance(), "No instance ID found"), + ( + tr::settings::license::save_failed(), + "Failed to save the license", + ), + (tr::settings::integrations::title(), "Integrations"), + ( + tr::settings::integrations::description(), + "Configure integrations to extend Cap's functionality and connect with third-party services.", + ), + ( + tr::settings::integrations::google_drive_description(), + "Connect Google Drive for new shareable link uploads. Cap stores new videos in a private Cap folder in your Drive and continues serving them through Cap after normal access checks.", + ), + (tr::settings::integrations::s3_name(), "S3 Config"), + ( + tr::settings::integrations::s3_description(), + "Connect your own S3 bucket for complete control over your data storage. All new shareable link uploads will be automatically uploaded to your configured S3 bucket, ensuring you maintain complete ownership and control over your content. Perfect for organizations requiring data sovereignty and custom storage policies.", + ), + ( + tr::settings::integrations::managed(), + "Managed by your organization", + ), + (tr::settings::integrations::upgrade(), "Upgrade to Pro"), + (tr::settings::integrations::configure(), "Configure"), + ( + tr::settings::integrations::request_timed_out(), + "The request timed out", + ), + ( + tr::settings::integrations::s3::configuration(), + "Configuration", + ), + ( + tr::settings::integrations::s3::guide(), + "Storage Config Guide", + ), + ( + tr::settings::integrations::s3::provider(), + "Storage Provider", + ), + ( + tr::settings::integrations::s3::other_provider(), + "Other S3-Compatible", + ), + ( + tr::settings::integrations::s3::access_key(), + "Access Key ID", + ), + ( + tr::settings::integrations::s3::secret_key(), + "Secret Access Key", + ), + (tr::settings::integrations::s3::endpoint(), "Endpoint"), + (tr::settings::integrations::s3::bucket(), "Bucket Name"), + (tr::settings::integrations::s3::region(), "Region"), + (tr::settings::integrations::s3::removing(), "Removing..."), + (tr::settings::integrations::s3::remove(), "Remove Config"), + (tr::settings::integrations::s3::testing(), "Testing..."), + (tr::settings::integrations::s3::test(), "Test Connection"), + (tr::settings::integrations::s3::saving(), "Saving..."), + ( + tr::settings::integrations::s3::fetch_failed(), + "Failed to fetch S3 config", + ), + ( + tr::settings::integrations::s3::saved(), + "S3 configuration saved successfully", + ), + ( + tr::settings::integrations::s3::save_failed(), + "Failed to save S3 config", + ), + ( + tr::settings::integrations::s3::deleted(), + "S3 configuration deleted successfully", + ), + ( + tr::settings::integrations::s3::delete_failed(), + "Failed to delete S3 config", + ), + ( + tr::settings::integrations::s3::test_ok(), + "S3 configuration test successful! Connection is working.", + ), + ( + tr::settings::integrations::s3::test_failed(), + "S3 connection test failed. Check your config and network connection.", + ), + ( + tr::settings::integrations::s3::test_timeout(), + "Connection test timed out after 5 seconds. Please check your endpoint URL and network connection.", + ), + ( + tr::settings::integrations::gdrive::connection_title(), + "Connection", + ), + ( + tr::settings::integrations::gdrive::connection_description(), + "Google Drive stores new uploads in a private Cap folder in your Drive. Existing Cap-hosted and S3 videos keep using their current storage.", + ), + ( + tr::settings::integrations::gdrive::active_for_uploads(), + "Active for new uploads", + ), + ( + tr::settings::integrations::gdrive::connected_inactive(), + "Connected but not active", + ), + ( + tr::settings::integrations::gdrive::not_connected(), + "Not connected", + ), + ( + tr::settings::integrations::gdrive::refreshing(), + "Refreshing...", + ), + (tr::settings::integrations::gdrive::refresh(), "Refresh"), + (tr::settings::integrations::gdrive::waiting(), "Waiting..."), + (tr::settings::integrations::gdrive::opening(), "Opening..."), + ( + tr::settings::integrations::gdrive::connect(), + "Connect Google Drive", + ), + (tr::settings::integrations::gdrive::active(), "Active"), + ( + tr::settings::integrations::gdrive::use_drive(), + "Use Google Drive", + ), + (tr::settings::integrations::gdrive::use_s3(), "Use S3"), + (tr::settings::integrations::gdrive::testing(), "Testing..."), + (tr::settings::integrations::gdrive::test(), "Test"), + ( + tr::settings::integrations::gdrive::disconnect(), + "Disconnect", + ), + ( + tr::settings::integrations::gdrive::connect_failed(), + "Failed to start Google Drive connection", + ), + ( + tr::settings::integrations::gdrive::finish_in_browser(), + "Finish connecting Google Drive in your browser, then return here and refresh.", + ), + ( + tr::settings::integrations::gdrive::working(), + "Google Drive connection is working", + ), + ( + tr::settings::integrations::gdrive::test_failed(), + "Google Drive connection test failed", + ), + ( + tr::settings::integrations::gdrive::set_active_failed(), + "Failed to update active storage provider", + ), + ( + tr::settings::integrations::gdrive::disconnected(), + "Google Drive disconnected", + ), + ( + tr::settings::integrations::gdrive::disconnect_failed(), + "Failed to disconnect Google Drive", + ), + (tr::settings::integrations::gdrive::storage(), "Storage"), + (tr::settings::integrations::gdrive::remaining(), "Remaining"), + ( + tr::settings::integrations::gdrive::drive_files(), + "Drive files", + ), + (tr::settings::integrations::gdrive::trash(), "Trash"), + (tr::settings::automations::title(), "Automations"), + ( + tr::settings::automations::description(), + "Rules are shared with the Cap CLI. Cap saves these rules but does not run automations yet.", + ), + (tr::settings::automations::templates_title(), "Templates"), + ( + tr::settings::automations::templates_description(), + "One click to add a ready-made automation. Tweak anything afterwards.", + ), + ( + tr::settings::automations::save_failed(), + "Failed to save automations. Your changes have not been saved.", + ), + (tr::settings::automations::retry_save(), "Retry save"), + ( + tr::settings::automations::empty_title(), + "No automations yet", + ), + ( + tr::settings::automations::empty_description(), + "Pick a template below to get started in one click, or build your own from scratch.", + ), + ( + tr::settings::automations::start_from_scratch(), + "Start from scratch", + ), + (tr::settings::automations::new(), "New automation"), + ( + tr::settings::automations::status::enabled_saved(), + "Enabled · Saved", + ), + ( + tr::settings::automations::status::disabled_saved(), + "Disabled · Saved", + ), + ( + tr::settings::automations::status::enabled_unsaved(), + "Enabled · Not saved", + ), + ( + tr::settings::automations::status::disabled_unsaved(), + "Disabled · Not saved", + ), + (tr::settings::automations::enabled(), "Enabled"), + (tr::settings::automations::name(), "Name"), + (tr::settings::automations::match_all(), "Match all"), + (tr::settings::automations::match_any(), "Match any"), + (tr::settings::automations::add_condition(), "Add condition"), + (tr::settings::automations::add_action(), "Add action"), + ( + tr::settings::automations::check_compatibility(), + "Check compatibility", + ), + (tr::settings::automations::delete(), "Delete"), + ( + tr::settings::automations::dangerous(), + "In the Cap CLI, this automation runs commands or sends network requests with your permissions. Only use values you trust.", + ), + ( + tr::settings::automations::compatibility_checked(), + "Compatibility checked: this automation doesn't run in Cap yet. Saved rules remain available to the Cap CLI. No actions were run.", + ), + ( + tr::settings::automations::condition_never_matches(), + "This condition never matches for the selected trigger.", + ), + ( + tr::settings::automations::action_no_effect(), + "This action has no effect for the selected trigger.", + ), + (tr::settings::automations::browse(), "Browse"), + ( + tr::settings::automations::copy_link(), + "Copy link to clipboard", + ), + ( + tr::settings::automations::open_in_browser(), + "Open in browser", + ), + ( + tr::settings::automations::run_through_shell(), + "Run through shell", + ), + ( + tr::settings::automations::no_presets(), + "No presets yet — create one in the editor first.", + ), + (tr::settings::automations::field::source(), "Source"), + (tr::settings::automations::field::folder(), "Folder"), + ( + tr::settings::automations::field::filename(), + "Filename template (optional)", + ), + ( + tr::settings::automations::field::organization(), + "Organization ID (optional)", + ), + (tr::settings::automations::field::program(), "Program"), + ( + tr::settings::automations::field::arguments(), + "Arguments (space-separated)", + ), + (tr::settings::automations::field::url(), "URL"), + (tr::settings::automations::field::method(), "Method"), + ( + tr::settings::automations::field::body_template(), + "Body template (optional)", + ), + (tr::settings::automations::field::title(), "Title"), + (tr::settings::automations::field::body(), "Body"), + (tr::settings::automations::field::preset(), "Preset"), + (tr::settings::automations::field::format(), "Format"), + (tr::settings::automations::field::resolution(), "Resolution"), + (tr::settings::automations::field::frame_rate(), "Frame rate"), + ( + tr::settings::automations::field::compression(), + "Compression", + ), + ( + tr::settings::automations::field::destination(), + "Destination folder (optional, blank = project folder)", + ), + ( + tr::settings::automations::placeholder::window_title(), + "e.g. Slack", + ), + ( + tr::settings::automations::placeholder::organization_id(), + "Organization ID", + ), + ( + tr::settings::automations::placeholder::project_folder(), + "Project folder", + ), + ( + tr::settings::automations::clipboard::original(), + "Original capture", + ), + ( + tr::settings::automations::clipboard::rendered(), + "Edited / rendered", + ), + (tr::settings::automations::target::display(), "Display"), + (tr::settings::automations::target::window(), "Window"), + (tr::settings::automations::target::area(), "Area"), + (tr::settings::automations::compression::maximum(), "Maximum"), + (tr::settings::automations::compression::social(), "Social"), + (tr::settings::automations::compression::web(), "Web"), + (tr::settings::automations::compression::potato(), "Potato"), + ( + tr::settings::automations::trigger::screenshot_taken::label(), + "On screenshot taken", + ), + ( + tr::settings::automations::trigger::screenshot_taken::phrase(), + "Screenshot taken", + ), + ( + tr::settings::automations::trigger::screenshot_taken::noun(), + "Screenshot", + ), + ( + tr::settings::automations::trigger::studio_finished::label(), + "On studio recording finished", + ), + ( + tr::settings::automations::trigger::studio_finished::phrase(), + "Studio recording ends", + ), + ( + tr::settings::automations::trigger::studio_finished::noun(), + "Studio recording", + ), + ( + tr::settings::automations::trigger::instant_finished::label(), + "On instant recording finished", + ), + ( + tr::settings::automations::trigger::instant_finished::phrase(), + "Instant recording ends", + ), + ( + tr::settings::automations::trigger::instant_finished::noun(), + "Instant recording", + ), + ( + tr::settings::automations::trigger::recording_started::label(), + "On recording started", + ), + ( + tr::settings::automations::trigger::recording_started::phrase(), + "Recording starts", + ), + ( + tr::settings::automations::trigger::recording_started::noun(), + "Recording start", + ), + ( + tr::settings::automations::trigger::upload_completed::label(), + "On upload completed", + ), + ( + tr::settings::automations::trigger::upload_completed::phrase(), + "Upload completes", + ), + ( + tr::settings::automations::trigger::upload_completed::noun(), + "Upload", + ), + ( + tr::settings::automations::trigger::video_imported::label(), + "On video imported", + ), + ( + tr::settings::automations::trigger::video_imported::phrase(), + "Video imported", + ), + ( + tr::settings::automations::trigger::video_imported::noun(), + "Import", + ), + ( + tr::settings::automations::trigger::recording_deleted::label(), + "On recording deleted", + ), + ( + tr::settings::automations::trigger::recording_deleted::phrase(), + "Recording deleted", + ), + ( + tr::settings::automations::trigger::recording_deleted::noun(), + "Deletion", + ), + ( + tr::settings::automations::action::copy_to_clipboard::label(), + "Copy to clipboard", + ), + ( + tr::settings::automations::action::copy_to_clipboard::short(), + "Copy to clipboard", + ), + ( + tr::settings::automations::action::copy_to_clipboard::noun(), + "Clipboard", + ), + ( + tr::settings::automations::action::save_to_location::label(), + "Save to location", + ), + ( + tr::settings::automations::action::save_to_location::short(), + "Save to folder", + ), + ( + tr::settings::automations::action::save_to_location::noun(), + "Folder", + ), + ( + tr::settings::automations::action::export::label(), + "Export with profile", + ), + (tr::settings::automations::action::export::short(), "Export"), + (tr::settings::automations::action::export::noun(), "Export"), + ( + tr::settings::automations::action::upload::label(), + "Upload + copy link", + ), + ( + tr::settings::automations::action::upload::short(), + "Upload & copy link", + ), + (tr::settings::automations::action::upload::noun(), "Upload"), + ( + tr::settings::automations::action::reveal::label(), + "Reveal in file manager", + ), + ( + tr::settings::automations::action::reveal::short(), + "Reveal in file manager", + ), + (tr::settings::automations::action::reveal::noun(), "Reveal"), + ( + tr::settings::automations::action::open_file::label(), + "Open file", + ), + ( + tr::settings::automations::action::open_file::short(), + "Open file", + ), + (tr::settings::automations::action::open_file::noun(), "Open"), + ( + tr::settings::automations::action::ocr::label(), + "Recognize text (OCR) to clipboard", + ), + ( + tr::settings::automations::action::ocr::short(), + "Copy text (OCR)", + ), + (tr::settings::automations::action::ocr::noun(), "Text"), + ( + tr::settings::automations::action::notify::label(), + "Show notification", + ), + (tr::settings::automations::action::notify::short(), "Notify"), + (tr::settings::automations::action::notify::noun(), "Notify"), + ( + tr::settings::automations::action::open_editor::label(), + "Open editor", + ), + ( + tr::settings::automations::action::open_editor::short(), + "Open editor", + ), + ( + tr::settings::automations::action::open_editor::noun(), + "Editor", + ), + ( + tr::settings::automations::action::skip_editor::label(), + "Skip editor (headless)", + ), + ( + tr::settings::automations::action::skip_editor::short(), + "Skip editor", + ), + ( + tr::settings::automations::action::skip_editor::noun(), + "Skip editor", + ), + ( + tr::settings::automations::action::apply_preset::label(), + "Apply editor preset", + ), + ( + tr::settings::automations::action::apply_preset::short(), + "Apply preset", + ), + ( + tr::settings::automations::action::apply_preset::noun(), + "Preset", + ), + ( + tr::settings::automations::action::run_command::label(), + "Run command", + ), + ( + tr::settings::automations::action::run_command::short(), + "Run command", + ), + ( + tr::settings::automations::action::run_command::noun(), + "Command", + ), + ( + tr::settings::automations::action::webhook::label(), + "Send webhook", + ), + ( + tr::settings::automations::action::webhook::short(), + "Send webhook", + ), + ( + tr::settings::automations::action::webhook::noun(), + "Webhook", + ), + ( + tr::settings::automations::action::delete_local_files::label(), + "Delete local files", + ), + ( + tr::settings::automations::action::delete_local_files::short(), + "Delete local files", + ), + ( + tr::settings::automations::action::delete_local_files::noun(), + "Delete", + ), + ( + tr::settings::automations::condition::capture_target(), + "Capture target is", + ), + ( + tr::settings::automations::condition::recording_mode(), + "Recording mode is", + ), + ( + tr::settings::automations::condition::duration_at_least(), + "Duration at least (seconds)", + ), + ( + tr::settings::automations::condition::duration_at_most(), + "Duration at most (seconds)", + ), + ( + tr::settings::automations::condition::window_title(), + "Window title contains", + ), + ( + tr::settings::automations::condition::organization(), + "Organization is", + ), + ( + tr::settings::automations::template::copy_screenshot::name(), + "Auto-copy new screenshots to clipboard", + ), + ( + tr::settings::automations::template::copy_screenshot::description(), + "Snap a screenshot and it's right there, ready to paste.", + ), + ( + tr::settings::automations::template::ocr_screenshot::name(), + "Pull the text out of screenshots", + ), + ( + tr::settings::automations::template::ocr_screenshot::description(), + "Cap reads the text in your screenshot and copies it for you.", + ), + ( + tr::settings::automations::template::save_screenshot::name(), + "Tuck screenshots into a folder", + ), + ( + tr::settings::automations::template::save_screenshot::description(), + "Send every new screenshot straight to a folder you pick.", + ), + ( + tr::settings::automations::template::reveal_screenshot::name(), + "Jump to each new screenshot", + ), + ( + tr::settings::automations::template::reveal_screenshot::description(), + "Pop open every screenshot in Finder the moment you take it.", + ), + ( + tr::settings::automations::template::export_studio::name(), + "Auto-export when you finish recording", + ), + ( + tr::settings::automations::template::export_studio::description(), + "Render an MP4 the second a Studio recording wraps up.", + ), + ( + tr::settings::automations::template::upload_share::name(), + "Upload and grab the share link", + ), + ( + tr::settings::automations::template::upload_share::description(), + "Finish a recording and the link is waiting on your clipboard.", + ), + ( + tr::settings::automations::template::notify_upload::name(), + "Ping me when an upload is ready", + ), + ( + tr::settings::automations::template::notify_upload::description(), + "Get a gentle desktop nudge once your recording is shareable.", + ), + ( + tr::settings::automations::template::notify_upload::body(), + "Your recording is ready to share.", + ), + ( + tr::settings::automations::template::webhook_share::name(), + "Tell Slack when you share something", + ), + ( + tr::settings::automations::template::webhook_share::description(), + "Send the share link to Slack, Discord, or your own webhook.", + ), + ]; + for (translated, literal) in pairs { + assert_eq!(translated, literal); + } + + let assembled: &[(&str, String)] = &[ + ( + tr::settings::recordings::empty::studio_matching(), + format!("No matching {} recordings", "studio"), + ), + ( + tr::settings::feedback::capture_supported(), + format!("Screen Capture: {}", "Supported"), + ), + ( + tr::settings::feedback::capture_unsupported(), + format!("Screen Capture: {}", "Not Supported"), + ), + ( + tr::settings::automations::when(), + "When this happens".to_uppercase(), + ), + ( + tr::settings::automations::only_if(), + "Only run if".to_uppercase(), + ), + ( + tr::settings::automations::then(), + "Then do this".to_uppercase(), + ), + ]; + for (translated, literal) in assembled { + assert_eq!(*translated, literal.as_str()); + } + + use tr::settings::automations::trigger; + let runs = [ + (trigger::screenshot_taken::runs(), "Screenshot taken"), + (trigger::studio_finished::runs(), "Studio recording ends"), + (trigger::instant_finished::runs(), "Instant recording ends"), + (trigger::recording_started::runs(), "Recording starts"), + (trigger::upload_completed::runs(), "Upload completes"), + (trigger::video_imported::runs(), "Video imported"), + (trigger::recording_deleted::runs(), "Recording deleted"), + ]; + for (translated, phrase) in runs { + assert_eq!( + translated, + format!("Runs for every {}.", phrase.to_lowercase()) + ); + } + + let plain_keys = KEYS + .iter() + .filter(|key| is_settings_surface_key(key)) + .filter(|key| !EFFECT_PREFIXES.iter().any(|prefix| key.starts_with(prefix))) + .count() + - FORMATTED.len(); + assert_eq!( + pairs.len() + assembled.len() + runs.len(), + plain_keys, + "every plain key has a literal above" + ); + for key in FORMATTED { + assert!(KEYS.contains(key), "{key}"); + } +} + +#[test] +fn settings_surface_messages_format_like_the_code_they_replaced() { + let version = "0.6.2"; + assert_eq!( + tr::settings::sidebar::downloading(version), + format!("Downloading v{version}…") + ); + for fraction in [0.0_f32, 0.425, 1.0] { + assert_eq!( + tr::settings::sidebar::downloading_progress(version, (fraction * 100.).round() as u32), + format!( + "Downloading v{version}… {}%", + (fraction * 100.).round() as u32 + ) + ); + } + assert_eq!( + tr::settings::sidebar::restart_to_update(version), + format!("Restart to update to v{version}") + ); + assert_eq!( + tr::settings::sidebar::installing(version), + format!("Installing v{version}…") + ); + + let keys = "⌘⇧4".to_string(); + let effect = tr::settings::shortcuts::effect::screenshot_area_copy(); + let mut moved = format!("Saved. Press {} in any app to {effect}.", keys); + assert_eq!(tr::settings::shortcuts::saved(&keys, effect), moved); + let from = tr::settings::shortcuts::label::record_area(); + moved.push_str(&format!(" Removed from {}.", from)); + assert_eq!( + tr::settings::shortcuts::saved_moved(&keys, effect, from), + moved + ); + + let error = "permission denied".to_string(); + assert_eq!( + tr::settings::cli::load_failed(&error), + format!("Couldn't load CLI status: {error}") + ); + assert_eq!( + tr::settings::experimental::switch::open_failed(&error), + format!("Couldn't open the classic Cap app: {error}") + ); + assert_eq!( + tr::settings::experimental::switch::check_failed(&error), + format!("Couldn't check whether the classic Cap app opened: {error}") + ); + let path_entry = "~/.local/bin".to_string(); + assert_eq!( + tr::settings::cli::path_missing(&path_entry), + format!( + "Add {} to your PATH to use cap from a new terminal.", + path_entry + ) + ); + + let marker = "\u{1}"; + let installs = tr::settings::cli::installs_command(marker); + assert_eq!( + installs.split_once(marker), + Some(( + "The desktop app installs a local\u{a0}", + "\u{a0}command that points back to the bundled CLI." + )) + ); + let guide = tr::settings::integrations::s3::guide_intro(marker); + assert_eq!( + guide.split_once(marker), + Some(( + "It should take under 10 minutes to set up and connect your storage \ + bucket to Cap. View the\u{a0}", + "\u{a0}to get started." + )) + ); + + for count in [0_usize, 1, 2, 5] { + assert_eq!( + tr::settings::transcription::count(count), + format!("{} {}", count, if count == 1 { "item" } else { "items" }) + ); + } + + assert_eq!( + tr::settings::feedback::diagnostic::confirm_body(crate::diagnostics::DEFAULT_DURATION_SECS), + format!( + "Cap will take over your screen with a flashing pattern and play loud beeps for \ + about {} seconds per pipeline. Take your headphones off, leave the volume \ + audible, and leave the machine alone until it finishes.", + crate::diagnostics::DEFAULT_DURATION_SECS + ) + ); + + let date = "8/17/2026".to_string(); + assert_eq!( + tr::settings::changelog::version("0.6.2", &date), + format!("Version {} - {}", "0.6.2", date) + ); + + let organization = "Acme".to_string(); + assert_eq!( + tr::settings::integrations::managed_by(&organization), + format!("Managed by your organization: {organization}") + ); + let email = "taylor@example.com"; + assert_eq!( + tr::settings::integrations::gdrive::working_for(email), + format!("Google Drive connection is working for {email}") + ); + let (usage, limit) = ("2.0 GB".to_string(), "15 GB".to_string()); + assert_eq!( + tr::settings::integrations::gdrive::used_of(&usage, &limit), + format!("{usage} of {limit} used") + ); + assert_eq!( + tr::settings::integrations::gdrive::used(&usage), + format!("{usage} used") + ); + let formatted = "Aug 17, 2026, 10:30 PM".to_string(); + assert_eq!( + tr::settings::integrations::gdrive::cached(&formatted), + format!("{} {formatted}", "Cached") + ); + assert_eq!( + tr::settings::integrations::gdrive::updated(&formatted), + format!("{} {formatted}", "Updated") + ); + + use tr::settings::automations::trigger; + for (phrase, noun) in [ + ( + trigger::screenshot_taken::phrase(), + trigger::screenshot_taken::noun(), + ), + ( + trigger::studio_finished::phrase(), + trigger::studio_finished::noun(), + ), + ( + trigger::instant_finished::phrase(), + trigger::instant_finished::noun(), + ), + ( + trigger::recording_started::phrase(), + trigger::recording_started::noun(), + ), + ( + trigger::upload_completed::phrase(), + trigger::upload_completed::noun(), + ), + ( + trigger::video_imported::phrase(), + trigger::video_imported::noun(), + ), + ( + trigger::recording_deleted::phrase(), + trigger::recording_deleted::noun(), + ), + ] { + assert_eq!( + tr::settings::automations::summary_empty(phrase), + format!("{phrase} → no actions yet") + ); + assert_eq!( + tr::settings::automations::auto_name_empty(noun), + format!("{noun} automation") + ); + } +} diff --git a/apps/desktop-gpui/src/i18n/tests.rs b/apps/desktop-gpui/src/i18n/tests.rs index c3b43e28840..36bdce0897f 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::settings_surface_tests::is_settings_surface_key(key) }) .count() - 3; diff --git a/apps/desktop-gpui/src/settings_pages.rs b/apps/desktop-gpui/src/settings_pages.rs index 536f5a2c88e..d7f73b1ed5b 100644 --- a/apps/desktop-gpui/src/settings_pages.rs +++ b/apps/desktop-gpui/src/settings_pages.rs @@ -31,6 +31,7 @@ use serde_json::{Map, Value, json}; use crate::{ diagnostics, + i18n::tr, settings_window::{MenuKind, Page, SettingsWindow}, store::{ self, Action, AutomationExportCompression, AutomationRecordingMode, AutomationRule, @@ -51,6 +52,10 @@ const MONO_FONT: &str = if cfg!(target_os = "macos") { "monospace" }; +/// Stands in for an inline element inside a translated sentence, so the +/// sentence can be split around it without assuming where the language puts it. +const COMMAND_MARKER: &str = "\u{1}"; + // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- @@ -193,7 +198,7 @@ struct DriveQuota { struct ListeningHotkey { action: &'static str, modifiers: gpui::Modifiers, - error: Option<&'static str>, + error: Option &'static str>, } struct SavedShortcut { @@ -211,20 +216,23 @@ const S3_REGION: usize = 4; /// `DEFAULT_CONFIG` in s3-config.tsx, indexed like the fields above. const S3_DEFAULTS: [&str; 5] = ["", "", "https://s3.amazonaws.com", "", "us-east-1"]; -const S3_FIELDS: [(&str, &str); 5] = [ - ("Access Key ID", "PL31OADSQNK"), - ("Secret Access Key", "PL31OADSQNK"), - ("Endpoint", "https://s3.amazonaws.com"), - ("Bucket Name", "my-bucket"), - ("Region", "us-east-1"), +const S3_FIELDS: [(fn() -> &'static str, &str); 5] = [ + (tr::settings::integrations::s3::access_key, "PL31OADSQNK"), + (tr::settings::integrations::s3::secret_key, "PL31OADSQNK"), + ( + tr::settings::integrations::s3::endpoint, + "https://s3.amazonaws.com", + ), + (tr::settings::integrations::s3::bucket, "my-bucket"), + (tr::settings::integrations::s3::region, "us-east-1"), ]; -const S3_PROVIDERS: [(&str, &str); 5] = [ - ("aws", "AWS S3"), - ("cloudflare", "Cloudflare R2"), - ("supabase", "Supabase"), - ("minio", "MinIO"), - ("other", "Other S3-Compatible"), +const S3_PROVIDERS: [(&str, fn() -> &'static str); 5] = [ + ("aws", || "AWS S3"), + ("cloudflare", || "Cloudflare R2"), + ("supabase", || "Supabase"), + ("minio", || "MinIO"), + ("other", tr::settings::integrations::s3::other_provider), ]; struct S3Page { @@ -371,21 +379,9 @@ pub(crate) struct PagesState { impl PagesState { pub(crate) fn new(window: &mut Window, cx: &mut Context) -> Self { - let hint_input = cx.new(|cx| { - let mut input = ui::TextInputState::single_line(window, cx); - input.set_placeholder("Add a term"); - input - }); - let feedback_input = cx.new(|cx| { - let mut input = ui::TextInputState::multi_line(window, cx); - input.set_placeholder("Tell us what you think about Cap..."); - input - }); - let license_input = cx.new(|cx| { - let mut input = ui::TextInputState::single_line(window, cx); - input.set_placeholder("License key"); - input - }); + let hint_input = cx.new(|cx| ui::TextInputState::single_line(window, cx)); + let feedback_input = cx.new(|cx| ui::TextInputState::multi_line(window, cx)); + let license_input = cx.new(|cx| ui::TextInputState::single_line(window, cx)); let s3_inputs = std::array::from_fn(|index| { cx.new(|cx| { let mut input = ui::TextInputState::single_line(window, cx); @@ -580,7 +576,7 @@ impl SettingsWindow { match kind { MenuKind::S3Provider => S3_PROVIDERS .iter() - .map(|(value, label)| ui::MenuItem::new(*label, *value == self.pages.s3.provider)) + .map(|(value, label)| ui::MenuItem::new(label(), *value == self.pages.s3.provider)) .collect(), MenuKind::AutomationTrigger(rule) => { let current = self.rule_at(rule).map(|rule| rule.trigger); @@ -593,10 +589,13 @@ impl SettingsWindow { } MenuKind::AutomationMatchMode(rule) => { let current = self.rule_at(rule).map(|rule| rule.match_mode); - [(MatchMode::All, "Match all"), (MatchMode::Any, "Match any")] - .iter() - .map(|(mode, label)| ui::MenuItem::new(*label, Some(*mode) == current)) - .collect() + [ + (MatchMode::All, tr::settings::automations::match_all()), + (MatchMode::Any, tr::settings::automations::match_any()), + ] + .iter() + .map(|(mode, label)| ui::MenuItem::new(*label, Some(*mode) == current)) + .collect() } MenuKind::AutomationConditionType(rule, index) => { let current = self @@ -618,7 +617,7 @@ impl SettingsWindow { }; CAPTURE_TARGETS .iter() - .map(|(target, label)| ui::MenuItem::new(*label, Some(*target) == current)) + .map(|(target, label)| ui::MenuItem::new(label(), Some(*target) == current)) .collect() } MenuKind::AutomationConditionMode(rule, index) => { @@ -651,7 +650,7 @@ impl SettingsWindow { }; CLIPBOARD_SOURCES .iter() - .map(|(source, label)| ui::MenuItem::new(*label, Some(*source) == current)) + .map(|(source, label)| ui::MenuItem::new(label(), Some(*source) == current)) .collect() } MenuKind::AutomationWebhookMethod(rule, index) => { @@ -699,7 +698,7 @@ impl SettingsWindow { .unwrap_or(AutomationExportCompression::Web); COMPRESSIONS .iter() - .map(|(compression, label)| ui::MenuItem::new(*label, *compression == current)) + .map(|(compression, label)| ui::MenuItem::new(label(), *compression == current)) .collect() } MenuKind::AutomationPreset(rule, index) => { @@ -896,7 +895,13 @@ impl SettingsWindow { /// `commands.globalMessageDialog(..)`. fn info_dialog(&mut self, message: &str, window: &mut Window, cx: &mut Context) { - let answer = window.prompt(gpui::PromptLevel::Info, message, None, &["Ok"], cx); + let answer = window.prompt( + gpui::PromptLevel::Info, + message, + None, + &[tr::settings::prompt::ok()], + cx, + ); cx.spawn(async move |_this, _cx| { let _ = answer.await; }) @@ -952,8 +957,8 @@ const SHORTCUT_SAVED_NOTICE: std::time::Duration = std::time::Duration::from_sec #[derive(Clone, Copy)] struct OutcomeOption { icon: &'static str, - label: &'static str, - hint: &'static str, + label: fn() -> &'static str, + hint: fn() -> &'static str, } const SCREENSHOT_OUTCOMES: [(store::PostScreenshotBehaviour, OutcomeOption); 4] = [ @@ -961,32 +966,32 @@ const SCREENSHOT_OUTCOMES: [(store::PostScreenshotBehaviour, OutcomeOption); 4] store::PostScreenshotBehaviour::OpenEditor, OutcomeOption { icon: "icons/edit.svg", - label: "Open editor", - hint: "Annotate first", + label: tr::settings::shortcuts::outcome::screenshot_open_editor, + hint: tr::settings::shortcuts::outcome::screenshot_open_editor_hint, }, ), ( store::PostScreenshotBehaviour::Copy, OutcomeOption { icon: "icons/copy.svg", - label: "Copy", - hint: "Paste anywhere", + label: tr::settings::shortcuts::outcome::screenshot_copy, + hint: tr::settings::shortcuts::outcome::screenshot_copy_hint, }, ), ( store::PostScreenshotBehaviour::CopyAndOpenEditor, OutcomeOption { icon: "icons/layers.svg", - label: "Copy and edit", - hint: "Both at once", + label: tr::settings::shortcuts::outcome::screenshot_copy_and_edit, + hint: tr::settings::shortcuts::outcome::screenshot_copy_and_edit_hint, }, ), ( store::PostScreenshotBehaviour::Save, OutcomeOption { icon: "icons/folder-down.svg", - label: "Do nothing", - hint: "Saved to library", + label: tr::settings::shortcuts::outcome::screenshot_do_nothing, + hint: tr::settings::shortcuts::outcome::saved_to_library, }, ), ]; @@ -996,24 +1001,24 @@ const STUDIO_OUTCOMES: [(store::PostStudioBehaviour, OutcomeOption); 3] = [ store::PostStudioBehaviour::OpenEditor, OutcomeOption { icon: "icons/edit.svg", - label: "Open editor", - hint: "Edit right away", + label: tr::settings::shortcuts::outcome::studio_open_editor, + hint: tr::settings::shortcuts::outcome::studio_open_editor_hint, }, ), ( store::PostStudioBehaviour::ShowOverlay, OutcomeOption { icon: "icons/app-window.svg", - label: "Show Cap", - hint: "Back to the recorder", + label: tr::settings::shortcuts::outcome::studio_show_cap, + hint: tr::settings::shortcuts::outcome::studio_show_cap_hint, }, ), ( store::PostStudioBehaviour::DoNothing, OutcomeOption { icon: "icons/folder-down.svg", - label: "Do nothing", - hint: "Saved to library", + label: tr::settings::shortcuts::outcome::studio_do_nothing, + hint: tr::settings::shortcuts::outcome::saved_to_library, }, ), ]; @@ -1024,94 +1029,206 @@ const INSTANT_OUTCOMES: [(bool, OutcomeOption); 2] = [ false, OutcomeOption { icon: "icons/globe.svg", - label: "Open link", - hint: "In your browser", + label: tr::settings::shortcuts::outcome::instant_open_link, + hint: tr::settings::shortcuts::outcome::instant_open_link_hint, }, ), ( true, OutcomeOption { icon: "icons/link.svg", - label: "Copy link only", - hint: "Nothing opens", + label: tr::settings::shortcuts::outcome::instant_copy_link, + hint: tr::settings::shortcuts::outcome::instant_copy_link_hint, }, ), ]; fn shortcut_label(action: &str) -> &'static str { match action { - "screenshotArea" => "Screenshot an area", - "screenshotWindow" => "Screenshot a window", - "screenshotDisplay" => "Screenshot the display", - "startStudioRecording" => "Start Studio recording", - "startInstantRecording" => "Start Instant recording", - "openRecordingPickerArea" => "Record an area", - "openRecordingPickerWindow" => "Record a window", - "openRecordingPickerDisplay" => "Record a display", - "openRecordingPicker" => "Open recording picker", - "cycleRecordingMode" => "Cycle recording mode", - "stopRecording" => "Stop recording", - "togglePauseRecording" => "Pause or resume", - "restartRecording" => "Restart recording", - _ => "Shortcut", + "screenshotArea" => tr::settings::shortcuts::label::screenshot_area(), + "screenshotWindow" => tr::settings::shortcuts::label::screenshot_window(), + "screenshotDisplay" => tr::settings::shortcuts::label::screenshot_display(), + "startStudioRecording" => tr::settings::shortcuts::label::start_studio(), + "startInstantRecording" => tr::settings::shortcuts::label::start_instant(), + "openRecordingPickerArea" => tr::settings::shortcuts::label::record_area(), + "openRecordingPickerWindow" => tr::settings::shortcuts::label::record_window(), + "openRecordingPickerDisplay" => tr::settings::shortcuts::label::record_display(), + "openRecordingPicker" => tr::settings::shortcuts::label::open_picker(), + "cycleRecordingMode" => tr::settings::shortcuts::label::cycle_mode(), + "stopRecording" => tr::settings::shortcuts::label::stop(), + "togglePauseRecording" => tr::settings::shortcuts::label::pause(), + "restartRecording" => tr::settings::shortcuts::label::restart(), + _ => tr::settings::shortcuts::label::fallback(), } } -fn screenshot_outcome(behaviour: store::PostScreenshotBehaviour) -> &'static str { - match behaviour { - store::PostScreenshotBehaviour::OpenEditor => "open it in the editor", - store::PostScreenshotBehaviour::Copy => "copy it to your clipboard", - store::PostScreenshotBehaviour::CopyAndOpenEditor => "copy it and open the editor", - store::PostScreenshotBehaviour::Save => "save it to your library without opening anything", - } -} - -fn picker_outcome(mode: crate::main_window::Mode) -> &'static str { - match mode { - crate::main_window::Mode::Studio => "record it in Studio mode", - crate::main_window::Mode::Instant => "record it in Instant mode", - crate::main_window::Mode::Screenshot => "take a screenshot of it", - } +/// What pressing a shortcut does. Languages word the two uses differently, +/// so each effect is a pair of whole messages rather than one phrase that is +/// capitalized for the description. +#[derive(Clone, Copy)] +struct ShortcutEffect { + /// Completes "Saved. Press ⌘⇧4 in any app to ...". + phrase: fn() -> &'static str, + /// The row's description. + sentence: fn() -> &'static str, } -/// What pressing the shortcut does, as a phrase that completes "Press ⌘⇧4 -/// to ...". It follows the after-capture settings and the current mode, so -/// the row always describes what will actually happen. +/// What pressing the shortcut does. It follows the after-capture settings +/// and the current mode, so the row always describes what will actually +/// happen. fn shortcut_effect( action: &str, settings: &store::GeneralSettings, mode: crate::main_window::Mode, -) -> String { - let screenshot = screenshot_outcome(settings.post_screenshot_behaviour); - let studio_stop = match settings.post_studio_recording_behaviour { - store::PostStudioBehaviour::OpenEditor => ", then open the editor when you stop", - store::PostStudioBehaviour::ShowOverlay | store::PostStudioBehaviour::DoNothing => "", - }; - let instant_stop = if settings.disable_auto_open_links { - ", then copy its share link when you stop" - } else { - ", then copy and open its share link when you stop" - }; - match action { - "screenshotArea" => format!("select an area and {screenshot}"), - "screenshotWindow" => format!("capture the window under your pointer and {screenshot}"), - "screenshotDisplay" => format!("capture the display under your pointer and {screenshot}"), - "startStudioRecording" => { - format!("record your selected screen or window in Studio mode{studio_stop}") - } +) -> Option { + use crate::main_window::Mode; + use store::{PostScreenshotBehaviour as Shot, PostStudioBehaviour as Studio}; + + let shot = settings.post_screenshot_behaviour; + let effect = match action { + "screenshotArea" => match shot { + Shot::OpenEditor => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_area_open_editor, + sentence: tr::settings::shortcuts::sentence::screenshot_area_open_editor, + }, + Shot::Copy => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_area_copy, + sentence: tr::settings::shortcuts::sentence::screenshot_area_copy, + }, + Shot::CopyAndOpenEditor => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_area_copy_and_open_editor, + sentence: tr::settings::shortcuts::sentence::screenshot_area_copy_and_open_editor, + }, + Shot::Save => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_area_save, + sentence: tr::settings::shortcuts::sentence::screenshot_area_save, + }, + }, + "screenshotWindow" => match shot { + Shot::OpenEditor => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_window_open_editor, + sentence: tr::settings::shortcuts::sentence::screenshot_window_open_editor, + }, + Shot::Copy => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_window_copy, + sentence: tr::settings::shortcuts::sentence::screenshot_window_copy, + }, + Shot::CopyAndOpenEditor => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_window_copy_and_open_editor, + sentence: tr::settings::shortcuts::sentence::screenshot_window_copy_and_open_editor, + }, + Shot::Save => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_window_save, + sentence: tr::settings::shortcuts::sentence::screenshot_window_save, + }, + }, + "screenshotDisplay" => match shot { + Shot::OpenEditor => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_display_open_editor, + sentence: tr::settings::shortcuts::sentence::screenshot_display_open_editor, + }, + Shot::Copy => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_display_copy, + sentence: tr::settings::shortcuts::sentence::screenshot_display_copy, + }, + Shot::CopyAndOpenEditor => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_display_copy_and_open_editor, + sentence: + tr::settings::shortcuts::sentence::screenshot_display_copy_and_open_editor, + }, + Shot::Save => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::screenshot_display_save, + sentence: tr::settings::shortcuts::sentence::screenshot_display_save, + }, + }, + "startStudioRecording" => match settings.post_studio_recording_behaviour { + Studio::OpenEditor => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::studio_open_editor, + sentence: tr::settings::shortcuts::sentence::studio_open_editor, + }, + Studio::ShowOverlay | Studio::DoNothing => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::studio, + sentence: tr::settings::shortcuts::sentence::studio, + }, + }, "startInstantRecording" => { - format!("record your selected screen or window in Instant mode{instant_stop}") + if settings.disable_auto_open_links { + ShortcutEffect { + phrase: tr::settings::shortcuts::effect::instant_copy, + sentence: tr::settings::shortcuts::sentence::instant_copy, + } + } else { + ShortcutEffect { + phrase: tr::settings::shortcuts::effect::instant_copy_and_open, + sentence: tr::settings::shortcuts::sentence::instant_copy_and_open, + } + } } - "openRecordingPickerArea" => format!("select an area, then {}", picker_outcome(mode)), - "openRecordingPickerWindow" => format!("choose a window, then {}", picker_outcome(mode)), - "openRecordingPickerDisplay" => format!("choose a display, then {}", picker_outcome(mode)), - "openRecordingPicker" => "open Cap so you can choose what to record".into(), - "cycleRecordingMode" => "switch between Studio, Instant and Screenshot mode".into(), - "stopRecording" => "stop the current recording".into(), - "togglePauseRecording" => "pause the current recording, or resume it".into(), - "restartRecording" => "throw away the current take and start again".into(), - _ => String::new(), - } + "openRecordingPickerArea" => match mode { + Mode::Studio => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_area_studio, + sentence: tr::settings::shortcuts::sentence::picker_area_studio, + }, + Mode::Instant => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_area_instant, + sentence: tr::settings::shortcuts::sentence::picker_area_instant, + }, + Mode::Screenshot => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_area_screenshot, + sentence: tr::settings::shortcuts::sentence::picker_area_screenshot, + }, + }, + "openRecordingPickerWindow" => match mode { + Mode::Studio => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_window_studio, + sentence: tr::settings::shortcuts::sentence::picker_window_studio, + }, + Mode::Instant => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_window_instant, + sentence: tr::settings::shortcuts::sentence::picker_window_instant, + }, + Mode::Screenshot => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_window_screenshot, + sentence: tr::settings::shortcuts::sentence::picker_window_screenshot, + }, + }, + "openRecordingPickerDisplay" => match mode { + Mode::Studio => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_display_studio, + sentence: tr::settings::shortcuts::sentence::picker_display_studio, + }, + Mode::Instant => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_display_instant, + sentence: tr::settings::shortcuts::sentence::picker_display_instant, + }, + Mode::Screenshot => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::picker_display_screenshot, + sentence: tr::settings::shortcuts::sentence::picker_display_screenshot, + }, + }, + "openRecordingPicker" => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::open_picker, + sentence: tr::settings::shortcuts::sentence::open_picker, + }, + "cycleRecordingMode" => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::cycle_mode, + sentence: tr::settings::shortcuts::sentence::cycle_mode, + }, + "stopRecording" => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::stop, + sentence: tr::settings::shortcuts::sentence::stop, + }, + "togglePauseRecording" => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::pause, + sentence: tr::settings::shortcuts::sentence::pause, + }, + "restartRecording" => ShortcutEffect { + phrase: tr::settings::shortcuts::effect::restart, + sentence: tr::settings::shortcuts::sentence::restart, + }, + _ => return None, + }; + Some(effect) } fn description_follows_outcome(action: &str) -> bool { @@ -1119,14 +1236,6 @@ fn description_follows_outcome(action: &str) -> bool { || matches!(action, "startStudioRecording" | "startInstantRecording") } -fn capitalized_sentence(phrase: &str) -> String { - let mut chars = phrase.chars(); - match chars.next() { - Some(first) => format!("{}{}.", first.to_uppercase(), chars.as_str()), - None => String::new(), - } -} - /// The gpui keystroke name for a key, as the W3C `KeyboardEvent.code` string /// the Tauri app stores (`e.code` in hotkeys.tsx, `global_hotkey::Code` in /// hotkeys.rs). `None` for keys neither side can bind. @@ -1188,22 +1297,13 @@ fn shortcut_needs_modifier(hotkey: &Hotkey) -> bool { !(hotkey.meta || hotkey.ctrl || hotkey.alt) && !is_function_key(&hotkey.code) } -const NEEDS_MODIFIER: &str = if cfg!(target_os = "macos") { - "Include ⌘, ⌃ or ⌥ so the shortcut doesn't get in the way of typing." +const NEEDS_MODIFIER: fn() -> &'static str = if cfg!(target_os = "macos") { + tr::settings::shortcuts::needs_modifier_macos } else { - "Include Ctrl, Alt or Win so the shortcut doesn't get in the way of typing." + tr::settings::shortcuts::needs_modifier_other }; -const UNSUPPORTED_KEY: &str = - "That key can't be used in a shortcut. Try a letter, number or F-key."; - -const RECORDING_HINT: &str = "Press the keys you want to use. Esc cancels, Delete removes it."; - -const BLOCKED_WHILE_RECORDING: &str = - "Shortcuts can't be changed while this recording holds its stop key. Stop recording first."; - -const UNAVAILABLE: &str = - "Another app or the system already uses this shortcut. Click to choose a different one."; +const UNSUPPORTED_KEY: fn() -> &'static str = tr::settings::shortcuts::unsupported_key; /// `HotkeyText`'s key list: modifiers in ⌘⌃⌥⇧ order, then the main key. /// [`ui::kbd_symbol`] does the per-OS glyph mapping. @@ -1481,8 +1581,8 @@ impl SettingsWindow { screenshots.push( self.render_outcome_panel( "outcome-screenshot", - "After a screenshot", - "For every screenshot, from a shortcut or from Cap", + tr::settings::shortcuts::after_screenshot::title(), + tr::settings::shortcuts::after_screenshot::hint(), &SCREENSHOT_OUTCOMES .iter() .map(|(value, option)| { @@ -1503,8 +1603,8 @@ impl SettingsWindow { recording.push( self.render_outcome_panel( "outcome-studio", - "After a Studio recording", - "When you stop", + tr::settings::shortcuts::after_studio::title(), + tr::settings::shortcuts::after_studio::hint(), &STUDIO_OUTCOMES .iter() .map(|(value, option)| { @@ -1526,8 +1626,8 @@ impl SettingsWindow { recording.push( self.render_outcome_panel( "outcome-instant", - "After an Instant recording", - "The share link is always copied", + tr::settings::shortcuts::after_instant::title(), + tr::settings::shortcuts::after_instant::hint(), &INSTANT_OUTCOMES .iter() .map(|(disabled, option)| { @@ -1561,21 +1661,21 @@ impl SettingsWindow { vec![ self.section( - "Screenshots", - Some("Shortcuts work from any app. Click one to change it."), + tr::settings::shortcuts::screenshots_title(), + Some(tr::settings::shortcuts::screenshots_description()), None, vec![card("shortcuts-screenshots", screenshots, cx)], ) .into_any_element(), self.section( - "Recording", - Some("Start a recording without opening Cap first."), + tr::settings::shortcuts::recording_title(), + Some(tr::settings::shortcuts::recording_description()), None, vec![card("shortcuts-recording", recording, cx)], ) .into_any_element(), self.section( - "While recording", + tr::settings::shortcuts::while_recording_title(), None, None, vec![card("shortcuts-while-recording", while_recording, cx)], @@ -1654,14 +1754,14 @@ impl SettingsWindow { .text_size(px(12.)) .font_weight(gpui::FontWeight::MEDIUM) .text_color(ink) - .child(option.label), + .child((option.label)()), ) .child( div() .text_size(px(11.)) .line_height(px(14.)) .text_color(theme.settings_muted()) - .child(option.hint), + .child((option.hint)()), ), ) .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) @@ -1728,29 +1828,42 @@ impl SettingsWindow { let blocked = self.pages.shortcut_blocked == Some(action) && crate::hotkeys::editing_blocked(cx); let (detail, detail_color, detail_icon): (String, Hsla, Option<(&str, Hsla)>) = if blocked { - (BLOCKED_WHILE_RECORDING.into(), red, None) + ( + tr::settings::shortcuts::blocked_while_recording().into(), + red, + None, + ) } else if let Some(listening) = listening { match listening.error { - Some(error) => (error.into(), red, None), - None => (RECORDING_HINT.into(), theme.settings_muted(), None), + Some(error) => (error().into(), red, None), + None => ( + tr::settings::shortcuts::recording_hint().into(), + theme.settings_muted(), + None, + ), } } else if unavailable && binding.is_some() { - (UNAVAILABLE.into(), amber, None) + (tr::settings::shortcuts::unavailable().into(), amber, None) } else if let (Some(saved), Some(binding)) = (saved, binding.as_ref()) { - let mut text = format!( - "Saved. Press {} in any app to {effect}.", - hotkey_inline_text(binding) - ); - if let Some(from) = saved.moved_from { - text.push_str(&format!(" Removed from {}.", shortcut_label(from))); - } + let keys = hotkey_inline_text(binding); + let phrase = effect.map_or("", |effect| (effect.phrase)()); + let text = match saved.moved_from { + Some(from) => { + tr::settings::shortcuts::saved_moved(keys, phrase, shortcut_label(from)) + } + None => tr::settings::shortcuts::saved(keys, phrase), + }; ( text, theme.settings_text(), Some(("icons/circle-check.svg", theme.settings_text())), ) } else { - (capitalized_sentence(&effect), theme.settings_muted(), None) + ( + effect.map_or_else(String::new, |effect| (effect.sentence)().to_string()), + theme.settings_muted(), + None, + ) }; let group: SharedString = format!("shortcut-row-{action}").into(); @@ -1887,7 +2000,7 @@ impl SettingsWindow { .px(px(4.)) .text_size(px(12.)) .text_color(theme.settings_muted()) - .child("Type shortcut…"), + .child(tr::settings::shortcuts::type_shortcut()), ); } else { field = field.child( @@ -1923,7 +2036,7 @@ impl SettingsWindow { .px(px(4.)) .text_size(px(12.)) .text_color(theme.settings_muted()) - .child("Record shortcut"), + .child(tr::settings::shortcuts::record_shortcut()), ) .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .on_click(cx.listener(move |this, _, window, cx| { @@ -2015,12 +2128,12 @@ impl SettingsWindow { .text_size(px(12.)) .line_height(px(18.)) .text_color(Hsla::from(theme.red_9)) - .child(format!("Couldn't load CLI status: {error}")), + .child(tr::settings::cli::load_failed(error)), ) .child(div().child(self.button( "cli-retry", (ui::ButtonVariant::Gray, None), - "Retry", + tr::settings::cli::retry(), false, cx, |this, window, cx| this.cli_refresh(window, cx), @@ -2033,10 +2146,8 @@ impl SettingsWindow { vec![ self.section( - "Command Line", - Some( - "Install the Cap command for terminals, agents, scripts, and local automation.", - ), + tr::settings::cli::title(), + Some(tr::settings::cli::description()), None, vec![self.card(true).child(body).into_any_element()], ) @@ -2055,15 +2166,19 @@ impl SettingsWindow { let install_label = if installing { if status.installed { - "Repairing..." + tr::settings::cli::repairing() } else { - "Installing..." + tr::settings::cli::installing() } } else if status.installed { - "Repair" + tr::settings::cli::repair() } else { - "Install CLI" + tr::settings::cli::install() }; + let installs_command = tr::settings::cli::installs_command(COMMAND_MARKER); + let (before_command, after_command) = installs_command + .split_once(COMMAND_MARKER) + .unwrap_or((installs_command.as_str(), "")); // ``. let code_span = |text: String| { @@ -2086,9 +2201,9 @@ impl SettingsWindow { .gap(px(4.)) .min_w_0() .child(div().text_size(px(13.)).child(if status.installed { - "Installed" + tr::settings::cli::installed() } else { - "Not installed" + tr::settings::cli::not_installed() })) .child( div() @@ -2099,9 +2214,9 @@ impl SettingsWindow { .text_size(px(12.)) .line_height(px(16.)) .text_color(theme.settings_muted()) - .child("The desktop app installs a local\u{a0}") + .child(before_command.to_string()) .child(code_span("cap".into()).text_size(px(12.))) - .child("\u{a0}command that points back to the bundled CLI."), + .child(after_command.to_string()), ), ) .child( @@ -2115,9 +2230,9 @@ impl SettingsWindow { "cli-remove", (ui::ButtonVariant::Gray, None), if uninstalling { - "Removing..." + tr::settings::cli::removing() } else { - "Remove" + tr::settings::cli::remove() }, uninstalling, cx, @@ -2144,7 +2259,7 @@ impl SettingsWindow { .min_w_0() .child( div() - .w(px(64.)) + .min_w(px(64.)) .flex_shrink_0() .text_size(px(12.)) .text_color(theme.settings_muted()) @@ -2177,8 +2292,14 @@ impl SettingsWindow { .flex() .flex_col() .gap(px(8.)) - .child(path_row("Command", status.shim_path.clone())) - .child(path_row("Target", status.target_path.clone())), + .child(path_row( + tr::settings::cli::command(), + status.shim_path.clone(), + )) + .child(path_row( + tr::settings::cli::target(), + status.target_path.clone(), + )), ) .when_some(status.conflict.clone(), |this, conflict| { // `rounded-lg border border-red-300/40 bg-red-500/10 px-3 py-2 @@ -2213,20 +2334,14 @@ impl SettingsWindow { .text_size(px(12.)) .line_height(px(18.)) .text_color(theme.settings_muted()) - .child( - "Added cap to your PATH. Restart your terminal to use it, or run \ - this now:", - ) + .child(tr::settings::cli::path_configured()) .into_any_element() } else { div() .text_size(px(12.)) .line_height(px(18.)) .text_color(theme.settings_muted()) - .child(format!( - "Add {} to your PATH to use cap from a new terminal.", - status.path_entry - )) + .child(tr::settings::cli::path_missing(&status.path_entry)) .into_any_element() }; this.child( @@ -2265,9 +2380,9 @@ impl SettingsWindow { "cli-copy-path", (ui::ButtonVariant::Gray, None), if self.pages.cli_copied { - "Copied" + tr::common::copied() } else { - "Copy" + tr::settings::cli::copy() }, false, cx, @@ -2372,10 +2487,13 @@ impl SettingsWindow { let theme = self.theme; let hints = self.pages.hints.clone(); let save_label = match self.pages.hint_save { - HintSave::Saving => "Saving...", - HintSave::Saved => "Saved", + HintSave::Saving => tr::settings::transcription::saving(), + HintSave::Saved => tr::settings::transcription::saved(), HintSave::Idle => "", }; + self.pages.hint_input.update(cx, |input, _| { + input.set_placeholder(tr::settings::transcription::placeholder()) + }); let header = div() .flex() @@ -2389,15 +2507,17 @@ impl SettingsWindow { .flex_col() .gap(px(2.)) .min_w_0() - .child(div().text_size(px(13.)).child("Remembered terms")) + .child( + div() + .text_size(px(13.)) + .child(tr::settings::transcription::terms_title()), + ) .child( div() .text_size(px(12.)) .line_height(px(16.)) .text_color(theme.settings_muted()) - .child( - "Add one term at a time to reduce typos and formatting mistakes.", - ), + .child(tr::settings::transcription::terms_description()), ), ) .child( @@ -2410,7 +2530,7 @@ impl SettingsWindow { this.child(self.button( "hints-clear", (ui::ButtonVariant::Gray, None), - "Clear", + tr::settings::transcription::clear(), false, cx, |this, _window, cx| { @@ -2444,7 +2564,7 @@ impl SettingsWindow { .child(self.button( "hints-add", (ui::ButtonVariant::Primary, Some("icons/plus.svg")), - "Add", + tr::common::add(), add_disabled, cx, |this, _window, cx| this.transcription_add_hint(cx), @@ -2462,16 +2582,13 @@ impl SettingsWindow { .text_size(px(12.)) .line_height(px(18.)) .text_color(theme.settings_muted()) - .child("These hints are applied when you generate captions in the editor."), + .child(tr::settings::transcription::applied_note()), ); let mut sections = vec![ self.section( - "Transcription", - Some( - "Add names, spellings, domains, and capitalization preferences that caption \ - generation should keep in mind.", - ), + tr::settings::transcription::title(), + Some(tr::settings::transcription::description()), None, vec![card.into_any_element()], ) @@ -2482,11 +2599,7 @@ impl SettingsWindow { let count = div() .text_size(px(12.)) .text_color(theme.settings_muted()) - .child(format!( - "{} {}", - hints.len(), - if hints.len() == 1 { "item" } else { "items" } - )) + .child(tr::settings::transcription::count(hints.len())) .into_any_element(); let chips = div().flex().flex_row().flex_wrap().gap(px(8.)).children( hints.into_iter().enumerate().map(|(index, hint)| { @@ -2523,7 +2636,7 @@ impl SettingsWindow { sections.push( self.section( - "Active hints", + tr::settings::transcription::active_title(), None, Some(count), vec![self.card(true).child(chips).into_any_element()], @@ -2550,9 +2663,9 @@ pub(crate) enum SwitchBack { /// failed and hand the user back their app with a pointer at the terminal. const CLASSIC_WAIT_TIMEOUT: Duration = Duration::from_secs(10 * 60); -const SWITCH_SENTENCES: &[&str] = &[ - "Switching back to the classic Cap app.", - "Your recordings and settings stay exactly where they are.", +const SWITCH_SENTENCES: &[fn() -> &'static str] = &[ + tr::settings::experimental::switch::switching, + tr::settings::experimental::switch::data_stays, ]; const SWITCH_SENTENCE_MS: u64 = 2000; const SWITCH_FADE_MS: f32 = 400.; @@ -2610,21 +2723,17 @@ impl SettingsWindow { // left untouched in the shared store -- the Tauri app still uses it. let mut sections = vec![ self.section( - "Reliability", + tr::settings::experimental::reliability(), None, None, vec![ self.rows(vec![ self.setting_row( - "Out-of-process muxer", - Some( - "Run the fragmented-MP4 muxer in an isolated subprocess so muxer \ - crashes can't take down your recording. Requires the bundled \ - cap-muxer binary.", - ), + tr::settings::experimental::muxer(), + Some(tr::settings::experimental::muxer_description()), self.toggle( "out-of-process-muxer", - "Out-of-process muxer", + tr::settings::experimental::muxer(), self.settings.out_of_process_muxer, cx, |this, cx| { @@ -2645,8 +2754,8 @@ impl SettingsWindow { if dev_supervisor_available() { sections.push( self.section( - "Developer", - Some("Only shown in development builds."), + tr::settings::experimental::developer(), + Some(tr::settings::experimental::developer_description()), None, vec![self.rows(vec![self.classic_app_row(cx)]).into_any_element()], ) @@ -2662,14 +2771,11 @@ impl SettingsWindow { fn classic_app_row(&self, cx: &mut Context) -> gpui::AnyElement { let checked = !matches!(self.pages.switch_back, Some(SwitchBack::Running(_))); self.setting_row( - "Use native app", - Some( - "Turn off to close this app and reopen the classic Tauri app in the dev harness. \ - Recordings and settings are shared.", - ), + tr::settings::experimental::native_app(), + Some(tr::settings::experimental::native_app_description()), self.toggle( "enable-gpui-app", - "Use native app", + tr::settings::experimental::native_app(), checked, cx, |this, cx| { @@ -2725,7 +2831,7 @@ impl SettingsWindow { .text_size(px(15.)) .text_center() .text_color(gpui::hsla(0., 0., 1., alpha)) - .child(SWITCH_SENTENCES[index]), + .child(SWITCH_SENTENCES[index]()), ); } SwitchBack::WaitingForClassic => { @@ -2737,7 +2843,7 @@ impl SettingsWindow { .font_weight(FontWeight::SEMIBOLD) .text_center() .text_color(white) - .child("Opening the classic Cap app"), + .child(tr::settings::experimental::switch::opening()), ) .child( div() @@ -2745,7 +2851,7 @@ impl SettingsWindow { .text_size(px(14.)) .text_center() .text_color(gpui::hsla(0., 0., 1., 0.7)) - .child("This window will close when the classic Cap app is ready."), + .child(tr::settings::experimental::switch::opening_detail()), ); } SwitchBack::Failed(error) => { @@ -2783,9 +2889,9 @@ impl SettingsWindow { .hover(|style| style.bg(gpui::hsla(0., 0., 1., 0.12))) .on_click(cx.listener(|this, _, _window, cx| this.cancel_switch_back(cx))) .child(if matches!(switch, SwitchBack::Failed(_)) { - "Close" + tr::common::close() } else { - "Cancel" + tr::common::cancel() }), ) .into_any_element(), @@ -2841,8 +2947,7 @@ impl SettingsWindow { } self.pages.switch_back = Some(SwitchBack::Failed( - "Finish your recording, export, upload, import, or transcription task before switching to the classic app." - .to_string(), + tr::settings::experimental::switch::busy().to_string(), )); cx.notify(); true @@ -2856,7 +2961,7 @@ impl SettingsWindow { } if !dev_supervisor_available() { self.pages.switch_back = Some(SwitchBack::Failed( - "Couldn't find the classic Cap app to switch back to.".to_string(), + tr::settings::experimental::switch::not_found().to_string(), )); cx.notify(); return; @@ -2869,7 +2974,7 @@ impl SettingsWindow { if !store::set_store_setting(GENERAL_SETTINGS, "enableGpuiApp", Value::Bool(false)) { self.pages.switch_back = Some(SwitchBack::Failed( - "Couldn't save your app preference. Cap is still open.".to_string(), + tr::settings::experimental::switch::save_failed().to_string(), )); cx.notify(); return; @@ -2887,7 +2992,7 @@ impl SettingsWindow { .await; let mut failure = result .err() - .map(|error| format!("Couldn't open the classic Cap app: {error}")); + .map(tr::settings::experimental::switch::open_failed); let started = std::time::Instant::now(); while failure.is_none() { match store::classic_pending_path().try_exists() { @@ -2898,17 +3003,12 @@ impl SettingsWindow { } Ok(true) => {} Err(error) => { - failure = Some(format!( - "Couldn't check whether the classic Cap app opened: {error}" - )); + failure = Some(tr::settings::experimental::switch::check_failed(error)); break; } } if started.elapsed() >= CLASSIC_WAIT_TIMEOUT { - failure = Some( - "The classic app hasn't opened. Check the dev terminal for build errors, then try again." - .to_string(), - ); + failure = Some(tr::settings::experimental::switch::timed_out().to_string()); break; } cx.background_executor() @@ -2916,7 +3016,7 @@ impl SettingsWindow { .await; } let message = - failure.unwrap_or_else(|| "Couldn't open the classic Cap app.".to_string()); + failure.unwrap_or_else(|| tr::settings::experimental::switch::failed().to_string()); tracing::error!("{message}"); store::clear_classic_pending(); store::set_store_setting(GENERAL_SETTINGS, "enableGpuiApp", Value::Bool(true)); @@ -2934,6 +3034,11 @@ impl SettingsWindow { // Feedback (feedback.tsx) // --------------------------------------------------------------------------- +/// Written into the diagnostic report, which stays English; the page shows +/// `settings.feedback.diagnostic.no_cli_sync` in its place. +const NO_SELFTEST_BINARY: &str = + "The Cap command-line tool was not found next to this build, so the sync test could not run."; + impl SettingsWindow { /// The page's two one-shot probes: the OS string and whether there is a /// `cap` sidecar to run the sync test with. Both stat the filesystem, so @@ -2968,11 +3073,8 @@ impl SettingsWindow { let auth = store::auth_snapshot(); let Some(token) = auth.token else { // `protectedHeaders()`'s throw. - self.pages.feedback = FeedbackStatus::Error( - "Please sign in to continue. Alternatively, email hello@cap.so or join our \ - Discord at cap.link/discord" - .to_string(), - ); + self.pages.feedback = + FeedbackStatus::Error(tr::settings::sign_in_required::body().to_string()); cx.notify(); return; }; @@ -3015,7 +3117,9 @@ impl SettingsWindow { input.update(cx, |input, cx| input.set_disabled(false, cx)); this.pages.feedback = match result { Ok((200, _)) => FeedbackStatus::Success, - Ok(_) => FeedbackStatus::Error("Failed to submit feedback".to_string()), + Ok(_) => { + FeedbackStatus::Error(tr::settings::feedback::submit_failed().to_string()) + } Err(error) => FeedbackStatus::Error(error.text()), }; }, @@ -3039,7 +3143,7 @@ impl SettingsWindow { // would land in whatever the user is currently recording. if crate::session::RecordingSession::recording_in_flight(cx) { self.pages.diagnostic = DiagnosticStatus::Error( - "Stop the current recording before running a diagnostic.".to_string(), + tr::settings::feedback::diagnostic::stop_recording_first().to_string(), ); cx.notify(); return; @@ -3047,18 +3151,14 @@ impl SettingsWindow { // A stray click costs a couple of minutes of hijacked screen and loud // beeps, so it is confirmed the way the Tauri app confirms it. - let message = format!( - "Cap will take over your screen with a flashing pattern and play loud beeps for \ - about {} seconds per pipeline. Take your headphones off, leave the volume \ - audible, and leave the machine alone until it finishes.", - diagnostics::DEFAULT_DURATION_SECS - ); + let message = + tr::settings::feedback::diagnostic::confirm_body(diagnostics::DEFAULT_DURATION_SECS); cx.spawn_in(window, async move |this, cx| { if !crate::platform::confirm_dialog( - "Run diagnostic?", + tr::settings::feedback::diagnostic::confirm_title(), &message, - "Run Diagnostic", - "Cancel", + tr::settings::feedback::diagnostic::run(), + tr::common::cancel(), true, ) { return; @@ -3120,14 +3220,7 @@ impl SettingsWindow { } // No sidecar: the environment half of the report is // still worth having, with the reason recorded in it. - None => ( - None, - Some( - "The Cap command-line tool was not found next to this build, so \ - the sync test could not run." - .to_string(), - ), - ), + None => (None, Some(NO_SELFTEST_BINARY.to_string())), }; if cancel.load(Ordering::Relaxed) { return Err(diagnostics::CANCELLED.to_string()); @@ -3309,7 +3402,7 @@ impl SettingsWindow { }; let result = task .await - .unwrap_or_else(|_| Err("The upload was interrupted".to_string())); + .unwrap_or_else(|_| Err(tr::settings::feedback::upload_interrupted().to_string())); this.update_in(cx, |this, window, cx| { *this.upload_status_mut(target) = match result { @@ -3329,9 +3422,12 @@ impl SettingsWindow { let theme = self.theme; let (text, color): (String, Hsla) = match self.upload_status(target) { UploadStatus::Idle => return None, - UploadStatus::Pending => ("Uploading...".to_string(), theme.settings_muted()), + UploadStatus::Pending => ( + tr::settings::feedback::uploading().to_string(), + theme.settings_muted(), + ), UploadStatus::Success => ( - "Sent. Thank you -- this helps us find the problem.".to_string(), + tr::settings::feedback::upload_sent().to_string(), theme.settings_muted(), ), UploadStatus::Error(error) => (error.clone(), Hsla::from(theme.red_9)), @@ -3359,19 +3455,14 @@ impl SettingsWindow { .line_height(px(18.)) .text_color(theme.settings_text()) .font_weight(FontWeight::MEDIUM) - .child("The test takes over your screen and makes noise."), + .child(tr::settings::feedback::diagnostic::warning_title()), ) .child( div() .text_size(px(12.)) .line_height(px(18.)) .text_color(theme.settings_muted()) - .child( - "A fullscreen window flashes black and white while 1kHz beeps play \ - through your speakers, and Cap records it and measures how far the \ - picture and the sound have drifted apart. Leave the machine alone \ - while it runs. Both modes together take a couple of minutes.", - ), + .child(tr::settings::feedback::diagnostic::warning_body()), ); let mode = self.pages.diagnostic_mode; @@ -3394,19 +3485,16 @@ impl SettingsWindow { let options = self.rows(vec![ self.setting_row( - "Recording mode", - Some("Which pipeline to test. Both runs the studio leg and then the instant one."), + tr::settings::feedback::diagnostic::mode_label(), + Some(tr::settings::feedback::diagnostic::mode_description()), mode_control.into_any_element(), ), self.setting_row( - "Test microphone", - Some( - "Also record your microphone and check that it hears the beeps in time. \ - Needs speakers the mic can actually hear.", - ), + tr::settings::feedback::diagnostic::mic_label(), + Some(tr::settings::feedback::diagnostic::mic_description()), self.toggle( "diagnostic-mic", - "Test microphone", + tr::settings::feedback::diagnostic::mic_label(), self.pages.diagnostic_mic, cx, |this, cx| { @@ -3429,7 +3517,7 @@ impl SettingsWindow { ui::ButtonVariant::Dark, ui::ButtonSize::Md, ) - .label("Run Diagnostic") + .label(tr::settings::feedback::diagnostic::run()) .on_click( cx.listener(|this, _, window, cx| this.diagnostic_run(window, cx)), ), @@ -3442,10 +3530,7 @@ impl SettingsWindow { div() .text_size(px(11.)) .text_color(theme.settings_muted()) - .child( - "The Cap command-line tool was not found next to this build, so \ - the report will cover your environment only.", - ), + .child(tr::settings::feedback::diagnostic::no_cli()), ); } } @@ -3465,7 +3550,7 @@ impl SettingsWindow { ui::ButtonVariant::Gray, ui::ButtonSize::Md, ) - .label("Cancel") + .label(tr::common::cancel()) .disabled_settings( &theme, self.pages.diagnostic_cancel.is_none() @@ -3492,12 +3577,17 @@ impl SettingsWindow { ); } if let Some(error) = sync_test_error { + let error = if error == NO_SELFTEST_BINARY { + tr::settings::feedback::diagnostic::no_cli_sync().to_string() + } else { + error.clone() + }; body = body.child( div() .text_size(px(12.)) .line_height(px(18.)) .text_color(Hsla::from(theme.red_9)) - .child(error.clone()), + .child(error), ); } @@ -3509,9 +3599,9 @@ impl SettingsWindow { ui::ButtonSize::Md, ) .label(if self.pages.diagnostic_upload == UploadStatus::Pending { - "Sending..." + tr::settings::feedback::diagnostic::sending() } else { - "Send to Cap" + tr::settings::feedback::diagnostic::send() }) .disabled_settings( &theme, @@ -3531,7 +3621,7 @@ impl SettingsWindow { ui::ButtonVariant::Gray, ui::ButtonSize::Md, ) - .label("Show File") + .label(tr::settings::feedback::diagnostic::show_file()) .on_click(move |_, _, cx| crate::library::reveal_path(&path, cx)), ); } @@ -3542,7 +3632,7 @@ impl SettingsWindow { ui::ButtonVariant::Gray, ui::ButtonSize::Md, ) - .label("Run Again") + .label(tr::settings::feedback::diagnostic::run_again()) .on_click(cx.listener(|this, _, window, cx| this.diagnostic_run(window, cx))), ); body = body @@ -3566,7 +3656,7 @@ impl SettingsWindow { ui::ButtonVariant::Gray, ui::ButtonSize::Md, ) - .label("Try Again") + .label(tr::settings::feedback::diagnostic::try_again()) .on_click( cx.listener(|this, _, window, cx| this.diagnostic_run(window, cx)), ), @@ -3594,24 +3684,28 @@ impl SettingsWindow { // failures, so they stay in the muted grey. let (label, fill, text): (&str, Hsla, Hsla) = match verdict { Some("pass") => ( - "PASS", + tr::settings::feedback::diagnostic::verdict::pass(), gpui::rgb(0x22c55e).into(), gpui::rgb(0x4ade80).into(), ), Some("warn") => ( - "WARN", + tr::settings::feedback::diagnostic::verdict::warn(), gpui::rgb(0xf59e0b).into(), gpui::rgb(0xfbbf24).into(), ), - Some("fail") => ("FAIL", theme.red_9.into(), theme.red_9.into()), + Some("fail") => ( + tr::settings::feedback::diagnostic::verdict::fail(), + theme.red_9.into(), + theme.red_9.into(), + ), Some("inconclusive") => ( - "INCONCLUSIVE", + tr::settings::feedback::diagnostic::verdict::inconclusive(), theme.settings_muted(), theme.settings_muted(), ), // No sync test in the report: the environment half still ran. _ => ( - "ENVIRONMENT ONLY", + tr::settings::feedback::diagnostic::verdict::environment_only(), theme.settings_muted(), theme.settings_muted(), ), @@ -3640,6 +3734,9 @@ impl SettingsWindow { let pending = self.pages.feedback == FeedbackStatus::Pending; // `disabled={feedback().trim().length < 4}`. let submit_disabled = pending || self.pages.feedback_draft.trim().chars().count() < 4; + self.pages.feedback_input.update(cx, |input, _| { + input.set_placeholder(tr::settings::feedback::placeholder()) + }); // The `