From f16f41394291e7d18d9cf1b648ddfb0d652df04f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 20:09:16 +0000 Subject: [PATCH 1/5] Fix #372: masking tile labels and long mask names stay inside their boxes The Create New Mask tiles painted their (untranslated) labels with an unconstrained Painter::text into tiles capped at 52 pt, so Background and Luminance ran past their tiles, and translations would more so. The grid now measures the translated labels: four 52 pt tiles when they fit (at 10.5 pt, or 9.5 pt before dropping a column), else three or two, a tile growing past 52 pt for a long label; only in a panel too narrow for even that is a label cut short (full text on hover). A mask row's name is cut short with an ellipsis before the eye (full name on hover). The Describe field places the translated Select button first and gives the field what is left, instead of reserving a fixed 64 pt. Status lines wrap explicitly. The painted label and name rects are registered (maskNewLabel:*, maskName:*), so the sidebar test checks the text, not just the widgets, at four panel widths with a long name and a long prompt. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8 --- crates/ui-egui/src/panels/masking.rs | 108 +++++++++++++++++++++------ crates/ui-egui/src/tests_panels.rs | 20 +++++ crates/ui-egui/src/widgets.rs | 9 +++ 3 files changed, 114 insertions(+), 23 deletions(-) diff --git a/crates/ui-egui/src/panels/masking.rs b/crates/ui-egui/src/panels/masking.rs index 31dbbac6..b73e91d8 100644 --- a/crates/ui-egui/src/panels/masking.rs +++ b/crates/ui-egui/src/panels/masking.rs @@ -1,6 +1,6 @@ //! The Masking panel: create masks, list them, edit components and local adjustments. -use egui::{Align2, Rect, Sense, Stroke, pos2, vec2}; +use egui::{Rect, Sense, Stroke, pos2, vec2}; use lightcraft_catalog::PhotoId; use lightcraft_develop::{ControlSpec, LocalAdjustments, MaskShape, Section, Track}; use serde_json::json; @@ -10,7 +10,7 @@ use super::right::header; use crate::LightcraftApp; use crate::icons::{Icon, paint}; use crate::theme::Tokens; -use crate::widgets::{divider, icon_button, register, slider, text_button}; +use crate::widgets::{divider, icon_button, one_line, register, slider, text_button}; const fn spec(id: &'static str, label: &'static str, min: f64, max: f64, step: f64, decimals: u8, track: Track) -> ControlSpec { ControlSpec { id, label, section: Section::Light, min, max, default: 0.0, step, decimals, track } @@ -61,12 +61,29 @@ fn kind_label(s: &MaskShape) -> (&'static str, Icon) { const TILE: f32 = 52.0; const TILE_GAP: f32 = 6.0; +/// Room left and right of a tile's label. +const TILE_PAD: f32 = 3.0; -/// Columns and tile width of the Create New Mask grid in `width`: four tiles of up to 52 pt (at -/// least 48, so the labels fit), else three (narrowed if even those don't fit). -fn tile_layout(width: f32) -> (usize, f32) { - let tile = |n: f32| ((width - (n - 1.0) * TILE_GAP) / n).floor().min(TILE); - if tile(4.0) >= 48.0 { (4, tile(4.0)) } else { (3, tile(3.0).max(24.0)) } +/// Columns, tile width and label size of the Create New Mask grid in `width`, `label(size)` being +/// the widest label at a font size: four tiles of up to 52 pt (at least 48), else three, else two — +/// the first that fits the labels at 10.5 pt, or at 9.5 pt before a column is dropped. A tile grows +/// past 52 pt for a long (translated) label. In a panel too narrow even for that, two tiles whose +/// labels are cut short. +fn tile_layout(width: f32, label: impl Fn(f32) -> f32) -> (usize, f32, f32) { + let room = |n: usize| ((width - (n as f32 - 1.0) * TILE_GAP) / n as f32).floor(); + for n in [4, 3, 2] { + let w = room(n); + if n == 4 && w < 48.0 { + continue; + } + for size in [10.5, 9.5] { + let need = (label(size) + 2.0 * TILE_PAD).ceil(); + if need <= w { + return (n, w.min(TILE.max(need)), size); + } + } + } + (2, room(2).max(24.0), 9.5) } pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) { @@ -88,15 +105,29 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) { ("luminanceRange", "Luminance", Icon::Sliders), ("colorRange", "Color", Icon::Picker), ]; - // four 52 pt tiles a row when they fit, else as many as fit (at least three, shrunk) - let (cols, tile) = tile_layout(ui.available_width()); + // four 52 pt tiles a row when they and their labels fit, else fewer (wider for a long label) + let widest = |size: f32| { + let font = t.font(size); + tiles + .iter() + .map(|(_, l, _)| ui.painter().layout_no_wrap(crate::i18n::tr(l).to_string(), font.clone(), t.text_dim).size().x) + .fold(0.0, f32::max) + }; + let (cols, tile, size) = tile_layout(ui.available_width(), widest); egui::Grid::new("mask-tiles").spacing(vec2(TILE_GAP, TILE_GAP)).show(ui, |ui| { for (i, (kind, label, icon)) in tiles.iter().enumerate() { let (r, resp) = ui.allocate_exact_size(vec2(tile, 52.0), Sense::click()); register(ui.ctx(), format!("maskNew:{kind}"), r); ui.painter().rect_filled(r, 4.0, if resp.hovered() { t.hover } else { t.inset }); paint(ui.painter(), Rect::from_center_size(r.center() - vec2(0.0, 7.0), vec2(20.0, 20.0)), *icon, t.text_label); - ui.painter().text(pos2(r.center().x, r.bottom() - 9.0), Align2::CENTER_CENTER, *label, t.font(10.5), t.text_dim); + // (cut short only in a panel too narrow for the label: the full name on hover) + let label = crate::i18n::tr(label); + let g = one_line(ui.painter(), label, t.font(size), t.text_dim, tile - 2.0 * TILE_PAD); + let elided = g.elided; + let at = pos2(r.center().x, r.bottom() - 9.0) - g.size() / 2.0; + register(ui.ctx(), format!("maskNewLabel:{kind}"), Rect::from_min_size(at, g.size())); + ui.painter().galley(at, g, t.text_dim); + let resp = if elided { resp.on_hover_text(label) } else { resp }; if resp.clicked() { match *kind { "colorRange" => { @@ -136,7 +167,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) { }; if let Some(status) = status { ui.add_space(4.0); - ui.label(egui::RichText::new(crate::i18n::tr(status)).color(t.text_dim)); + ui.add(egui::Label::new(egui::RichText::new(crate::i18n::tr(status)).color(t.text_dim)).wrap()); ui.ctx().request_repaint_after(std::time::Duration::from_millis(250)); } // the model download, while its dialog is closed @@ -207,13 +238,14 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) { ); let icon = m.components.first().map(|c| kind_label(&c.shape).1).unwrap_or(Icon::Mask); paint(ui.painter(), Rect::from_min_size(r.min + vec2(8.0, 7.0), vec2(16.0, 16.0)), icon, t.text_label); - ui.painter().text( - pos2(r.left() + 32.0, r.center().y), - Align2::LEFT_CENTER, - &m.name, - t.font(13.0), - if m.visible { t.text } else { t.text_disabled }, - ); + // a long name is cut short (with …) before the eye, the full name on hover + let color = if m.visible { t.text } else { t.text_disabled }; + let g = one_line(ui.painter(), &m.name, t.font(13.0), color, r.width() - 32.0 - 32.0); + let elided = g.elided; + let at = pos2(r.left() + 32.0, r.center().y - g.size().y / 2.0); + register(ui.ctx(), format!("maskName:{}", m.id), Rect::from_min_size(at, g.size())); + ui.painter().galley(at, g, color); + let resp = if elided { resp.on_hover_text(&m.name) } else { resp }; // show / hide on hover (and always while hidden) // (the pointer test, not `hovered`: over the eye, the row itself no longer counts as hovered) if ui.rect_contains_pointer(r) || !m.visible { @@ -706,11 +738,15 @@ fn describe_field(app: &mut LightcraftApp, ui: &mut egui::Ui, new_mask: bool) { }; ui.label(crate::i18n::tr(prompt)); let mut submit = false; - ui.horizontal(|ui| { + // the (translated) Select button is placed first, at the right; the field takes what is left + ui.allocate_ui_with_layout(vec2(ui.available_width(), 24.0), egui::Layout::right_to_left(egui::Align::Center), |ui| { + if text_button(ui, "maskDescribeGo", crate::i18n::tr("Select"), false).clicked() { + submit = true; + } let r = ui.add( egui::TextEdit::singleline(&mut text) .hint_text(crate::i18n::tr("e.g. sky · the red car · car, road")) - .desired_width(ui.available_width() - 64.0), + .desired_width(ui.available_width()), ); register(ui.ctx(), "maskDescribe", r.rect); if !r.has_focus() && !r.lost_focus() && text.is_empty() { @@ -719,9 +755,6 @@ fn describe_field(app: &mut LightcraftApp, ui: &mut egui::Ui, new_mask: bool) { if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { submit = true; } - if text_button(ui, "maskDescribeGo", crate::i18n::tr("Select"), false).clicked() { - submit = true; - } }); if ui.input(|i| i.key_pressed(egui::Key::Escape)) { app.ui.describe = None; @@ -813,3 +846,32 @@ fn brush_settings(app: &mut LightcraftApp, ui: &mut egui::Ui) { } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// The grid keeps four 52 pt tiles when the labels fit, and gives a long (translated) label + /// room by dropping a column rather than drawing it past its tile. + #[test] + fn tile_labels_get_the_room_they_need() { + let short = |size: f32| size * 4.0; // "Luminance"-ish: 42 pt at 10.5 + let long = |size: f32| size * 5.6; // "Background"/"Hintergrund": 59 pt at 10.5, 53 at 9.5 + assert_eq!(tile_layout(224.0, short), (4, 51.0, 10.5)); + // too wide for four at either size: three tiles, wider than 52 so the label fits + let (n, w, size) = tile_layout(224.0, long); + assert_eq!((n, size), (3, 10.5)); + assert!(w >= long(10.5) + 2.0 * TILE_PAD && w <= (224.0 - 2.0 * TILE_GAP) / 3.0, "{w}"); + // a smaller label keeps a column: 9.5 pt fits where 10.5 doesn't + let mid = |size: f32| size * 4.6; // 48.3 at 10.5, 43.7 at 9.5 + assert_eq!(tile_layout(224.0, mid), (4, 51.0, 9.5)); + // a wide panel: tiles stay 52 pt (or as wide as the label) + assert_eq!(tile_layout(474.0, short).0, 4); + assert_eq!(tile_layout(474.0, short).1, TILE); + // far too narrow for any label: two tiles (labels cut short), never a negative width + let (n, w, _) = tile_layout(60.0, long); + assert_eq!(n, 2); + assert!(w >= 24.0); + assert_eq!(tile_layout(f32::NAN, long).0, 2); + } +} diff --git a/crates/ui-egui/src/tests_panels.rs b/crates/ui-egui/src/tests_panels.rs index ee4a0f8a..cb2d8e30 100644 --- a/crates/ui-egui/src/tests_panels.rs +++ b/crates/ui-egui/src/tests_panels.rs @@ -117,7 +117,27 @@ fn masking_contents_fit_the_right_panel_at_any_width() { T, ); assert_eq!(r["ok"], true, "{r}"); + let long = "The person standing beside the very long fence across the background"; + let r = h.request("engine.execute", json!({"command": "mask.rename", "params": {"id": 1, "name": long}}), T); + assert_eq!(r["ok"], true, "{r}"); for width in [RIGHT_WIDTH.min, RIGHT_WIDTH.default, 330.0, RIGHT_WIDTH.max] { + // a long Describe prompt being typed: the field and its Select button stay in the panel + h.app.ui.describe = Some(("new".into(), long.repeat(2))); + h.step(); + h.step(); + assert_inside_right_panel(&h, &format!("width {width}, describing")); + let (field, go) = (widget(&h, "maskDescribe"), widget(&h, "button:maskDescribeGo")); + assert!(field.right() <= go.left() && go.right() <= widget(&h, "panel:right_panel").right(), "width {width}: {field:?} {go:?}"); + h.app.ui.describe = None; + // the painted text, not just the widgets: tile labels inside their tiles, a long mask + // name cut short before the eye + h.step(); + for (id, r) in h.app.widgets.iter().filter(|(id, _)| id.starts_with("maskNewLabel:")) { + let tile = widget(&h, &id.replace("maskNewLabel:", "maskNew:")); + assert!(tile.contains_rect(*r), "width {width}: {id} {r:?} outside its tile {tile:?}"); + } + let (name, row) = (widget(&h, "maskName:1"), widget(&h, "mask:1")); + assert!(name.right() <= row.right() - 27.0, "width {width}: the name {name:?} runs under the eye of {row:?}"); for (selected, tool) in [(false, ""), (true, ""), (true, "brush")] { let mask = if selected { json!(2) } else { serde_json::Value::Null }; h.request("engine.execute", json!({"command": "mask.select", "params": {"id": mask}}), T); diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 5dcd9f49..c98baf41 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -593,6 +593,15 @@ mod access_tests { } } +/// `text` laid out on one line, cut short with … when it is wider than `max` (the galley's +/// `elided` says whether it was): for text painted into a fixed box (a tile, a row before its +/// buttons), which a plain `Painter::text` would draw past. +pub(crate) fn one_line(painter: &egui::Painter, text: &str, font: egui::FontId, color: Color32, max: f32) -> std::sync::Arc { + let mut job = egui::text::LayoutJob::simple_singleline(text.to_string(), font, color); + job.wrap = egui::text::TextWrapping::truncate_at_width(max.max(1.0)); + painter.layout_job(job) +} + /// `text` shortened to fit `max` (as `width` measures it): leading folders drop first /// (`Users/me/Pictures/Lightroom` → `…/Pictures/Lightroom`) so the end of a path, which says the /// most, stays; a single name still too long loses its end (`2024-06-12 Tri…`: folders tend to From 60a98354d97782c1f251aee0c819f041cf19271d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 20:09:19 +0000 Subject: [PATCH 2/5] Fix #366: the SAM 3 dialog without a download location shows how to install by hand With no mirror configured the dialog was titled "Download the SAM 3 Model?", only said to put "the files" in the folder (see docs/ai-masks.md) and offered Close. It is now titled "Install the SAM 3 Model", names the three files (model.safetensors, vocab.json, merges.txt from facebook/sam3), and has a Show in Finder / Explorer / File Manager button that creates and shows the model folder, plus a link to the installation guide. Object and Describe work as soon as the files are there. The download location itself (a hosted mirror) is still a maintainer decision: DEFAULT_MIRRORS stays empty. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8 --- crates/ui-egui/locales/de.json | 4 ++- crates/ui-egui/locales/ja.json | 4 ++- crates/ui-egui/locales/pt-br.json | 4 ++- crates/ui-egui/locales/ru.json | 3 ++ crates/ui-egui/locales/zh-hans.json | 4 ++- crates/ui-egui/locales/zh-hant.json | 4 ++- crates/ui-egui/src/panels/dialogs.rs | 42 ++++++++++++++++++++++++++-- crates/ui-egui/src/tests_masking.rs | 14 ++++++++++ docs/ai-masks.md | 4 ++- docs/parity.md | 2 +- 10 files changed, 76 insertions(+), 9 deletions(-) diff --git a/crates/ui-egui/locales/de.json b/crates/ui-egui/locales/de.json index 84a033c2..ed685f81 100644 --- a/crates/ui-egui/locales/de.json +++ b/crates/ui-egui/locales/de.json @@ -649,6 +649,7 @@ "Horizontal": "Horizontal", "Horizontal text": "Horizontaler Text", "Hours": "Stunden", + "How to install the model": "So installierst du das Modell", "Hue": "Farbton", "Hue Range": "Farbtonbereich", "ISO Speed": "ISO-Empfindlichkeit", @@ -694,6 +695,7 @@ "Info overlay": "Infoüberlagerung", "Insert at the cursor": "An Cursorposition einfügen", "Inspect Photo": "Foto untersuchen", + "Install the SAM 3 Model": "SAM-3-Modell installieren", "Instant ’70s": "Sofortbild der 70er", "Interface": "Oberfläche", "Interface language": "Sprache der Oberfläche", @@ -1455,7 +1457,7 @@ "They move to Recently Deleted and can be restored; no file on disk is touched.": "Sie werden nach „Zuletzt gelöscht“ verschoben und können wiederhergestellt werden; keine Datei auf dem Datenträger wird verändert.", "Thirds": "Drittel", "This Computer": "Dieser Computer", - "This build has no download location for the model yet: put the files in the folder above yourself (see docs/ai-masks.md).": "Diese Version enthält noch keine Downloadquelle für das Modell: Lege die Dateien selbst im obigen Ordner ab (siehe docs/ai-masks.md).", + "This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "Diese Version kann das Modell noch nicht herunterladen. Zum Installieren von Hand lege model.safetensors, vocab.json und merges.txt aus Metas facebook/sam3 in den obigen Ordner: Objekt und Beschreiben funktionieren, sobald sie dort liegen.", "This folder is not available (drive disconnected?). Smart previews are not built or used until it is back or you choose another folder.": "Dieser Ordner ist nicht verfügbar (Laufwerk getrennt?). Smart-Vorschauen werden erst wieder erstellt oder verwendet, wenn er verfügbar ist oder du einen anderen Ordner auswählst.", "This photo can't be opened": "Dieses Foto kann nicht geöffnet werden", "Threshold": "Schwellenwert", diff --git a/crates/ui-egui/locales/ja.json b/crates/ui-egui/locales/ja.json index d3e39f27..cbde1b82 100644 --- a/crates/ui-egui/locales/ja.json +++ b/crates/ui-egui/locales/ja.json @@ -499,6 +499,7 @@ "High Contrast B&W": "ハイコントラスト白黒", "Home": "ホーム", "Hours": "時間", + "How to install the model": "モデルのインストール方法", "ISO Speed": "ISO感度", "ISO speed": "ISO感度", "Import Point Curve Presets…": "ポイントカーブのプリセットを読み込む…", @@ -512,6 +513,7 @@ "Info Overlay: File": "情報オーバーレイ:ファイル", "Insert at the cursor": "カーソル位置に挿入", "Inspect Photo": "写真を調べる", + "Install the SAM 3 Model": "SAM 3モデルのインストール", "Instant ’70s": "インスタント’70s", "Invert Component": "構成要素を反転", "JPEG (Large)": "JPEG(大)", @@ -1319,7 +1321,7 @@ "The photo has these settings": "この写真に適用されている設定", "They can be restored from Recently Deleted until it is emptied.": "「最近削除した写真」を空にするまでは復元できます", "Thirds": "三分割", - "This build has no download location for the model yet: put the files in the folder above yourself (see docs/ai-masks.md).": "このビルドにはモデルのダウンロード元がまだありません。上のフォルダにファイルを手動で置いてください(docs/ai-masks.md を参照)", + "This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "このビルドではまだモデルをダウンロードできません。手動でインストールするには、Meta の facebook/sam3 の model.safetensors、vocab.json、merges.txt を上のフォルダに置いてください。置いた時点で「オブジェクト」と「説明で選択」が使えるようになります。", "This folder is not available (drive disconnected?). Smart previews are not built or used until it is back or you choose another folder.": "このフォルダにアクセスできません。ドライブを接続するか別のフォルダを選ぶまで、スマートプレビューは作成・使用されません", "This photo can't be opened": "この写真を開けません", "Threshold": "しきい値", diff --git a/crates/ui-egui/locales/pt-br.json b/crates/ui-egui/locales/pt-br.json index 6e7045bf..9ae6bfe3 100644 --- a/crates/ui-egui/locales/pt-br.json +++ b/crates/ui-egui/locales/pt-br.json @@ -623,6 +623,7 @@ "Horizontal": "Horizontal", "Horizontal text": "Texto horizontal", "Hours": "Horas", + "How to install the model": "Como instalar o modelo", "Hue": "Matiz", "Hue Range": "Faixa de matiz", "ISO Speed": "Sensibilidade ISO", @@ -663,6 +664,7 @@ "Info overlay": "Sobreposição de informações", "Insert at the cursor": "Inserir no cursor", "Inspect Photo": "Inspecionar foto", + "Install the SAM 3 Model": "Instalar o modelo SAM 3", "Instant ’70s": "Instantâneo anos 70", "Interface": "Interface", "Interface language": "Idioma da interface", @@ -1401,7 +1403,7 @@ "They move to Recently Deleted and can be restored; no file on disk is touched.": "Elas vão para Excluídos recentemente e podem ser restauradas; nenhum arquivo no disco é alterado.", "Thirds": "Terços", "This Computer": "Este computador", - "This build has no download location for the model yet: put the files in the folder above yourself (see docs/ai-masks.md).": "Esta versão ainda não tem um local de download para o modelo: coloque os arquivos você mesmo na pasta acima (veja docs/ai-masks.md).", + "This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "Esta versão ainda não consegue baixar o modelo. Para instalá-lo manualmente, coloque model.safetensors, vocab.json e merges.txt do facebook/sam3 da Meta na pasta acima: Objeto e Descrever funcionam assim que eles estiverem lá.", "This folder is not available (drive disconnected?). Smart previews are not built or used until it is back or you choose another folder.": "Esta pasta não está disponível (unidade desconectada?). As pré-visualizações inteligentes não são geradas nem usadas até ela voltar ou você escolher outra pasta.", "This photo can't be opened": "Não foi possível abrir esta foto", "Threshold": "Limite", diff --git a/crates/ui-egui/locales/ru.json b/crates/ui-egui/locales/ru.json index 6cb076db..61d35e48 100644 --- a/crates/ui-egui/locales/ru.json +++ b/crates/ui-egui/locales/ru.json @@ -509,6 +509,7 @@ "History & Activity (Y)": "История и активность (Y)", "Horizontal": "По горизонтали", "Horizontal text": "Горизонтальный текст", + "How to install the model": "Как установить модель", "Hue": "Тон", "Hue Range": "Диапазон тонов", "If file exists": "Если файл существует", @@ -541,6 +542,7 @@ "Info (I)": "Информация (I)", "Info overlay": "Информационная накладка", "Inspect Photo": "Просмотр фото", + "Install the SAM 3 Model": "Установка модели SAM 3", "Instant ’70s": "Мгновенный стиль 70-х", "Interface": "Интерфейс", "Interface language": "Язык интерфейса", @@ -1151,6 +1153,7 @@ "Theme": "Тема", "They can be restored from Recently Deleted until it is emptied.": "Их можно восстановить из «Недавно удалённых», пока этот раздел не очищен.", "Thirds": "Правило третей", + "This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "Эта сборка пока не умеет скачивать модель. Чтобы установить её вручную, положите model.safetensors, vocab.json и merges.txt из facebook/sam3 от Meta в папку выше: «Объект» и «Описать» заработают, как только файлы окажутся там.", "This folder is not available (drive disconnected?). Smart previews are not built or used until it is back or you choose another folder.": "Эта папка недоступна (диск отключён?). Смарт-просмотры не создаются и не используются, пока она не появится снова или вы не выберете другую папку.", "This photo can't be opened": "Это фото не удаётся открыть", "Threshold": "Порог", diff --git a/crates/ui-egui/locales/zh-hans.json b/crates/ui-egui/locales/zh-hans.json index c42e2abd..fe2e8672 100644 --- a/crates/ui-egui/locales/zh-hans.json +++ b/crates/ui-egui/locales/zh-hans.json @@ -505,6 +505,7 @@ "Has GPS": "有 GPS", "Home": "主文件夹", "Hours": "小时", + "How to install the model": "如何安装模型", "ISO Speed": "ISO 感光度", "ISO speed": "ISO 感光度", "Import as new": "作为新照片导入", @@ -513,6 +514,7 @@ "Info Overlay: Exposure": "信息叠加:曝光", "Info Overlay: File": "信息叠加:文件", "Insert at the cursor": "在光标处插入", + "Install the SAM 3 Model": "安装 SAM 3 模型", "Invert Component": "反相组件", "Join the ArtCraft community on Discord": "加入 Discord 上的 ArtCraft 社区", "Label": "标签", @@ -1388,7 +1390,7 @@ "Theme": "主题", "They can be restored from Recently Deleted until it is emptied.": "在“最近删除”被清空之前,都可以从那里恢复。", "Thirds": "三分法", - "This build has no download location for the model yet: put the files in the folder above yourself (see docs/ai-masks.md).": "此版本尚未提供模型的下载地址:请自行将文件放入上方的文件夹(参见 docs/ai-masks.md)。", + "This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "此版本尚无法下载模型。如需手动安装,请将 Meta 的 facebook/sam3 中的 model.safetensors、vocab.json 和 merges.txt 放入上方的文件夹:放入后即可使用“对象”和“描述”。", "This folder is not available (drive disconnected?). Smart previews are not built or used until it is back or you choose another folder.": "此文件夹不可用(驱动器已断开连接?)。在它恢复或您选择其他文件夹之前,不会构建或使用智能预览。", "This photo can't be opened": "无法打开此照片", "Threshold": "阈值", diff --git a/crates/ui-egui/locales/zh-hant.json b/crates/ui-egui/locales/zh-hant.json index c097f4f6..4a1050a0 100644 --- a/crates/ui-egui/locales/zh-hant.json +++ b/crates/ui-egui/locales/zh-hant.json @@ -505,6 +505,7 @@ "Has GPS": "有 GPS", "Home": "個人資料夾", "Hours": "小時", + "How to install the model": "如何安裝模型", "ISO Speed": "ISO 感光度", "ISO speed": "ISO 感光度", "Import as new": "以新照片匯入", @@ -513,6 +514,7 @@ "Info Overlay: Exposure": "資訊疊加:曝光", "Info Overlay: File": "資訊疊加:檔案", "Insert at the cursor": "在游標處插入", + "Install the SAM 3 Model": "安裝 SAM 3 模型", "Invert Component": "反轉元件", "Join the ArtCraft community on Discord": "加入 Discord 上的 ArtCraft 社群", "Label": "標籤", @@ -1388,7 +1390,7 @@ "Theme": "主題", "They can be restored from Recently Deleted until it is emptied.": "清空「最近刪除」之前,可以從中還原照片。", "Thirds": "三分法", - "This build has no download location for the model yet: put the files in the folder above yourself (see docs/ai-masks.md).": "此版本尚未提供模型的下載位置:請自行將檔案放入上方的資料夾(請參閱 docs/ai-masks.md)。", + "This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "此版本尚無法下載模型。如需手動安裝,請將 Meta 的 facebook/sam3 中的 model.safetensors、vocab.json 和 merges.txt 放入上方的資料夾:放入後即可使用「物件」與「描述」。", "This folder is not available (drive disconnected?). Smart previews are not built or used until it is back or you choose another folder.": "此資料夾無法使用(磁碟可能已中斷連線)。恢復連線或選擇其他資料夾之前,不會建立或使用智慧型預覽。", "This photo can't be opened": "無法開啟此照片", "Threshold": "臨界值", diff --git a/crates/ui-egui/src/panels/dialogs.rs b/crates/ui-egui/src/panels/dialogs.rs index af4635c3..735cb6c8 100644 --- a/crates/ui-egui/src/panels/dialogs.rs +++ b/crates/ui-egui/src/panels/dialogs.rs @@ -99,6 +99,8 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) { Dialog::ConfirmDelete { .. } => "Delete Photos", Dialog::RemoveFolder { disk: true, .. } => "Remove Disk from Library", Dialog::RemoveFolder { .. } => "Remove Folder from Library", + // (nothing to download from in this build: the dialog explains the manual install) + Dialog::SamModel { .. } if sam_by_hand(&app.session.segmenter) => "Install the SAM 3 Model", Dialog::SamModel { .. } => "Download the SAM 3 Model?", Dialog::About => "About LightCraft", Dialog::Shortcuts => "Keyboard Shortcuts", @@ -829,7 +831,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) { let sam = &app.session.segmenter; let (sam_installed, sam_running, sam_failed) = (sam.installed(), sam.download_status().running, sam.download_status().error.is_some()); // no download location in this build: nothing to offer but the manual install - let sam_nowhere = !sam_installed && !sam_running && sam.mirrors().is_empty(); + let sam_nowhere = sam_by_hand(sam); let cancel = match &dlg { Dialog::SamModel { .. } if sam_running || sam_installed || sam_nowhere => "Close", Dialog::SamModel { .. } => "Not Now", @@ -925,6 +927,22 @@ pub fn keeps_open(app: &LightcraftApp, dlg: &Dialog) -> bool { matches!(dlg, Dialog::SamModel { .. }) && !app.session.segmenter.installed() } +/// No SAM 3 model, no download running and nowhere to download it from: installing it by hand is +/// all the dialog can offer. +fn sam_by_hand(sam: &lightcraft_engine::segment::Segmenter) -> bool { + !sam.installed() && !sam.download_status().running && sam.mirrors().is_empty() +} + +/// The model installation guide. +const SAM_HELP: &str = "https://github.com/storytold/lightcraft/blob/main/docs/ai-masks.md#getting-the-model"; + +/// Show the SAM 3 model folder in the file manager (created first, so there is something to show). +fn show_model_folder(app: &mut LightcraftApp, dir: &std::path::Path) -> Result<(), String> { + std::fs::create_dir_all(dir).map_err(|e| format!("{}: {e}", dir.display()))?; + let reveal = app.services.reveal.as_mut().ok_or("not available here")?; + reveal(&dir.to_string_lossy()) +} + /// The SAM 3 dialog: what the model is, its size and licence, and the download's progress. fn sam_model_body(app: &mut LightcraftApp, ui: &mut egui::Ui, error: Option<&str>) { use lightcraft_engine::segment::{LICENSE_NAME, LICENSE_URL, MODEL_BYTES}; @@ -971,12 +989,32 @@ fn sam_model_body(app: &mut LightcraftApp, ui: &mut egui::Ui, error: Option<&str return; } if seg.mirrors().is_empty() { + // nothing to download from: say how to install it by hand, with the folder and the guide a click away + let dir = seg.dir.clone(); ui.label( egui::RichText::new(crate::i18n::tr( - "This build has no download location for the model yet: put the files in the folder above yourself (see docs/ai-masks.md).", + "This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.", )) .color(t.text_dim), ); + ui.horizontal_wrapped(|ui| { + if let Some(dir) = dir.filter(|_| app.services.reveal.is_some()) { + let r = ui.button(crate::i18n::tr(crate::menus::reveal_label())); + crate::widgets::register(ui.ctx(), "button:samFolder", r.rect); + if r.clicked() + && let Err(e) = show_model_folder(app, &dir) + { + app.toast(ui.ctx(), e); + } + } + let r = ui.link(crate::i18n::tr("How to install the model")).on_hover_text(SAM_HELP); + crate::widgets::register(ui.ctx(), "link:samHelp", r.rect); + if r.clicked() + && let Err(e) = crate::links::open(app, SAM_HELP) + { + app.toast(ui.ctx(), e); + } + }); } if let Some(e) = error.map(str::to_string).or(d.error) { ui.label(egui::RichText::new(format!("{} {e}", crate::i18n::tr("The download didn't work:"))).color(egui::Color32::from_rgb(230, 90, 80))); diff --git a/crates/ui-egui/src/tests_masking.rs b/crates/ui-egui/src/tests_masking.rs index 8e2c8139..0729a854 100644 --- a/crates/ui-egui/src/tests_masking.rs +++ b/crates/ui-egui/src/tests_masking.rs @@ -649,6 +649,20 @@ fn ai_masks_without_the_model_offer_the_download() { // no location configured in this build: no Download button (only Close), and an agent // confirming anyway gets the reason; the dialog stays h.step(); + // …but a way to install it by hand: the guide, and the model folder (created on demand) + assert!(h.app.widgets.iter().any(|(id, _)| id == "link:samHelp"), "no installation guide link"); + let shown = std::sync::Arc::new(std::sync::Mutex::new(Vec::::new())); + let log = shown.clone(); + h.app.services.reveal = Some(Box::new(move |p: &str| { + log.lock().unwrap().push(p.to_string()); + Ok(()) + })); + h.step(); + let r = h.request("ui.clickWidget", json!({"id": "button:samFolder"}), T); + assert_eq!(r["ok"], true, "{r}"); + assert!(dir.is_dir(), "the model folder is created to be shown"); + assert_eq!(*shown.lock().unwrap(), vec![dir.to_string_lossy().to_string()]); + let _ = std::fs::remove_dir_all(&dir); let r = h.request("ui.clickWidget", json!({"id": "button:dialogOk"}), T); assert_eq!(r["ok"], false, "{r}"); let r = h.request("ui.dialog.confirm", json!({}), T); diff --git a/docs/ai-masks.md b/docs/ai-masks.md index 5a32d690..7e760aa1 100644 --- a/docs/ai-masks.md +++ b/docs/ai-masks.md @@ -80,7 +80,9 @@ The model is fetched from an ordered list of mirrors: base URLs where `/mo > **Maintainers:** the built-in list is **empty** until LightCraft's own CDN locations exist > (see the `TODO(maintainer)` there): add them in order of preference, host the three files > unchanged, and pin `vocab.json` / `merges.txt` (size + SHA-256) in `SAM3_FILES` at the same -> time. Until then the in-app download needs a user-configured mirror, and the dialog says so. +> time. Until then the in-app download needs a user-configured mirror, and the dialog says so: +> it names the three files to put in the model folder by hand, with a button that shows that folder +> (created if needed) and a link to this guide. Hugging Face's `facebook/sam3` can't be a default: it is **gated** (each person must accept the licence there, wait for approval and download with their own token). Someone with access can diff --git a/docs/parity.md b/docs/parity.md index 5054c113..492b0923 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -342,7 +342,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins. | LR-MASK-SUBJECT | Select subject | P2 | 🟡 | `cmd:mask.add` (`subject`), `crates/pipeline/src/masks.rs` | saliency heuristic, no segmentation model | | LR-MASK-SKY | Select sky | P2 | 🟡 | `cmd:mask.add` (`sky`) | heuristic | | LR-MASK-BACKGROUND | Select background | P2 | 🟡 | `cmd:mask.add` (`background`) | inverse of the subject heuristic | -| LR-MASK-OBJECTS | Object selection | P2 | 🟡 | `cmd:mask.add` (`object`, `prompt`), `cmd:mask.objectPoint`, `cmd:mask.refineDetail`, `cmd:segment.prepare`, `cmd:segment.model.status`, `cmd:segment.model.download`, `cmd:segment.model.cancel`, `crates/segment`, `crates/engine/src/segment/mod.rs` | SAM 3 in pure Rust (candle; Metal on macOS, CPU elsewhere): Object tile → click to include, ⌥-click to leave out; Describe tile → a text prompt selects every instance ("sky", "the red car"); both also as Add/Subtract/Intersect components; + / − under the selected mask; comma lists (`car, road`); a zoomed-in detail pass for 5–10× finer edges on small objects; per-selection Edge (hard ↔ soft). The model runs on its own worker thread (the UI never waits; panics become errors), is unloaded after 10 min idle, and is optional: the segmentation is stored with the mask (288² logits), so renders and exports never need it. Weights are never bundled (SAM License): without them the app offers a consented background download (mirrors, resume, timeouts, SHA-256) — **but no default download location is configured yet** (users set `LIGHTCRAFT_SAM3_MIRRORS` or install by hand; see docs/ai-masks.md). Not verified against Lightroom's Select Object; no brush/box object mode; the first click on a photo waits for its analysis (~4 s on an M4 Pro, much longer on CPU) | +| LR-MASK-OBJECTS | Object selection | P2 | 🟡 | `cmd:mask.add` (`object`, `prompt`), `cmd:mask.objectPoint`, `cmd:mask.refineDetail`, `cmd:segment.prepare`, `cmd:segment.model.status`, `cmd:segment.model.download`, `cmd:segment.model.cancel`, `crates/segment`, `crates/engine/src/segment/mod.rs` | SAM 3 in pure Rust (candle; Metal on macOS, CPU elsewhere): Object tile → click to include, ⌥-click to leave out; Describe tile → a text prompt selects every instance ("sky", "the red car"); both also as Add/Subtract/Intersect components; + / − under the selected mask; comma lists (`car, road`); a zoomed-in detail pass for 5–10× finer edges on small objects; per-selection Edge (hard ↔ soft). The model runs on its own worker thread (the UI never waits; panics become errors), is unloaded after 10 min idle, and is optional: the segmentation is stored with the mask (288² logits), so renders and exports never need it. Weights are never bundled (SAM License): without them the app offers a consented background download (mirrors, resume, timeouts, SHA-256) — **but no default download location is configured yet** (users set `LIGHTCRAFT_SAM3_MIRRORS` or install by hand: the dialog names the three files, shows the model folder and links docs/ai-masks.md). Not verified against Lightroom's Select Object; no brush/box object mode; the first click on a photo waits for its analysis (~4 s on an M4 Pro, much longer on CPU) | | LR-MASK-PEOPLE | People parts | P2 | ⬜ | | | | LR-MASK-LANDSCAPE | Landscape classes | P2 | ⬜ | | shape exists, evaluates empty | | LR-MASK-BRUSH | Brush mask | P0 | ✅ | `cmd:tool.brush`, `cmd:mask.brushStroke` (`autoMask`), `crates/pipeline/src/masks.rs` | size/feather/flow/density/erase; Auto Mask: dabs weighted by similarity to the colour under the dab centre, refined by a guided filter on luminance (CPU + GPU); no A/B brushes, no pressure | From 0b587a0a8a864bde984556f11b963a8df78dbd65 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 20:20:17 +0000 Subject: [PATCH 3/5] Fix #371: Spanish interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Spanish (es) catalog and formats catalog covering the same messages as the Brazilian Portuguese, Japanese and Simplified Chinese catalogs (1595 messages, 180 formats), an Edit > Language > Español command (app.language.spanish), es tag tests (es, es-ES, es_MX.UTF-8, es-419), docs/localization-es.md with the terminology used, and the language lists in docs/localization.md, parity and What's New. Instructions use infinitives or impersonal forms (no usted). Plural endings follow the existing convention: the English "s" passes through where Spanish adds one, else {:.0} swallows it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8 --- crates/ui-egui/locales/es-formats.json | 182 +++ crates/ui-egui/locales/es.json | 1597 ++++++++++++++++++++++++ crates/ui-egui/src/i18n.rs | 5 + crates/ui-egui/src/menus.rs | 2 + docs/localization-es.md | 47 + docs/localization.md | 4 +- docs/parity.md | 4 +- docs/whats-new.md | 4 +- 8 files changed, 1839 insertions(+), 6 deletions(-) create mode 100644 crates/ui-egui/locales/es-formats.json create mode 100644 crates/ui-egui/locales/es.json create mode 100644 docs/localization-es.md diff --git a/crates/ui-egui/locales/es-formats.json b/crates/ui-egui/locales/es-formats.json new file mode 100644 index 00000000..320df67d --- /dev/null +++ b/crates/ui-egui/locales/es-formats.json @@ -0,0 +1,182 @@ +{ + " ({unsure} with several look-alike files: use Locate)": " ({unsure} con varios archivos parecidos: usar Localizar)", + " ({} in Recently Deleted)": " ({} en Eliminadas recientemente)", + " · {:.0}% clipped": " · {:.0}% recortado", + " · {failed} couldn't be rendered": " · no se pudieron renderizar: {failed}", + " · {skipped} skipped (file exists)": " · omitidas: {skipped} (el archivo existe)", + " · {} duplicate{} skipped": " · {0} duplicado{1} omitido{1}", + " · {} failed: {}": " · {} con errores: {}", + " · {} moved": " · movidas: {}", + " · {} not readable": " · ilegibles: {}", + " · {} original{} left at the source": " · originales conservados en el origen: {}{:.0}", + " · {} restored from Recently Deleted": " · restauradas desde Eliminadas recientemente: {}", + " — not carried over: {}{}": " — no transferido: {}{}", + "+{} more": "+{} más", + ", {failed} file{} not readable": ", {failed} archivo{0} ilegible{0}", + "; {kept} already had a location": "; ya tenían ubicación: {kept}", + "; {outside} outside its time range": "; fuera de su intervalo de tiempo: {outside}", + "@{}: {} PRs, {} commits, +{} / −{} lines (Δ {}), +{} / −{} binary assets, {} – {}": "@{}: {} PR, {} commits, +{} / −{} líneas (Δ {}), +{} / −{} recursos binarios, {} – {}", + "Add {local_n} to My Photos": "Añadir {local_n} a Mis fotos", + "Add {} Photo{}": "Añadir {} foto{}", + "Add {} Photo{}, restore {}": "Añadir {} foto{}, restaurar {}", + "Add “{}” to Favorites": "Añadir “{}” a Favoritos", + "Added {n} photo{} to My Photos": "{n} foto{0} añadida{0} a Mis fotos", + "Added {n} photo{} to “{}”": "{n} foto{0} añadida{0} a “{1}”", + "Added {what} to {name}": "{what} añadido a {name}", + "Adding photos… {} of {}": "Añadiendo fotos… {} de {}", + "All {n} selected photos move by the same amount.": "Las {n} fotos seleccionadas se desplazan la misma cantidad.", + "Already in the library, in Recently Deleted: {} photo{} selected · right-click ▸ Restore, or Delete Permanently to import afresh": "Ya en la biblioteca, en Eliminadas recientemente: {0} foto{1} seleccionada{1} · clic derecho ▸ Restaurar, o Eliminar definitivamente para importar de nuevo", + "Already in the library: {} photo{} selected in All Photos": "Ya en la biblioteca: {0} foto{1} seleccionada{1} en Todas las fotos", + "Auto ({})": "Automático ({})", + "Auto Import: added {} photo{}": "Importación automática: {0} foto{1} añadida{1}", + "Auto Sync: edits apply to {n} photos": "Sincronización automática: las ediciones se aplican a {n} fotos", + "Automatic ({} MB)": "Automático ({} MB)", + "Build 1:1 Previews ({scope})": "Generar vistas previas 1:1 ({scope})", + "Build Smart Previews ({scope})": "Generar vistas previas inteligentes ({scope})", + "Build Standard-Sized Previews ({scope})": "Generar vistas previas de tamaño estándar ({scope})", + "Building previews for {} photos…": "Generando vistas previas de {} fotos…", + "Click {}:1": "Clic {}:1", + "Couldn't open the editor: {e}": "No se pudo abrir el editor: {e}", + "Create {n} Virtual Copies": "Crear {n} copias virtuales", + "Creates {} stacks from {} of {scope}": "Crea {} pilas a partir de {} de {scope}", + "Decoder: {reason}": "Decodificador: {reason}", + "Delete \"{label}\"": "Eliminar \"{label}\"", + "Delete \"{label}\" (and the mask)": "Eliminar \"{label}\" (y la máscara)", + "Delete {n} Photos": "Eliminar {n} fotos", + "Delete “{current}”": "Eliminar “{current}”", + "Discard Smart Previews ({scope})": "Descartar vistas previas inteligentes ({scope})", + "Done ({what}): {done} photo{plural}": "Listo ({what}): {done} foto{plural}", + "Duplicate \"{label}\"": "Duplicar \"{label}\"", + "Example: {example}": "Ejemplo: {example}", + "Exported {n} photo{}": "{n} foto{0} exportada{0}", + "Exported {ok} of {total} photo{}": "Exportadas {ok} de {total} foto{}", + "Exported {} point curve preset{}": "{0} ajuste{1} preestablecido{1} de curva de puntos exportado{1}", + "Exported {} preset{}": "{0} ajuste{1} preestablecido{1} exportado{1}", + "Exporting {} of {total}": "Exportando {} de {total}", + "Filter bar — {active} active filter{}": "Barra de filtros: {active} filtro{0} activo{0}", + "Focus {:.0}": "Enfoque {:.0}", + "Folder “{}” (and its subfolders)": "Carpeta “{}” (y sus subcarpetas)", + "Found {n} missing photo{}; {left} still missing{unsure}": "Fotos que faltaban encontradas: {n}{:.0}; siguen faltando: {left}{unsure}", + "Healed {n} dust spot{}": "{n} mancha{0} de polvo corregida{0}", + "Import cancelled · {} photo{} added": "Importación cancelada · {0} foto{1} añadida{1}", + "Import failed: {e}": "Error de importación: {e}", + "Imported {n} point curve preset{}": "{n} ajuste{0} preestablecido{0} de curva de puntos importado{0}", + "Imported {n} preset{}": "{n} ajuste{0} preestablecido{0} importado{0}", + "Imported {n} preset{}, {f} file{} not readable": "{n} ajuste{0} preestablecido{0} importado{0}, {f} archivo{1} ilegible{1}", + "Imported {profiles} profile{} (Profile browser ▸ their groups)": "Perfiles importados: {profiles}{:.0} (Explorador de perfiles ▸ sus grupos)", + "Imported {} photo{}": "{0} foto{1} importada{1}", + "Insert at the cursor (also written {})": "Insertar en el cursor (también se escribe {})", + "Label Set: {name}": "Conjunto de etiquetas: {name}", + "LightCraft could not open its window: {e}": "LightCraft no pudo abrir su ventana: {e}", + "LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft no pudo abrir su ventana: {e}\n\nLos detalles están en el archivo de registro: {path}", + "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Fuentes: {} (OFL). Iconos: originales.", + "Matches: {}": "Coincidencias: {}", + "Merging… {stage} {:.0}%": "Combinando… {stage} {:.0}%", + "Move {what} to Recently Deleted?": "¿Mover {what} a Eliminadas recientemente?", + "Named ({named_n})": "Con nombre ({named_n})", + "Painting “{k}”: click photos to add or remove it · Esc stops": "Pintando “{k}”: hacer clic en las fotos para añadirla o quitarla · Esc para terminar", + "Paste into {n} photo{} — only settings that were copied are pasted": "Pegar en {n} foto{}: solo se pegan los ajustes copiados", + "Preset: {name}": "Ajuste preestablecido: {name}", + "Preview only — editing the camera's embedded JPEG ({})": "Solo vista previa: editando el JPEG incrustado de la cámara ({})", + "Preview only — {}": "Solo vista previa: {}", + "Previews ready for {done} photo{plural}": "Vistas previas listas para {done} foto{plural}", + "Profile: {}": "Perfil: {}", + "Proof Copy ({})": "Copia de prueba ({})", + "Proof Preview · {}": "Vista previa de prueba · {}", + "Quick Develop · {n} photos": "Revelado rápido · {n} fotos", + "Quick Develop: {name}": "Revelado rápido: {name}", + "Rated {}": "Valorada con {}", + "Reading photos… {done} of {total}": "Leyendo fotos… {done} de {total}", + "Reading photos… {} of {}": "Leyendo fotos… {} de {}", + "Redo {}": "Rehacer {}", + "Remove “{name}” and its {count} photo{} from the library?": "¿Quitar “{name}” y sus {count} foto{} de la biblioteca?", + "Remove “{}” from Favorites": "Quitar “{}” de Favoritos", + "Removed {what} from {name}": "{what} quitado de {name}", + "Rename {n} Photos…": "Cambiar nombre de {n} fotos…", + "Rename “{name}”": "Cambiar nombre de “{name}”", + "Renames “{from}” on {n} photo{} (keywords below it too). Use | for levels, e.g. Travel|Italy. An existing name merges the two.": "Cambia el nombre de “{from}” en {n} foto{} (también las palabras clave que dependen de ella). | separa niveles, p. ej. Viajes|Italia. Un nombre existente combina las dos.", + "Rendering on the CPU: {why}": "Renderizado en la CPU: {why}", + "Replace {} with:": "Reemplazar {} por:", + "Reset {name}": "Restablecer {name}", + "Reset {} Channel": "Restablecer canal {}", + "Restore {} Photo{}": "Restaurar {} foto{}", + "Saved export preset “{}”": "Ajuste preestablecido de exportación “{}” guardado", + "Scores {scope} for focus and exposure and finds similar shots taken within seconds of each other (bursts).": "Puntúa el enfoque y la exposición de {scope} y busca tomas similares hechas con pocos segundos de diferencia (ráfagas).", + "Show {hidden} hidden location{}": "Mostrar ubicaciones ocultas: {hidden}{:.0}", + "Showing {n} of {}": "Mostrando {n} de {}", + "Smart album: {}": "Álbum inteligente: {}", + "Tagged {n} photo{} from the tracklog": "{n} foto{0} ubicada{0} desde el registro de ruta", + "The SAM 3 download failed: {e}": "Error en la descarga de SAM 3: {e}", + "Undo {}": "Deshacer {}", + "Unknown tag{s} {} stay{} as typed (see Tags)": "Etiqueta{s} desconocida{s} {} sin cambios{:.0} (ver Etiquetas)", + "Updated “{name}”": "“{name}” actualizado", + "Version {} — a clean-room, pure-Rust photo library and raw developer.": "Versión {}: biblioteca de fotos y revelador RAW de desarrollo independiente (clean-room) en Rust puro.", + "Version: {}": "Versión: {}", + "Virtual copy “{name}” of {of}": "Copia virtual “{name}” de {of}", + "Working on {what} for {} photos…": "Procesando {what} para {} fotos…", + "none (CPU rendering): {why}": "ninguna (renderizado por CPU): {why}", + "the {n} selected photos": "las {n} fotos seleccionadas", + "the {} photos in view": "las {} fotos de la vista", + "these {count} photos": "estas {count} fotos", + "{count} smart preview{} · {:.1} MB": "{count} vista{0} previa{0} inteligente{0} · {1:.1} MB", + "{f} folders (and their subfolders)": "{f} carpetas (y sus subcarpetas)", + "{label} Grading": "Gradación de {label}", + "{label} {d:+} on every selected photo": "{label} {d:+} en cada foto seleccionada", + "{label} {index} of {n}": "{label} {index} de {n}", + "{label}, {n} photos": "{label}, {n} fotos", + "{matching} of {t} photos": "{matching} de {t} fotos", + "{matching} photos": "{matching} fotos", + "{mode} spot {} of {}": "{mode}: mancha {} de {}", + "{month}": "{month:02}", + "{month} {year}": "{month:02}/{year}", + "{msg} ({failed} failed)": "{msg} ({failed} con errores)", + "{name} Label": "Etiqueta {name}", + "{name} Off": "{name}: desactivado", + "{name} On": "{name}: activado", + "{name} opened for editing; it is stacked with the original": "{name} abierta para editar; está apilada con el original", + "{name} — double-click to reset it": "{name}: doble clic para restablecer", + "{n} change{} couldn't be written to disk: {e}\nIf you quit now, {} lost.": "Cambios que no se pudieron escribir en el disco: {n}{0:.0}: {e}\nSi se sale ahora, se perderán.{1:.0}", + "{n} change{} saved in memory but not written to disk: {e}\nLightCraft retries automatically; quitting now would lose {}.": "Cambios guardados en memoria, pero no escritos en el disco: {n}{0:.0}: {e}\nLightCraft vuelve a intentarlo automáticamente; si se sale ahora, se perderán.{1:.0}", + "{n} change{} saved in memory but not written to disk: {e} — LightCraft will retry": "Cambios guardados solo en memoria: {n}{:.0}: {e}; LightCraft volverá a intentarlo", + "{n} found · {sel} selected": "{n} encontradas · {sel} seleccionadas", + "{n} photos": "{n} fotos", + "{n} photos in {} are hidden by the filters above": "Los filtros de arriba ocultan {n} fotos en {}", + "{n} photo{}": "{n} foto{}", + "{n} photo{} match · updates automatically as photos change": "{n} foto{0} coincidente{0} · se actualiza automáticamente cuando cambian las fotos", + "{n} photo{} now · updates automatically as photos change": "{n} foto{} ahora · se actualiza automáticamente cuando cambian las fotos", + "{pid} (missing)": "{pid} (falta)", + "{sel_n} selected · {counted}": "{sel_n} seleccionadas · {counted}", + "{stage}… {:.0}%": "{stage}… {:.0}%", + "{verb} {n} Photo{}": "{verb} {n} foto{}", + "{weekday}, {day}": "{weekday}, {day}", + "{weekday}, {day} {month} {year}": "{weekday}, {day}/{month:02}/{year}", + "{what} merge added": "Combinación {what} añadida", + "{what} merge failed: {e}": "Error en la combinación {what}: {e}", + "{year}": "{year}", + "{{date:…}} directives: {}": "Directivas de {{date:…}}: {}", + "{} and higher": "{} o más", + "{} contributors · {} commits": "{} colaboradores · {} commits", + "{} failed": "Con errores: {}", + "{} files": "{} archivos", + "{} files and {f} folder{}": "{} archivos y {f} carpeta{}", + "{} item{}": "{0} elemento{1}", + "{} of 4 guides — drag along lines that should be vertical or horizontal.": "{} de 4 guías: arrastrar a lo largo de líneas que deban ser verticales u horizontales.", + "{} photos": "{} fotos", + "{} photos · exposures {} EV": "{} fotos · exposiciones {} EV", + "{} photos · {} · {:.0}° × {:.0}°": "{} fotos · {} · {:.0}° × {:.0}°", + "{} photo{} not readable": "{0} foto{1} ilegible{1}", + "{} sample{}": "{} muestra{}", + "{} sec": "{} s", + "{} spot(s) on this photo": "{} mancha(s) en esta foto", + "{} — show their photos": "{}: mostrar sus fotos", + "· {dups} already in the library": "· ya en la biblioteca: {dups}", + "· {n} photo{}": "· {n} foto{}", + "… and {more} more": "… y {more} más", + "LightCraft can't decode this raw variant yet ({}). You're editing the camera's embedded JPEG preview, which already includes the camera's picture style (e.g. Monochrome) and white balance.": "LightCraft aún no puede decodificar esta variante RAW ({}). Se está editando la vista previa JPEG incrustada de la cámara, que ya incluye el estilo de imagen de la cámara (p. ej. Monocromo) y el balance de blancos.", + "Filtered: {label}": "Filtrado: {label}", + "{n} similar photo{} · View ▸ Clear Filters to see all": "Fotos similares: {n}{:.0} · Vista ▸ Borrar filtros para ver todas", + "Moved; {} photo(s) relinked": "Movida; {} foto(s) vinculada(s) de nuevo", + "{} label (click more labels to show any of them)": "Etiqueta {} (hacer clic en más etiquetas para mostrar cualquiera de ellas)", + ", {} star{}": ", {} estrella{}", + ", {} label": ", etiqueta {}" +} diff --git a/crates/ui-egui/locales/es.json b/crates/ui-egui/locales/es.json new file mode 100644 index 00000000..79eb04a0 --- /dev/null +++ b/crates/ui-egui/locales/es.json @@ -0,0 +1,1597 @@ +{ + " (library default)": " (predeterminado de la biblioteca)", + " (inverted)": " (invertida)", + " · best of its burst": " · la mejor de su ráfaga", + " · in a burst": " · en una ráfaga", + "% of all commits": "% de todos los commits", + "+ Group": "+ Grupo", + "+ Rule": "+ Regla", + "0: nothing is rejected.": "0: no se rechaza nada.", + "1 day": "1 día", + "2026-04 or 2026-04-12": "2026-04 o 2026-04-12", + "= exactly": "= exactamente", + "A blank template keeps the original names.": "Una plantilla vacía conserva los nombres originales.", + "A filter is hiding this view's photos: change it, or clear it (View → Clear Filters)": "Un filtro oculta las fotos de esta vista: cambiar el filtro o borrarlo (Vista → Borrar filtros)", + "A one-time download of about": "Una única descarga de aproximadamente", + "AI Mask Model Status": "Estado del modelo de máscaras con IA", + "About": "Acerca de", + "About LightCraft": "Acerca de LightCraft", + "Active": "Activa", + "Activity": "Actividad", + "Actual size": "Tamaño real", + "Add": "Añadir", + "Add Catchlight": "Añadir reflejo ocular", + "Add Folder to Local": "Añadir carpeta a Local", + "Add Mask": "Añadir máscara", + "Add Object Click": "Añadir clic en objeto", + "Add Point Color Sample": "Añadir muestra de color de punto", + "Add Red Eye Correction": "Añadir corrección de ojos rojos", + "Add Remove Spot": "Añadir eliminación de manchas", + "Add Selected Photos": "Añadir fotos seleccionadas", + "Add a rule (⌥-click: a group)": "Añadir una regla (⌥-clic: un grupo)", + "Add in place": "Añadir sin mover", + "Add in place: the library references the files where they are; nothing is copied or moved.": "Añadir sin mover: la biblioteca hace referencia a los archivos donde están; no se copia ni se mueve nada.", + "Add keyword": "Añadir palabra clave", + "Add number": "Añadir número", + "Add to": "Añadir a", + "Add to Album": "Añadir al álbum", + "Add to Favorites": "Añadir a Favoritos", + "Add to Local": "Añadir a Local", + "Add to My Photos": "Añadir a Mis fotos", + "Add to Selected Photos": "Añadir a las fotos seleccionadas", + "Add to Target Album": "Añadir al álbum de destino", + "Add to this mask": "Añadir a esta máscara", + "Add/Subtract/Intersect Mask": "Añadir/restar/intersecar máscara", + "Added to photos you import that don't already have it.": "Se añade a las fotos importadas que aún no lo tienen.", + "Adding": "Añadiendo", + "Advanced search…": "Búsqueda avanzada…", + "After": "Después", + "After 30 days": "Después de 30 días", + "After 7 days": "Después de 7 días", + "After 90 days": "Después de 90 días", + "After a year": "Después de un año", + "Album": "Álbum", + "Albums": "Álbumes", + "All": "Todo", + "All Metadata": "Todos los metadatos", + "All Metadata…": "Todos los metadatos…", + "All Photos": "Todas las fotos", + "All rights reserved": "Todos los derechos reservados", + "All the photos imported from this disk are included.": "Se incluyen todas las fotos importadas desde este disco.", + "All types": "Todos los tipos", + "Alt Text": "Texto alternativo", + "Always": "Siempre", + "Amount": "Cantidad", + "Analyzing the photo for AI masks…": "Analizando la foto para las máscaras con IA…", + "Any Searchable Text": "Cualquier texto que se pueda buscar", + "Aperture": "Abertura", + "Application": "Aplicación", + "Apply": "Aplicar", + "Apply Color Label Set": "Aplicar conjunto de etiquetas de color", + "Apply Filter Preset": "Aplicar ajuste preestablecido de filtro", + "Apply Metadata Preset": "Aplicar ajuste preestablecido de metadatos", + "Apply Point Curve Preset": "Aplicar ajuste preestablecido de curva de puntos", + "Apply Preset": "Aplicar ajuste preestablecido", + "Apply Settings JSON": "Aplicar ajustes JSON", + "Approved": "Aprobada", + "Aqua": "Aguamarina", + "Aqua Hue": "Tono aguamarina", + "Aqua Luminance": "Luminancia aguamarina", + "Aqua Saturation": "Saturación aguamarina", + "ArtCraft Website": "Sitio web de ArtCraft", + "ArtCraft — more creative apps": "ArtCraft: más aplicaciones creativas", + "As Shot": "Como se tomó", + "As shot": "Como se tomó", + "Ascending": "Ascendente", + "Aspect": "Aspecto", + "Aspect Ratio": "Relación de aspecto", + "Assisted Culling": "Selección asistida", + "Assisted Culling…": "Selección asistida…", + "Auto": "Automático", + "Auto Advance": "Avance automático", + "Auto Advance Off": "Avance automático desactivado", + "Auto Advance On": "Avance automático activado", + "Auto Advance after rating or flagging": "Avance automático tras valorar o marcar", + "Auto Advance: move to the next photo after rating or flagging": "Avance automático: pasar a la foto siguiente tras valorar o marcar", + "Auto Align": "Alineación automática", + "Auto B&W Mix": "Mezcla de ByN automática", + "Auto Crop": "Recorte automático", + "Auto Import": "Importación automática", + "Auto Import Now": "Importar automáticamente ahora", + "Auto Import Settings": "Ajustes de importación automática", + "Auto Mask": "Máscara automática", + "Auto Select": "Selección automática", + "Auto Settings": "Ajustes automáticos", + "Auto Straighten": "Enderezamiento automático", + "Auto Sync": "Sincronización automática", + "Auto settings applied": "Ajustes automáticos aplicados", + "Auto-Stack by Capture Time": "Apilar automáticamente por hora de captura", + "Auto-Stack by Capture Time…": "Apilar automáticamente por hora de captura…", + "Auto-Tag Photos from Tracklog": "Etiquetar fotos automáticamente desde el registro de ruta", + "Auto-Tag from Tracklog": "Etiquetar automáticamente desde el registro de ruta", + "Auto-Tag from Tracklog…": "Etiquetar automáticamente desde el registro de ruta…", + "Automatic": "Automático", + "Automatically Write Changes into XMP": "Escribir automáticamente los cambios en XMP", + "Automatically write changes into XMP sidecars": "Escribir automáticamente los cambios en los archivos XMP complementarios", + "Autumn Gold": "Oro otoñal", + "B&W": "ByN", + "B&W Mix": "Mezcla de ByN", + "B&W Mixer": "Mezclador de ByN", + "Back": "Atrás", + "Back Up Library…": "Hacer copia de seguridad de la biblioteca…", + "Back to Edit": "Volver a Editar", + "Back to Edit (E)": "Volver a Editar (E)", + "Back to Grid": "Volver a la cuadrícula", + "Background": "Fondo", + "Balance": "Balance", + "Basic": "Básico", + "Before": "Antes", + "Before/After (Y)": "Antes/Después (Y)", + "Before/After Settings": "Ajustes de Antes/Después", + "Before/After Split": "Antes/Después dividido", + "Before/After Split Top/Bottom": "Antes/Después dividido arriba/abajo", + "Before/After Top/Bottom": "Antes/Después arriba/abajo", + "Begin Interaction": "Iniciar interacción", + "Best of Similar Shots": "La mejor de las tomas similares", + "Binary assets added": "Recursos binarios añadidos", + "Binary assets removed": "Recursos binarios eliminados", + "Bit depth": "Profundidad de bits", + "Black & White": "Blanco y negro", + "Blacks": "Negros", + "Bleach Bypass": "Blanqueo omitido", + "Bleached": "Blanqueado", + "Blending": "Fusión", + "Blue": "Azul", + "Blue Hour Boost": "Realce de hora azul", + "Blue Hue": "Tono azul", + "Blue Luminance": "Luminancia azul", + "Blue Primary": "Azul primario", + "Blue Saturation": "Saturación azul", + "Blue channel": "Canal azul", + "Blue/Yellow Fringe": "Franja azul/amarilla", + "Blurry photos below the score are flagged as rejects.": "Las fotos borrosas por debajo de la puntuación se marcan como rechazadas.", + "Boundary Warp": "Deformación del contorno", + "Bright & Airy": "Luminoso y etéreo", + "Bring All to Front": "Traer todo al frente", + "Browse Folder": "Explorar carpeta", + "Browse Folder…": "Explorar carpeta…", + "Browse Profiles": "Explorar perfiles", + "Browse…": "Explorar…", + "Brush": "Pincel", + "Brush Settings": "Ajustes del pincel", + "Brush Stroke": "Trazo de pincel", + "Build Previews": "Generar vistas previas", + "Build Smart Previews": "Generar vistas previas inteligentes", + "By Date": "Por fecha", + "By day (YYYY/YYYY-MM-DD)": "Por día (AAAA/AAAA-MM-DD)", + "By month (YYYY/YYYY-MM)": "Por mes (AAAA/AAAA-MM)", + "CPU threads": "Hilos de CPU", + "Calibration": "Calibración", + "Camera": "Cámara", + "Camera make and model": "Marca y modelo de la cámara", + "Cameras and Cards": "Cámaras y tarjetas", + "Cancel": "Cancelar", + "Cancel AI Mask Model Download": "Cancelar la descarga del modelo de máscaras con IA", + "Cancel Download": "Cancelar descarga", + "Cancel Interaction": "Cancelar interacción", + "Cancel Preview Build": "Cancelar generación de vistas previas", + "Candidate": "Candidata", + "Caption": "Pie de foto", + "Capture Date": "Fecha de captura", + "Capture date and time in your own format (directives below)": "Fecha y hora de captura en un formato propio (directivas abajo)", + "Capture date as YYYYMMDD": "Fecha de captura como AAAAMMDD", + "Captured": "Capturada", + "Catalog Statistics": "Estadísticas del catálogo", + "Catchlight": "Reflejo ocular", + "Change": "Cambiar", + "Change Component Mode": "Cambiar modo del componente", + "Characters not allowed in file names (/ \\ : * ? \" < > |) become -; an existing name gets -1, -2….": "Los caracteres no permitidos en nombres de archivo (/ \\ : * ? \" < > |) se convierten en -; un nombre existente recibe -1, -2….", + "Check All": "Marcar todo", + "Check Output Path": "Comprobar ruta de salida", + "Checking names…": "Comprobando nombres…", + "Choose Another Library…": "Elegir otra biblioteca…", + "Choose Edit Settings to Copy": "Elegir ajustes de edición para copiar", + "Choose Edit Settings to Copy…": "Elegir ajustes de edición para copiar…", + "Choose Folder…": "Elegir carpeta…", + "Choose a photo in the grid or filmstrip": "Elegir una foto en la cuadrícula o en la tira de película", + "Choose…": "Elegir…", + "Cinematic": "Cinematográfico", + "City": "Ciudad", + "Clarity": "Claridad", + "Clean Studio": "Estudio limpio", + "Clear": "Borrar", + "Clear Cache": "Vaciar caché", + "Clear Filters": "Borrar filtros", + "Clear Guides": "Borrar guías", + "Clear History": "Borrar historial", + "Clear Preview Cache": "Vaciar caché de vistas previas", + "Clear Quick Collection": "Borrar colección rápida", + "Clear Reference": "Borrar referencia", + "Clear all": "Borrar todo", + "Clear form": "Borrar formulario", + "Cleared History": "Historial borrado", + "Click Zoom Ratio": "Proporción de zoom al hacer clic", + "Click a tag to insert it. Examples are for IMG_0042.CR3 taken 14 Jan 2026, 05:58:48.": "Hacer clic en una etiqueta para insertarla. Los ejemplos corresponden a IMG_0042.CR3, tomada el 14 ene 2026, 05:58:48.", + "Click photos in the grid to toggle the keyword; Esc stops": "Hacer clic en las fotos de la cuadrícula para activar o desactivar la palabra clave; Esc para terminar", + "Click the object to select it · ⌥-click leaves a part out": "Hacer clic en el objeto para seleccionarlo · ⌥-clic excluye una parte", + "Click the photo to pick a colour · ⇧-click adds more": "Hacer clic en la foto para elegir un color · ⇧-clic añade más", + "Click the photo to pick a colour; ⇧-click adds more (up to 5)": "Hacer clic en la foto para elegir un color; ⇧-clic añade más (hasta 5)", + "Click to clear these filters": "Hacer clic para borrar estos filtros", + "Click to restore · double-click to rename": "Hacer clic para restaurar · doble clic para cambiar el nombre", + "Clone": "Clonar", + "Close": "Cerrar", + "Close Panel": "Cerrar panel", + "Close Window": "Cerrar ventana", + "Close all": "Cerrar todo", + "Close file": "Cerrar archivo", + "Cloudy": "Nublado", + "Collapse": "Contraer", + "Collapse All Stacks": "Contraer todas las pilas", + "Collapse Stack": "Contraer pila", + "Color": "Color", + "Color Grading": "Gradación de color", + "Color Label": "Etiqueta de color", + "Color Label Names": "Nombres de etiquetas de color", + "Color Label Sets": "Conjuntos de etiquetas de color", + "Color Mixer": "Mezclador de color", + "Color Noise Reduction": "Reducción de ruido de color", + "Color Priority": "Prioridad de color", + "Color Range": "Rango de color", + "Color label cleared": "Etiqueta de color eliminada", + "Color space": "Espacio de color", + "Color space: sRGB (AVIF)": "Espacio de color: sRGB (AVIF)", + "Colors": "Colores", + "Combine files…": "Combinar archivos…", + "Command Journal": "Registro de comandos", + "Comments panel": "Panel de comentarios", + "Commits": "Commits", + "Company": "Empresa", + "Compare": "Comparar", + "Compare (Shift+C)": "Comparar (Mayús+C)", + "Compare Before and After": "Comparar antes y después", + "Compare needs at least two photos": "Comparar requiere al menos dos fotos", + "Component options": "Opciones del componente", + "Compression": "Compresión", + "Concrete Cool": "Hormigón frío", + "Confirm before deleting": "Confirmar antes de eliminar", + "Confirm before moving photos to Recently Deleted": "Confirmar antes de mover fotos a Eliminadas recientemente", + "Constrain Crop": "Restringir recorte", + "Continue": "Continuar", + "Continue Without Saving": "Continuar sin guardar", + "Contrast": "Contraste", + "Contributors": "Colaboradores", + "Convert to DNG": "Convertir a DNG", + "Cool Fade": "Desvanecido frío", + "Cool Morning": "Mañana fresca", + "Copy": "Copiar", + "Copy After's Settings to Before": "Copiar los ajustes de Después en Antes", + "Copy Before to After": "Copiar Antes en Después", + "Copy Before's Settings to After": "Copiar los ajustes de Antes en Después", + "Copy Edit Settings": "Copiar ajustes de edición", + "Copy History Step Settings to Before": "Copiar los ajustes del paso del historial en Antes", + "Copy Metadata": "Copiar metadatos", + "Copy as DNG": "Copiar como DNG", + "Copy into the library (else use the files where they are)": "Copiar en la biblioteca (si no, usar los archivos donde están)", + "Copy pages": "Copiar páginas", + "Copy the files (into the library's Originals/, or a folder you choose)": "Copiar los archivos (en Originals/ de la biblioteca o en una carpeta a elección)", + "Copy to": "Copiar en", + "Copy to Clipboard": "Copiar en el portapapeles", + "Copy: the files are copied to the folder below and the library uses the copies; the originals are left as they are.": "Copiar: los archivos se copian en la carpeta de abajo y la biblioteca usa las copias; los originales quedan como están.", + "Copyright": "Copyright", + "Copyright Info URL": "URL de información de copyright", + "Copyright Status": "Estado del copyright", + "Country": "País", + "Create Album": "Crear álbum", + "Create Album…": "Crear álbum…", + "Create Folder": "Crear carpeta", + "Create Folder…": "Crear carpeta…", + "Create New Mask": "Crear nueva máscara", + "Create PDF from clipboard": "Crear PDF desde el portapapeles", + "Create PDF from file…": "Crear PDF desde archivo…", + "Create PDF from images…": "Crear PDF desde imágenes…", + "Create Preset": "Crear ajuste preestablecido", + "Create Preset…": "Crear ajuste preestablecido…", + "Create Proof Copy": "Crear copia de prueba", + "Create Smart Album": "Crear álbum inteligente", + "Create Smart Album from Filter…": "Crear álbum inteligente desde el filtro…", + "Create Smart Album…": "Crear álbum inteligente…", + "Create Stack": "Crear pila", + "Create Version": "Crear versión", + "Create Version from Step": "Crear versión desde el paso", + "Create Virtual Copy": "Crear copia virtual", + "Creator": "Creador", + "Creator metadata": "Creador en los metadatos", + "Crisp Landscape": "Paisaje nítido", + "Crisp Vista": "Vista nítida", + "Crop": "Recortar", + "Crop & Rotate": "Recortar y rotar", + "Crop & Rotate (C)": "Recortar y rotar (C)", + "Crop Aspect": "Aspecto del recorte", + "Crop, Rotate, Geometry": "Recorte, rotación, geometría", + "Cross Process": "Proceso cruzado", + "Culling": "Selección", + "Curve": "Curva", + "Custom": "Personalizado", + "Custom template…": "Plantilla personalizada…", + "Custom…": "Personalizado…", + "Cut": "Cortar", + "Cut pages": "Cortar páginas", + "Cycle Crop Overlay": "Alternar superposición de recorte", + "Cycle Crop Overlay Orientation": "Alternar orientación de la superposición de recorte", + "Cycle Info Overlay": "Alternar superposición de información", + "Cycle Mask Overlay Color": "Alternar color de superposición de máscara", + "Cycle Mask Overlay Mode": "Alternar modo de superposición de máscara", + "Cylindrical": "Cilíndrica", + "Dark & Moody": "Oscuro y sombrío", + "Darken": "Oscurecer", + "Darks": "Oscuros", + "Date": "Fecha", + "Date Groups": "Grupos por fecha", + "Day": "Día", + "Daylight": "Luz de día", + "Decrease Brush Feather": "Reducir desvanecimiento del pincel", + "Decrease Brush Size": "Reducir tamaño del pincel", + "Deep Sky": "Cielo profundo", + "Defringe": "Eliminar franjas", + "Deghost Amount": "Cantidad de eliminación de fantasmas", + "Dehaze": "Eliminar neblina", + "Delete": "Eliminar", + "Delete (⌫)": "Eliminar (⌫)", + "Delete Album": "Eliminar álbum", + "Delete All Masks": "Eliminar todas las máscaras", + "Delete Color Label Set": "Eliminar conjunto de etiquetas de color", + "Delete Component": "Eliminar componente", + "Delete Export Preset": "Eliminar ajuste preestablecido de exportación", + "Delete Filter Preset": "Eliminar ajuste preestablecido de filtro", + "Delete Imported Profile": "Eliminar perfil importado", + "Delete Keyword": "Eliminar palabra clave", + "Delete Keyword Set": "Eliminar conjunto de palabras clave", + "Delete Mask": "Eliminar máscara", + "Delete Metadata Preset": "Eliminar ajuste preestablecido de metadatos", + "Delete Permanently": "Eliminar definitivamente", + "Delete Photo": "Eliminar foto", + "Delete Photos": "Eliminar fotos", + "Delete Point Color": "Eliminar color de punto", + "Delete Point Color Sample": "Eliminar muestra de color de punto", + "Delete Point Curve Preset": "Eliminar ajuste preestablecido de curva de puntos", + "Delete Preset": "Eliminar ajuste preestablecido", + "Delete Red Eye": "Eliminar ojos rojos", + "Delete Red Eye Correction": "Eliminar corrección de ojos rojos", + "Delete Spot": "Eliminar mancha", + "Delete Version": "Eliminar versión", + "Delete all": "Eliminar todo", + "Delete all spots": "Eliminar todas las manchas", + "Delete mask": "Eliminar máscara", + "Delete them": "Eliminarlas", + "Density": "Densidad", + "Depth Range": "Rango de profundidad", + "Descending": "Descendente", + "Describe": "Describir", + "Describe what to keep": "Describir qué conservar", + "Describe what to leave out": "Describir qué excluir", + "Describe what to select": "Describir qué seleccionar", + "Describe…": "Describir…", + "Deselect": "Deseleccionar", + "Deselect All": "Deseleccionar todo", + "Desert Heat": "Calor del desierto", + "Desert Warmth": "Calidez del desierto", + "Desktop": "Escritorio", + "Detail": "Detalle", + "Detail (D)": "Detalle (D)", + "Details": "Detalles", + "Diagonal": "Diagonal", + "Discard Preview Cache": "Descartar caché de vistas previas", + "Display P3": "Display P3", + "Display name": "Nombre visible", + "Display theme": "Tema de visualización", + "Distortion": "Distorsión", + "Document properties…": "Propiedades del documento…", + "Don't enlarge": "No ampliar", + "Done": "Hecho", + "Download": "Descargar", + "Download AI Mask Model": "Descargar modelo de máscaras con IA", + "Download the SAM 3 Model?": "¿Descargar el modelo SAM 3?", + "Downloads": "Descargas", + "Drag along the horizon; double-click for Auto": "Arrastrar a lo largo del horizonte; doble clic para Automático", + "Drag over an eye on the photo; the pupil inside is found automatically.": "Arrastrar sobre un ojo en la foto; la pupila se detecta automáticamente.", + "Draw Guides": "Dibujar guías", + "Duplicate": "Duplicar", + "Duplicate Component": "Duplicar componente", + "Duplicate Mask": "Duplicar máscara", + "Duplicate and Invert Mask": "Duplicar e invertir máscara", + "Each / starts a folder level; tags are filled in per photo (a level with missing metadata is \"unknown\").": "Cada / inicia un nivel de carpeta; las etiquetas se rellenan para cada foto (un nivel sin metadatos queda como \"unknown\").", + "Edge": "Borde", + "Edit": "Editar", + "Edit (E)": "Editar (E)", + "Edit Capture Time": "Editar hora de captura", + "Edit Capture Time…": "Editar hora de captura…", + "Edit Color Label Names": "Editar nombres de etiquetas de color", + "Edit Color Label Names…": "Editar nombres de etiquetas de color…", + "Edit Copy for External Editor": "Editar copia para el editor externo", + "Edit Date": "Fecha de edición", + "Edit Info": "Editar información", + "Edit Label Names": "Editar nombres de etiquetas", + "Edit Label Names…": "Editar nombres de etiquetas…", + "Edit Mask": "Editar máscara", + "Edit Mask Component": "Editar componente de máscara", + "Edit Sections": "Editar secciones", + "Edit Smart Album": "Editar álbum inteligente", + "Edit Smart Album…": "Editar álbum inteligente…", + "Edit Spot": "Editar mancha", + "Edit in External Editor": "Editar en el editor externo", + "Edit settings copied": "Ajustes de edición copiados", + "Edited": "Editada", + "Edited or not": "Editadas o no", + "Editing": "Edición", + "Effect": "Efecto", + "Effects": "Efectos", + "Empty Recently Deleted": "Vaciar Eliminadas recientemente", + "Enable Lens Corrections": "Activar correcciones de lente", + "Enable Profile Corrections": "Activar correcciones de perfil", + "End Interaction": "Finalizar interacción", + "English": "Inglés", + "Enter Full Screen": "Pantalla completa", + "Enter a date as YYYY-MM-DD HH:MM:SS": "Introducir una fecha como AAAA-MM-DD HH:MM:SS", + "Erase": "Borrar", + "Every EXIF, GPS and XMP field in the file": "Todos los campos EXIF, GPS y XMP del archivo", + "Existing previews": "Vistas previas existentes", + "Exit": "Salir", + "Expand": "Expandir", + "Expand All Stacks": "Expandir todas las pilas", + "Expand Stack": "Expandir pila", + "Expand/Collapse Stack": "Expandir/contraer pila", + "Export": "Exportar", + "Export Album with Preset": "Exportar álbum con ajuste preestablecido", + "Export Album…": "Exportar álbum…", + "Export As…": "Exportar como…", + "Export Group…": "Exportar grupo…", + "Export Now": "Exportar ahora", + "Export Point Curve Presets": "Exportar ajustes preestablecidos de curva de puntos", + "Export Point Curve Presets…": "Exportar ajustes preestablecidos de curva de puntos…", + "Export Presets": "Exportar ajustes preestablecidos", + "Export Presets…": "Exportar ajustes preestablecidos…", + "Export User Presets…": "Exportar ajustes preestablecidos del usuario…", + "Export stopped unexpectedly": "La exportación se detuvo inesperadamente", + "Export with Preset": "Exportar con ajuste preestablecido", + "Export with Previous": "Exportar con los ajustes anteriores", + "Exporting": "Exportando", + "Export…": "Exportar…", + "Exposure": "Exposición", + "Exposure (shutter · aperture · ISO)": "Exposición (obturación · abertura · ISO)", + "Extended Description": "Descripción ampliada", + "External Editor": "Editor externo", + "Face Boxes": "Recuadros de caras", + "Face boxes": "Recuadros de caras", + "Face names written to XMP by Lightroom and other apps show up here.": "Aquí aparecen los nombres de caras escritos en XMP por Lightroom y otras aplicaciones.", + "Faded Matte": "Mate desvaído", + "Faded Slide": "Diapositiva desvaída", + "Faded Urban": "Urbano desvaído", + "Favorite Preset": "Ajuste preestablecido favorito", + "Favorite Profile": "Perfil favorito", + "Favorites": "Favoritos", + "Feather": "Desvanecer", + "File": "Archivo", + "File & date": "Archivo y fecha", + "File Format": "Formato de archivo", + "File Name": "Nombre de archivo", + "File Path": "Ruta del archivo", + "File Size": "Tamaño del archivo", + "File Type": "Tipo de archivo", + "File name": "Nombre de archivo", + "Filename": "Nombre de archivo", + "Fill": "Rellenar", + "Fill Edges": "Rellenar bordes", + "Film": "Película", + "Filmstrip": "Tira de película", + "Filmstrip (/)": "Tira de película (/)", + "Filter": "Filtro", + "Filter Bar": "Barra de filtros", + "Filter Presets": "Ajustes preestablecidos de filtro", + "Filter bar": "Barra de filtros", + "Filter fields": "Campos de filtro", + "Find Dust Spots": "Buscar manchas de polvo", + "Find Missing Photos": "Buscar fotos que faltan", + "Find Missing Photos…": "Buscar fotos que faltan…", + "Find Similar Photos": "Buscar fotos similares", + "Find tools and commands…": "Buscar herramientas y comandos…", + "Find…": "Buscar…", + "First": "Primero", + "First commit": "Primer commit", + "Fit": "Encajar", + "Fit (F)": "Encajar (F)", + "Fit on Screen": "Encajar en pantalla", + "Fit to width": "Ajustar al ancho", + "Fit visible": "Ajustar a lo visible", + "Five stars": "Cinco estrellas", + "Flag": "Marca", + "Flag as Pick": "Marcar como seleccionada", + "Flag as Reject": "Marcar como rechazada", + "Flagged as Pick": "Marcada como seleccionada", + "Flagged as Reject": "Marcada como rechazada", + "Flash": "Flash", + "Flip H": "Voltear H", + "Flip Horizontal": "Voltear horizontalmente", + "Flip Overlay (⇧O)": "Voltear superposición (⇧O)", + "Flip V": "Voltear V", + "Flip Vertical": "Voltear verticalmente", + "Flow": "Flujo", + "Fluorescent": "Fluorescente", + "Focal Length": "Distancia focal", + "Focal length": "Distancia focal", + "Focus (assisted culling)": "Enfoque (selección asistida)", + "Folder": "Carpeta", + "Folder name (renamed on disk; its photos follow)": "Nombre de la carpeta (se cambia en el disco; sus fotos la siguen)", + "Folders": "Carpetas", + "Folders and {date} use the capture time; a photo without one uses today's date.": "Las carpetas y {date} usan la hora de captura; una foto sin ella usa la fecha de hoy.", + "Food": "Comida", + "Forget Unchanged Local Photos": "Olvidar fotos locales sin cambios", + "Forget unchanged photos": "Olvidar fotos sin cambios", + "Form fields panel": "Panel de campos de formulario", + "Format": "Formato", + "Forward": "Adelante", + "Frame time": "Tiempo de fotograma", + "Frame update": "Actualización de fotograma", + "Free": "Libre", + "Fresh & Bright": "Fresco y luminoso", + "Friday": "Viernes", + "Full": "Completa", + "Full Screen Preview": "Vista previa a pantalla completa", + "Full screen mode": "Modo de pantalla completa", + "Full size": "Tamaño completo", + "GPS Track Log": "Registro de ruta GPS", + "GPU Rendering": "Renderizado por GPU", + "GPU rendering": "Renderizado por GPU", + "General": "General", + "Generated demo photo": "Foto de demostración generada", + "Geometry": "Geometría", + "Get Develop Settings": "Obtener ajustes de revelado", + "Global Hue": "Tono global", + "Global Luminance": "Luminancia global", + "Global Saturation": "Saturación global", + "Glossy": "Brillante", + "Go to History Step": "Ir al paso del historial", + "Golden": "Dorado", + "Golden Glow": "Resplandor dorado", + "Golden Hour": "Hora dorada", + "Grab bag": "Lista de nombres", + "Grain": "Grano", + "Graphic": "Gráfico", + "Green": "Verde", + "Green Amount": "Cantidad de verde", + "Green Hue": "Tono verde", + "Green Hue High": "Tono verde alto", + "Green Hue Low": "Tono verde bajo", + "Green Luminance": "Luminancia verde", + "Green Primary": "Verde primario", + "Green Saturation": "Saturación verde", + "Green channel": "Canal verde", + "Grid": "Cuadrícula", + "Grid Info": "Información de la cuadrícula", + "Gritty Street": "Calle cruda", + "Group": "Grupo", + "Group by Date: Automatic": "Agrupar por fecha: automático", + "Group by Day": "Agrupar por día", + "Group by Month": "Agrupar por mes", + "Group by Year": "Agrupar por año", + "Group by date": "Agrupar por fecha", + "Group into Stack": "Agrupar en pila", + "Group name": "Nombre del grupo", + "Guided": "Guiado", + "Guided Upright": "Vertical guiado", + "Guided Upright Guides": "Guías de vertical guiado", + "HDR Merge": "Combinación HDR", + "HDR Merge Preview": "Vista previa de combinación HDR", + "HDR Panorama Merge": "Combinación de panorámica HDR", + "HDR Panorama Merge Preview": "Vista previa de combinación de panorámica HDR", + "HDR Panorama with Last Settings": "Panorámica HDR con los últimos ajustes", + "HDR Panoramas": "Panorámicas HDR", + "HDR Panorama…": "Panorámica HDR…", + "HDR panorama": "Panorámica HDR", + "HDR panoramas": "panorámicas HDR", + "HDR with Last Settings": "HDR con los últimos ajustes", + "HDR…": "HDR…", + "Has Edits": "Con ediciones", + "Has GPS": "Con GPS", + "Heal": "Corregir", + "Heal the small, soft dark spots sensor dust leaves on smooth areas": "Corregir las pequeñas manchas oscuras y difusas que deja el polvo del sensor en zonas lisas", + "Height": "Alto", + "Height (px)": "Alto (px)", + "Help": "Ayuda", + "Hide LightCraft": "Ocultar LightCraft", + "Hide Mask": "Ocultar máscara", + "Hide Others": "Ocultar otras", + "Hides this shortcut only; the folder and its photos stay as they are": "Solo oculta este acceso directo; la carpeta y sus fotos quedan como están", + "High": "Alto", + "High Contrast B&W": "ByN de alto contraste", + "Highlight": "Iluminación", + "Highlights": "Iluminaciones", + "Highlights Hue": "Tono de iluminaciones", + "Highlights Luminance": "Luminancia de iluminaciones", + "Highlights Saturation": "Saturación de iluminaciones", + "Highlights split": "División de iluminaciones", + "Histogram": "Histograma", + "History": "Historial", + "History & Activity (Y)": "Historial y actividad (Y)", + "Home": "Carpeta personal", + "Horizontal": "Horizontal", + "Horizontal text": "Texto horizontal", + "Hours": "Horas", + "Hue": "Tono", + "Hue Range": "Rango de tono", + "ISO Speed": "Sensibilidad ISO", + "ISO speed": "Sensibilidad ISO", + "If file exists": "Si el archivo existe", + "Image": "Imagen", + "Import": "Importar", + "Import Date": "Fecha de importación", + "Import Photos": "Importar fotos", + "Import Photos…": "Importar fotos…", + "Import Point Curve Presets": "Importar ajustes preestablecidos de curva de puntos", + "Import Point Curve Presets…": "Importar ajustes preestablecidos de curva de puntos…", + "Import Presets": "Importar ajustes preestablecidos", + "Import Presets…": "Importar ajustes preestablecidos…", + "Import Profiles": "Importar perfiles", + "Import Profiles & Presets…": "Importar perfiles y ajustes preestablecidos…", + "Import as new": "Importar como nuevas", + "Import from Device": "Importar desde dispositivo", + "Import from Folder…": "Importar desde carpeta…", + "Import photos with File → Import Photos… (Cmd+Shift+I), or drop them here": "Importar fotos con Archivo → Importar fotos… (Cmd+Mayús+I) o soltarlas aquí", + "Imported": "Importadas", + "Imported Photos": "Fotos importadas", + "Importing": "Importando", + "Importing scans the source for photos; it doesn't add the folder to Local (use Local → Browse Folder… to work in a folder without importing).": "La importación busca fotos en el origen; no añade la carpeta a Local (Local → Explorar carpeta… permite trabajar en una carpeta sin importar).", + "In Recently Deleted": "En Eliminadas recientemente", + "In library": "En la biblioteca", + "In memory — nothing is saved": "En memoria: no se guarda nada", + "In use": "En uso", + "In-memory session: these settings last until LightCraft quits.": "Sesión en memoria: estos ajustes duran hasta que se cierre LightCraft.", + "Include subfolders": "Incluir subcarpetas", + "Increase Brush Feather": "Aumentar desvanecimiento del pincel", + "Increase Brush Size": "Aumentar tamaño del pincel", + "Info": "Información", + "Info (I)": "Información (I)", + "Info Overlay Off": "Superposición de información desactivada", + "Info Overlay: Exposure": "Superposición de información: exposición", + "Info Overlay: File": "Superposición de información: archivo", + "Info overlay": "Superposición de información", + "Insert at the cursor": "Insertar en el cursor", + "Inspect Photo": "Inspeccionar foto", + "Instant ’70s": "Instantánea de los 70", + "Interface": "Interfaz", + "Interface language": "Idioma de la interfaz", + "Intersect": "Intersecar", + "Intersect with": "Intersecar con", + "Into one folder": "En una sola carpeta", + "Invert": "Invertir", + "Invert Component": "Invertir componente", + "Invert Mask": "Invertir máscara", + "JPEG (Large)": "JPEG (grande)", + "JPEG (Small)": "JPEG (pequeño)", + "Join the ArtCraft Discord": "Unirse al Discord de ArtCraft", + "Join the ArtCraft Discord…": "Unirse al Discord de ArtCraft…", + "Join the ArtCraft community on Discord": "Unirse a la comunidad de ArtCraft en Discord", + "Keep in Local": "Conservar en Local", + "Keep it on a drive with room: smart previews are about 1 MB per photo. The thumbnail cache stays in the library.": "Conviene un disco con espacio: las vistas previas inteligentes ocupan aproximadamente 1 MB por foto. La caché de miniaturas permanece en la biblioteca.", + "Keyboard Shortcuts": "Métodos abreviados de teclado", + "Keyboard shortcuts": "Métodos abreviados de teclado", + "Keyword": "Palabra clave", + "Keyword Painter": "Pintor de palabras clave", + "Keyword Set": "Conjunto de palabras clave", + "Keyword Sets": "Conjuntos de palabras clave", + "Keyword Suggestions": "Sugerencias de palabras clave", + "Keyword to paint": "Palabra clave para pintar", + "Keywords": "Palabras clave", + "Keywords (K)": "Palabras clave (K)", + "Keywords you add appear here; ⌥1–⌥9 apply them.": "Las palabras clave añadidas aparecen aquí; ⌥1–⌥9 las aplican.", + "Label": "Etiqueta", + "Landscape": "Paisaje", + "Language": "Idioma", + "Largest long edge the Detail view renders at; larger is sharper on big displays but slower.": "Lado largo máximo con el que se renderiza la vista Detalle; un valor mayor es más nítido en pantallas grandes, pero más lento.", + "Last": "Último", + "Last GPU fallback": "Último recurso alternativo de GPU", + "Last commit": "Último commit", + "Last loupe render": "Último renderizado de lupa", + "Last view": "Última vista", + "Lateral Chromatic Aberration": "Aberración cromática lateral", + "Layer": "Capa", + "Layers": "Capas", + "Leave them": "Dejarlas", + "Lens": "Objetivo", + "Lens Blur": "Desenfoque de objetivo", + "Lens Corrections": "Correcciones de lente", + "Level": "Nivel", + "Library": "Biblioteca", + "Library Info": "Información de la biblioteca", + "Library Originals": "Originales de la biblioteca", + "Library Preferences": "Preferencias de la biblioteca", + "Library State": "Estado de la biblioteca", + "Library saved": "Biblioteca guardada", + "Licence:": "Licencia:", + "Light": "Luz", + "LightCraft Default": "Predeterminado de LightCraft", + "LightCraft Help": "Ayuda de LightCraft", + "LightCraft Preset": "Ajuste preestablecido de LightCraft", + "LightCraft Presets": "Ajustes preestablecidos de LightCraft", + "LightCraft Website": "Sitio web de LightCraft", + "LightCraft could not start": "No se pudo iniciar LightCraft", + "LightCraft couldn't find where to keep your library.": "LightCraft no encontró dónde guardar la biblioteca.", + "LightCraft couldn't open the library at": "LightCraft no pudo abrir la biblioteca en", + "LightCraft on GitHub": "LightCraft en GitHub", + "LightCraft website": "Sitio web de LightCraft", + "LightCraft — Second Window": "LightCraft: segunda ventana", + "Lights": "Claros", + "Limit file size (KB, 0 = off)": "Limitar tamaño de archivo (KB, 0 = desactivado)", + "Line delta": "Diferencia de líneas", + "Linear": "Lineal", + "Linear Gradient": "Degradado lineal", + "Lines +/−": "Líneas +/−", + "Lines added": "Líneas añadidas", + "Lines deleted": "Líneas eliminadas", + "List Albums": "Enumerar álbumes", + "List Develop Controls": "Enumerar controles de revelado", + "List History": "Enumerar historial", + "List Presets": "Enumerar ajustes preestablecidos", + "List Profiles": "Enumerar perfiles", + "List this folder in Local from now on": "Mostrar esta carpeta en Local a partir de ahora", + "Local": "Local", + "Local folders": "Carpetas locales", + "Local library — no cloud account needed": "Biblioteca local: no requiere cuenta en la nube", + "Locate Missing File…": "Localizar archivo que falta…", + "Locate Photo": "Localizar foto", + "Location": "Ubicación", + "Lock": "Bloquear", + "Lock Crop Aspect": "Bloquear aspecto del recorte", + "Locked": "Bloqueada", + "Long Edge": "Lado largo", + "Looking for photos…": "Buscando fotos…", + "Lossless": "Sin pérdida", + "Low": "Bajo", + "Luminance": "Luminancia", + "Luminance Range": "Rango de luminancia", + "Lush Greens": "Verdes exuberantes", + "Magenta": "Magenta", + "Magenta Hue": "Tono magenta", + "Magenta Luminance": "Luminancia magenta", + "Magenta Saturation": "Saturación magenta", + "Make Candidate the Select": "Convertir la candidata en la selección", + "Manual": "Manual", + "Marquee zoom": "Zoom de selección rectangular", + "Mask Adjustment": "Ajuste de máscara", + "Masking": "Máscaras", + "Masking (M)": "Máscaras (M)", + "Masks": "Máscaras", + "Match": "Coincidir con", + "Match Total Exposures": "Igualar exposiciones totales", + "Matte": "Mate", + "Matte Soft": "Mate suave", + "Med": "Medio", + "Medium Contrast": "Contraste medio", + "Megapixels": "Megapíxeles", + "Memory Budget": "Presupuesto de memoria", + "Memory Usage": "Uso de memoria", + "Memory budget": "Presupuesto de memoria", + "Memory for caches": "Memoria para cachés", + "Menu": "Menú", + "Merge": "Combinar", + "Merge Keywords": "Combinar palabras clave", + "Merge into…": "Combinar con…", + "Merged": "Combinadas", + "Merged PRs": "PR fusionadas", + "Merging…": "Combinando…", + "Meta's terms, not LightCraft's. Downloading it means accepting them.": "Términos de Meta, no de LightCraft. Descargarlo implica aceptarlos.", + "Metadata": "Metadatos", + "Metadata Preset": "Ajuste preestablecido de metadatos", + "Metadata Presets": "Ajustes preestablecidos de metadatos", + "Metadata preset": "Ajuste preestablecido de metadatos", + "Midpoint": "Punto medio", + "Midtones": "Medios tonos", + "Midtones Hue": "Tono de medios tonos", + "Midtones Luminance": "Luminancia de medios tonos", + "Midtones Saturation": "Saturación de medios tonos", + "Midtones split": "División de medios tonos", + "Minimize": "Minimizar", + "Missing Photos": "Fotos que faltan", + "Missing metadata ({camera}, {title}…) leaves an empty gap; a name that comes out empty keeps the original name.": "Los metadatos que faltan ({camera}, {title}…) dejan un hueco vacío; un nombre que resulta vacío conserva el nombre original.", + "Misty Morning": "Mañana brumosa", + "Mode": "Modo", + "Model": "Modelo", + "Models": "Modelos", + "Modified Date": "Fecha de modificación", + "Monday": "Lunes", + "Mono Red Filter": "Mono con filtro rojo", + "Mono Rich": "Mono intenso", + "Mono Soft": "Mono suave", + "Monochrome": "Monocromo", + "Month": "Mes", + "Moody": "Atmosférico", + "Moody Portrait": "Retrato atmosférico", + "More": "Más", + "More preset options": "Más opciones de ajustes preestablecidos", + "Move": "Mover", + "Move Album": "Mover álbum", + "Move Down": "Bajar", + "Move Down in Stack": "Bajar en la pila", + "Move Folder": "Mover carpeta", + "Move Folder To…": "Mover carpeta a…", + "Move Mask": "Mover máscara", + "Move Preset to Group": "Mover ajuste preestablecido a un grupo", + "Move Preset to New Group": "Mover ajuste preestablecido a un nuevo grupo", + "Move Up": "Subir", + "Move Up in Stack": "Subir en la pila", + "Move in Stack": "Mover en la pila", + "Move the files (into the library's Originals/, or a folder you choose), removing them from the source": "Mover los archivos (a Originals/ de la biblioteca o a una carpeta a elección) y quitarlos del origen", + "Move them": "Moverlas", + "Move to": "Mover a", + "Move to Group": "Mover a un grupo", + "Move: the files are moved to the folder below; each original (and its XMP sidecar) is removed from the source only after its copy is verified. Duplicates and files that fail stay where they are.": "Mover: los archivos se mueven a la carpeta de abajo; cada original (y su archivo XMP complementario) se quita del origen solo después de verificar su copia. Los duplicados y los archivos que fallan quedan donde están.", + "Moves every photo imported from this disk to Recently Deleted; no file is touched": "Mueve todas las fotos importadas desde este disco a Eliminadas recientemente; no se toca ningún archivo", + "Moves the photos imported from this folder to Recently Deleted; no file is touched": "Mueve las fotos importadas desde esta carpeta a Eliminadas recientemente; no se toca ningún archivo", + "Muted": "Apagado", + "Muted Film": "Película apagada", + "My Photos": "Mis fotos", + "My Photos Panel": "Panel Mis fotos", + "Name": "Nombre", + "Name (A–Z)": "Nombre (A–Z)", + "Name of the folder the original is in": "Nombre de la carpeta en la que está el original", + "Named": "Con nombre", + "Named People": "Personas con nombre", + "Names appear in the label menu, the filter bar and the Info panel, and are written to XMP. Empty = the colour's name.": "Los nombres aparecen en el menú de etiquetas, la barra de filtros y el panel Información, y se escriben en XMP. Vacío = el nombre del color.", + "Navigator": "Navegador", + "Needs Work": "Necesita trabajo", + "Neon Dusk": "Atardecer de neón", + "Neon Night": "Noche de neón", + "Nested group": "Grupo anidado", + "Neutral": "Neutro", + "Never": "Nunca", + "New": "Nuevo", + "New Album": "Nuevo álbum", + "New Album…": "Nuevo álbum…", + "New Folder…": "Nueva carpeta…", + "New Group…": "Nuevo grupo…", + "New Smart Album": "Nuevo álbum inteligente", + "New Smart Album from Filter…": "Nuevo álbum inteligente desde el filtro…", + "New Smart Album…": "Nuevo álbum inteligente…", + "New album": "Nuevo álbum", + "New album…": "Nuevo álbum…", + "New blank PDF": "Nuevo PDF en blanco", + "New name": "Nuevo nombre", + "New time": "Nueva hora", + "New…": "Nuevo…", + "Next Photo": "Foto siguiente", + "Night Blue": "Azul nocturno", + "No": "No", + "No Camera or Card Found": "No se encontró ninguna cámara ni tarjeta", + "No Date Groups": "Sin grupos por fecha", + "No Stars": "Sin estrellas", + "No Tool": "Sin herramienta", + "No automatic versions.": "Sin versiones automáticas.", + "No camera": "Sin cámara", + "No contributor data was built into this copy.": "Esta copia no incluye datos de colaboradores.", + "No date": "Sin fecha", + "No dust spots found": "No se encontraron manchas de polvo", + "No edits yet.": "Aún no hay ediciones.", + "No exposure information": "Sin información de exposición", + "No filter presets yet": "Aún no hay ajustes preestablecidos de filtro", + "No lens data embedded in this file (DNG lens opcodes only).": "Este archivo no incluye datos de objetivo (solo opcodes de objetivo DNG).", + "No masks yet. Choose a mask type above.": "Aún no hay máscaras. Elegir un tipo de máscara arriba.", + "No matching photos": "No hay fotos coincidentes", + "No metadata found": "No se encontraron metadatos", + "No model credits were built into this copy.": "Esta copia no incluye créditos de modelos.", + "No named people in these photos": "No hay personas con nombre en estas fotos", + "No named people yet": "Aún no hay personas con nombre", + "No new presets": "No hay ajustes preestablecidos nuevos", + "No photo selected": "No hay ninguna foto seleccionada", + "No photos": "Sin fotos", + "No photos found": "No se encontraron fotos", + "No photos in this folder": "No hay fotos en esta carpeta", + "No photos match the active filters": "Ninguna foto coincide con los filtros activos", + "No photos match the filters (View → Clear Filters)": "Ninguna foto coincide con los filtros (Vista → Borrar filtros)", + "No raw photos yet: cameras appear here once their photos are in the library.": "Aún no hay fotos RAW: las cámaras aparecen aquí cuando sus fotos están en la biblioteca.", + "No reference photo": "Sin foto de referencia", + "No usable GPU found: rendering on the CPU": "No se encontró ninguna GPU utilizable: renderizado en la CPU", + "No versions yet. Create one to keep this look.": "Aún no hay versiones. Crear una permite conservar este aspecto.", + "Noise": "Ruido", + "Noise Reduction": "Reducción de ruido", + "Non-raw photos": "Fotos no RAW", + "None": "Ninguno", + "Not Now": "Ahora no", + "Nothing to compare": "Nada que comparar", + "Nothing to survey": "Nada que mostrar en Encuesta", + "Nudge Develop Value": "Ajustar valor de revelado", + "OK": "Aceptar", + "Object": "Objeto", + "Object Mask": "Máscara de objeto", + "Object and Describe masks use SAM 3, Meta's segmentation model. It isn't part of LightCraft, and everything else works without it.": "Las máscaras Objeto y Describir usan SAM 3, el modelo de segmentación de Meta. No forma parte de LightCraft y todo lo demás funciona sin él.", + "October 2026": "Octubre de 2026", + "Off": "Desactivado", + "Offset X": "Desplazamiento X", + "Offset Y": "Desplazamiento Y", + "Opacity": "Opacidad", + "Opacity (%)": "Opacidad (%)", + "Open Library": "Abrir biblioteca", + "Open Library…": "Abrir biblioteca…", + "Open in": "Abrir en", + "Open in Detail": "Abrir en Detalle", + "Open the place in OpenStreetMap": "Abrir el lugar en OpenStreetMap", + "Open…": "Abrir…", + "Optics": "Óptica", + "Optimize Library": "Optimizar biblioteca", + "Optional name": "Nombre opcional", + "Options": "Opciones", + "Orange": "Naranja", + "Orange Hue": "Tono naranja", + "Orange Luminance": "Luminancia naranja", + "Orange Saturation": "Saturación naranja", + "Orientation": "Orientación", + "Original": "Original", + "Original + Settings": "Original + ajustes", + "Original extension without the dot": "Extensión original sin el punto", + "Original file name without its extension": "Nombre del archivo original sin la extensión", + "Original files": "Archivos originales", + "Original offline · editing the smart preview": "Original sin conexión · editando la vista previa inteligente", + "Original offline · no smart preview": "Original sin conexión · sin vista previa inteligente", + "Originals": "Originales", + "Other images (JPEG, PNG, TIFF, HEIC…)": "Otras imágenes (JPEG, PNG, TIFF, HEIC…)", + "Overlay": "Superposición", + "Overwrite": "Sobrescribir", + "Pages": "Páginas", + "Paint": "Pintar", + "Paint only areas like the one under the brush": "Pintar solo zonas similares a la que está bajo el pincel", + "Paint over a distraction on the photo to remove it.": "Pintar sobre un elemento que distrae en la foto para eliminarlo.", + "Paint overlay": "Superposición de pintura", + "Panorama": "Panorámica", + "Panorama Merge": "Combinación de panorámica", + "Panorama Merge Preview": "Vista previa de combinación de panorámica", + "Panorama with Last Settings": "Panorámica con los últimos ajustes", + "Panoramas": "Panorámicas", + "Panorama…": "Panorámica…", + "Parametric curve": "Curva paramétrica", + "Paste": "Pegar", + "Paste Edit Settings": "Pegar ajustes de edición", + "Paste Metadata": "Pegar metadatos", + "Paste Selected Settings": "Pegar ajustes seleccionados", + "Paste Selected Settings…": "Pegar ajustes seleccionados…", + "Paste Settings": "Pegar ajustes", + "Paste Settings from Previous": "Pegar ajustes de la anterior", + "Paste from Previous": "Pegar de la anterior", + "Paste pages": "Pegar páginas", + "Pastel Haze": "Bruma pastel", + "People": "Personas", + "Percent": "Porcentaje", + "Percentage": "Porcentaje", + "Performance": "Rendimiento", + "Person": "Persona", + "Perspective": "Perspectiva", + "Pet Eye": "Ojos de mascota", + "Pet Eye Catchlight": "Reflejo ocular de mascota", + "Photo": "Foto", + "Photo Grid": "Cuadrícula de fotos", + "Photo Grid (G)": "Cuadrícula de fotos (G)", + "Photo Merge": "Combinación de fotos", + "Photo ▸ Edit in External Editor (⇧⌘E) renders a 16-bit TIFF copy, stacks it with the original and opens it here (an app name on macOS, a program path elsewhere).": "Foto ▸ Editar en el editor externo (⇧⌘E) renderiza una copia TIFF de 16 bits, la apila con el original y la abre aquí (un nombre de aplicación en macOS, una ruta de programa en otros sistemas).", + "Photos": "Fotos", + "Photos per bracket (0 = auto)": "Fotos por horquillado (0 = automático)", + "Photos seen in Local but never added or changed leave the catalog when their folder hasn't been browsed for this long (checked when the library opens). Files stay on disk; browsing the folder shows them again.": "Las fotos vistas en Local, pero nunca añadidas ni modificadas, salen del catálogo cuando su carpeta no se ha explorado durante este tiempo (se comprueba al abrir la biblioteca). Los archivos permanecen en el disco; al explorar la carpeta vuelven a aparecer.", + "Photos that arrive in this folder (tethering, a scanner, a sync app) are added as soon as they're complete.": "Las fotos que llegan a esta carpeta (captura conectada, un escáner, una aplicación de sincronización) se añaden en cuanto están completas.", + "Pick": "Seleccionada", + "Pick Flag": "Marca de seleccionada", + "Pick a colour on the photo with the eyedropper.": "Elegir un color en la foto con el cuentagotas.", + "Pick the sharpest photo of each burst": "Elegir la foto más nítida de cada ráfaga", + "Picked": "Seleccionada", + "Picks": "Seleccionadas", + "Pictures": "Imágenes", + "Pixels": "Píxeles", + "Point Color": "Color de punto", + "Point Curve": "Curva de puntos", + "Point Curve Presets": "Ajustes preestablecidos de curva de puntos", + "Point curve": "Curva de puntos", + "Portrait": "Retrato", + "Position": "Posición", + "Preferences": "Preferencias", + "Preferences…": "Preferencias…", + "Prepare AI Masks": "Preparar máscaras con IA", + "Preset": "Ajuste preestablecido", + "Preset name": "Nombre del ajuste preestablecido", + "Presets": "Ajustes preestablecidos", + "Presets & Profiles": "Ajustes preestablecidos y perfiles", + "Presets (Shift+P)": "Ajustes preestablecidos (Mayús+P)", + "Presets and profiles": "Ajustes preestablecidos y perfiles", + "Presets ▾": "Ajustes preestablecidos ▾", + "Preview": "Vista previa", + "Preview Build Progress": "Progreso de generación de vistas previas", + "Preview only": "Solo vista previa", + "Preview size": "Tamaño de la vista previa", + "Previews": "Vistas previas", + "Previous Photo": "Foto anterior", + "Print": "Imprimir", + "Print…": "Imprimir…", + "Profile": "Perfil", + "Profile Browser": "Explorador de perfiles", + "Profile Distortion": "Distorsión del perfil", + "Profile Menu": "Menú de perfiles", + "Profile Vignetting": "Viñeteado del perfil", + "Profile:": "Perfil:", + "Profiles": "Perfiles", + "Projection": "Proyección", + "Proof copy created": "Copia de prueba creada", + "Properties": "Propiedades", + "Public domain": "Dominio público", + "Pupil Size": "Tamaño de pupila", + "Purple": "Púrpura", + "Purple Amount": "Cantidad de púrpura", + "Purple Hue": "Tono púrpura", + "Purple Hue High": "Tono púrpura alto", + "Purple Hue Low": "Tono púrpura bajo", + "Purple Luminance": "Luminancia púrpura", + "Purple Saturation": "Saturación púrpura", + "Put the locations you removed from Local back (no files change)": "Volver a poner en Local las ubicaciones quitadas (no cambia ningún archivo)", + "Quality": "Calidad", + "Query Photos": "Consultar fotos", + "Quick Collection": "Colección rápida", + "Quick Develop": "Revelado rápido", + "Quiet Green": "Verde sereno", + "Quit": "Salir", + "Quit Anyway": "Salir de todos modos", + "Quit LightCraft": "Salir de LightCraft", + "Quit with unsaved changes?": "¿Salir con cambios sin guardar?", + "RAW decoding": "Decodificación RAW", + "Radial": "Radial", + "Radial Gradient": "Degradado radial", + "Radius": "Radio", + "Random": "Aleatorio", + "Range": "Rango", + "Rating": "Valoración", + "Rating cleared": "Valoración eliminada", + "Ratings & flags": "Valoraciones y marcas", + "Raw": "RAW", + "Raw default": "Predeterminado RAW", + "Raw defaults": "Valores predeterminados RAW", + "Raw files": "Archivos RAW", + "Raw photos": "Fotos RAW", + "Raw photos as DNG, with the edits embedded. Size, color and output options don't apply.": "Fotos RAW como DNG, con las ediciones incrustadas. Las opciones de tamaño, color y salida no se aplican.", + "Read Metadata from File": "Leer metadatos del archivo", + "Read mode": "Modo de lectura", + "Read the SAM License": "Leer la licencia de SAM", + "Reading": "Leyendo", + "Reading folder…": "Leyendo carpeta…", + "Real name": "Nombre real", + "Recent": "Recientes", + "Recent Keywords": "Palabras clave recientes", + "Recently Added": "Añadidas recientemente", + "Recently Deleted": "Eliminadas recientemente", + "Red": "Rojo", + "Red Eye": "Ojos rojos", + "Red Hue": "Tono rojo", + "Red Luminance": "Luminancia roja", + "Red Primary": "Rojo primario", + "Red Saturation": "Saturación roja", + "Red channel": "Canal rojo", + "Red/Cyan Fringe": "Franja roja/cian", + "Redo": "Rehacer", + "Reference": "Referencia", + "Reference View": "Vista de referencia", + "Reference the files where they are": "Hacer referencia a los archivos donde están", + "Refine": "Refinar", + "Refine AI Mask Detail": "Refinar detalle de la máscara con IA", + "Refine Edges": "Refinar bordes", + "Refine Mask Edges": "Refinar bordes de la máscara", + "Refine Saturation": "Refinar saturación", + "Refining the mask's detail…": "Refinando el detalle de la máscara…", + "Refresh Source": "Actualizar origen", + "Refresh Source (/)": "Actualizar origen (/)", + "Reject": "Rechazar", + "Reject below focus": "Rechazar por debajo del enfoque", + "Rejected": "Rechazada", + "Rejects": "Rechazadas", + "Reliability": "Fiabilidad", + "Relink Photo": "Volver a vincular foto", + "Reload Changed Files": "Recargar archivos modificados", + "Reload from Disk": "Recargar desde el disco", + "Remove": "Quitar", + "Remove (H)": "Eliminar (H)", + "Remove CA": "Eliminar AC", + "Remove Chromatic Aberration": "Eliminar aberración cromática", + "Remove Disk from Library": "Quitar disco de la biblioteca", + "Remove Disk from Library…": "Quitar disco de la biblioteca…", + "Remove Face Box": "Quitar recuadro de cara", + "Remove Folder from Library": "Quitar carpeta de la biblioteca", + "Remove a filter above, or choose Clear all": "Quitar un filtro arriba o elegir Borrar todo", + "Remove from Album": "Quitar del álbum", + "Remove from Favorites": "Quitar de Favoritos", + "Remove from Library…": "Quitar de la biblioteca…", + "Remove from Local": "Quitar de Local", + "Remove from Photo": "Quitar de la foto", + "Remove from Selected Photos": "Quitar de las fotos seleccionadas", + "Remove from Stack": "Quitar de la pila", + "Remove from Target Album": "Quitar del álbum de destino", + "Remove from survey": "Quitar de Encuesta", + "Remove location info": "Quitar información de ubicación", + "Remove this face box (undo with Edit ▸ Undo)": "Quitar este recuadro de cara (deshacer con Editar ▸ Deshacer)", + "Remove this filter": "Quitar este filtro", + "Remove this group": "Quitar este grupo", + "Remove this rule": "Quitar esta regla", + "Rename": "Cambiar nombre", + "Rename Album": "Cambiar nombre del álbum", + "Rename Component": "Cambiar nombre del componente", + "Rename Folder": "Cambiar nombre de la carpeta", + "Rename Folder…": "Cambiar nombre de la carpeta…", + "Rename Keyword": "Cambiar nombre de la palabra clave", + "Rename Keyword…": "Cambiar nombre de la palabra clave…", + "Rename Mask": "Cambiar nombre de la máscara", + "Rename Photos": "Cambiar nombre de las fotos", + "Rename Photos…": "Cambiar nombre de las fotos…", + "Rename Preset": "Cambiar nombre del ajuste preestablecido", + "Rename Preview": "Vista previa del cambio de nombre", + "Rename Template Tags": "Etiquetas de plantilla de nombres", + "Rename Version": "Cambiar nombre de la versión", + "Rename…": "Cambiar nombre…", + "Rendering": "Renderizado", + "Rendering…": "Renderizando…", + "Reorder Masks": "Reordenar máscaras", + "Reset": "Restablecer", + "Reset All Curves": "Restablecer todas las curvas", + "Reset Before to Import State": "Restablecer Antes al estado de importación", + "Reset Control": "Restablecer control", + "Reset Crop": "Restablecer recorte", + "Reset Edits": "Restablecer ediciones", + "Reset Parametric Curve": "Restablecer curva paramétrica", + "Reset Section": "Restablecer sección", + "Reset Tone Curve": "Restablecer curva de tonos", + "Reset all curves: point curves (all channels) and parametric (double-click a channel to reset only that one)": "Restablecer todas las curvas: curvas de puntos (todos los canales) y paramétrica (doble clic en un canal para restablecer solo ese)", + "Reset all edits (Cmd+Shift+R)": "Restablecer todas las ediciones (Cmd+Mayús+R)", + "Reshuffle": "Volver a mezclar", + "Resize Face Box": "Cambiar tamaño del recuadro de cara", + "Resize to": "Cambiar tamaño a", + "Resolution (ppi)": "Resolución (ppp)", + "Restore": "Restaurar", + "Restore Library from Backup…": "Restaurar biblioteca desde copia de seguridad…", + "Restore Version": "Restaurar versión", + "Restore keeps their edits; Import as new deletes the old record first.": "Restaurar conserva sus ediciones; Importar como nuevas elimina antes el registro anterior.", + "Restore them": "Restaurarlas", + "Retouch": "Retoque", + "Reverse the order": "Invertir el orden", + "Revert": "Revertir", + "Review": "Revisión", + "Review Import": "Revisar importación", + "Right-click a photo ▸ Set as Reference Photo, then View → Reference View (Shift+R)": "Clic derecho en una foto ▸ Definir como foto de referencia y, después, Vista → Vista de referencia (Mayús+R)", + "Right-click a preset to delete it": "Clic derecho en un ajuste preestablecido para eliminarlo", + "Rights Usage Terms": "Condiciones de uso de los derechos", + "Rotate": "Rotar", + "Rotate Crop": "Rotar recorte", + "Rotate Crop Aspect": "Rotar aspecto del recorte", + "Rotate Left": "Rotar a la izquierda", + "Rotate Right": "Rotar a la derecha", + "Roughness": "Aspereza", + "Roundness": "Redondez", + "Rules": "Reglas", + "SAM 3 download stopped: it resumes where it left off next time.": "Descarga de SAM 3 detenida: la próxima vez continuará donde se quedó.", + "Same as raw default": "Igual que el predeterminado RAW", + "Sample Mask Color": "Muestrear color de máscara", + "Sample a colour on the photo": "Muestrear un color en la foto", + "Saturation": "Saturación", + "Saturation Range": "Rango de saturación", + "Saturday": "Sábado", + "Save": "Guardar", + "Save As…": "Guardar como…", + "Save Color Label Set": "Guardar conjunto de etiquetas de color", + "Save Current Filter as Preset…": "Guardar filtro actual como ajuste preestablecido…", + "Save Current Keywords as Set…": "Guardar palabras clave actuales como conjunto…", + "Save Export Preset": "Guardar ajuste preestablecido de exportación", + "Save Filter Preset": "Guardar ajuste preestablecido de filtro", + "Save Keyword Set": "Guardar conjunto de palabras clave", + "Save Metadata Preset": "Guardar ajuste preestablecido de metadatos", + "Save Metadata Preset…": "Guardar ajuste preestablecido de metadatos…", + "Save Metadata to File": "Guardar metadatos en el archivo", + "Save Point Curve Preset": "Guardar ajuste preestablecido de curva de puntos", + "Save Point Curve…": "Guardar curva de puntos…", + "Save as Preset": "Guardar como ajuste preestablecido", + "Save as Set…": "Guardar como conjunto…", + "Save as Smart Album…": "Guardar como álbum inteligente…", + "Save as set": "Guardar como conjunto", + "Save as…": "Guardar como…", + "Save preset": "Guardar ajuste preestablecido", + "Scale": "Escala", + "Scan failed": "Error en la exploración", + "Scanning": "Explorando", + "Scores stay on the photos: filter or make smart albums with Focus and Best of Similar Shots.": "Las puntuaciones se quedan en las fotos: se pueden filtrar o crear álbumes inteligentes con Enfoque y La mejor de las tomas similares.", + "Screen": "Pantalla", + "Search": "Buscar", + "Search Photos": "Buscar fotos", + "Search…": "Buscar…", + "Seasons": "Estaciones", + "Second Window": "Segunda ventana", + "Select": "Seleccionar", + "Select All": "Seleccionar todo", + "Select Luminance Range": "Seleccionar rango de luminancia", + "Select Mask": "Seleccionar máscara", + "Select Photos": "Seleccionar fotos", + "Select Spot": "Seleccionar mancha", + "Select a photo to edit": "Seleccionar una foto para editarla", + "Select by": "Seleccionar por", + "Select by Flag, Rating or Label": "Seleccionar por marca, valoración o etiqueta", + "Select photos, then choose View → Survey (N)": "Seleccionar fotos y elegir Vista → Encuesta (N)", + "Select two photos, then choose View → Compare (Shift+C)": "Seleccionar dos fotos y elegir Vista → Comparar (Mayús+C)", + "Selected": "Seleccionada", + "Selecting…": "Seleccionando…", + "Selenium Tone": "Virado al selenio", + "Send Feedback…": "Enviar comentarios…", + "Sepia Tone": "Virado sepia", + "Sequence number zero-padded to N digits (1–9)": "Número de secuencia rellenado con ceros hasta N dígitos (1–9)", + "Sequence number, counting from the start number": "Número de secuencia, contando desde el número inicial", + "Services": "Servicios", + "Set": "Definir", + "Set Album Cover": "Definir portada del álbum", + "Set Before": "Definir Antes", + "Set Color Label": "Definir etiqueta de color", + "Set Crop": "Definir recorte", + "Set Develop Values": "Definir valores de revelado", + "Set Flag": "Definir marca", + "Set Image Zoom and Pan": "Definir zoom y desplazamiento de la imagen", + "Set Mask Adjustments": "Definir ajustes de máscara", + "Set Point Curve": "Definir curva de puntos", + "Set Profile": "Definir perfil", + "Set Rating": "Definir valoración", + "Set Top of Stack": "Definir parte superior de la pila", + "Set as Album Cover": "Definir como portada del álbum", + "Set as Before": "Definir como Antes", + "Set as Reference Photo": "Definir como foto de referencia", + "Set as Target Album": "Definir como álbum de destino", + "Set as Target Album (B adds to it)": "Definir como álbum de destino (B añade a él)", + "Set as Top of Stack": "Definir como parte superior de la pila", + "Set date & time": "Definir fecha y hora", + "Set name": "Nombre del conjunto", + "Set the mix from the photo's colours": "Definir la mezcla a partir de los colores de la foto", + "Settings": "Ajustes", + "Settings file problem": "Problema con el archivo de ajustes", + "Settings new raw photos start from. Changing them doesn't touch photos already in the library.": "Ajustes de partida para las nuevas fotos RAW. Cambiarlos no afecta a las fotos que ya están en la biblioteca.", + "Settings pasted": "Ajustes pegados", + "Settings to include": "Ajustes que incluir", + "Settings…": "Ajustes…", + "Shade": "Sombra", + "Shadow": "Sombra", + "Shadows": "Sombras", + "Shadows Hue": "Tono de sombras", + "Shadows Luminance": "Luminancia de sombras", + "Shadows Saturation": "Saturación de sombras", + "Shadows Tint": "Matiz de sombras", + "Shadows split": "División de sombras", + "Sharpen": "Enfocar", + "Sharpening": "Enfoque", + "Sharpness": "Nitidez", + "Shift": "Desplazar", + "Shift by": "Desplazar en", + "Short Edge": "Lado corto", + "Show": "Mostrar", + "Show All": "Mostrar todo", + "Show Clipping": "Mostrar recorte de tonos", + "Show Deghost Overlay": "Mostrar superposición de eliminación de fantasmas", + "Show Hidden Local Locations": "Mostrar ubicaciones ocultas de Local", + "Show Luminance Map": "Mostrar mapa de luminancia", + "Show Mask": "Mostrar máscara", + "Show Mask Overlay": "Mostrar superposición de máscara", + "Show Mask Pins": "Mostrar chinchetas de máscara", + "Show Original": "Mostrar original", + "Show Photo Counts": "Mostrar recuento de fotos", + "Show Photos with Keyword": "Mostrar fotos con la palabra clave", + "Show Pins": "Mostrar chinchetas", + "Show Source": "Mostrar origen", + "Show Thumbnails": "Mostrar miniaturas", + "Show destination gamut warning": "Mostrar aviso de gama de destino", + "Show display gamut warning": "Mostrar aviso de gama de la pantalla", + "Show file names": "Mostrar nombres de archivo", + "Show in Explorer": "Mostrar en el Explorador", + "Show in File Manager": "Mostrar en el gestor de archivos", + "Show in Finder": "Mostrar en el Finder", + "Show on Map": "Mostrar en el mapa", + "Show overlay (O)": "Mostrar superposición (O)", + "Show ratings, flags and edit badges": "Mostrar valoraciones, marcas e indicadores de edición", + "Show the Navigator while zoomed in": "Mostrar el Navegador al ampliar", + "Show the template tags; click one to insert it": "Mostrar las etiquetas de plantilla; hacer clic en una para insertarla", + "Show/Hide Mask": "Mostrar/ocultar máscara", + "Show/hide My Photos panel": "Mostrar/ocultar panel Mis fotos", + "Shutter speed": "Velocidad de obturación", + "Side panels": "Paneles laterales", + "Sidecar names": "Nombres de archivos complementarios", + "Size": "Tamaño", + "Size (% of short edge)": "Tamaño (% del lado corto)", + "Size limit": "Límite de tamaño", + "Skip": "Omitir", + "Sky": "Cielo", + "Slideshow": "Presentación", + "Slideshow paused": "Presentación en pausa", + "Slideshow resumed": "Presentación reanudada", + "Slideshow · Space pauses · Esc ends": "Presentación · Espacio para pausar · Esc para terminar", + "Smart Album": "Álbum inteligente", + "Smart Album Rule Fields": "Campos de reglas de álbum inteligente", + "Smart Preview Status": "Estado de las vistas previas inteligentes", + "Smart Previews Location": "Ubicación de las vistas previas inteligentes", + "Smart preview available": "Vista previa inteligente disponible", + "Smart previews": "Vistas previas inteligentes", + "Smart previews folder changed": "Carpeta de vistas previas inteligentes cambiada", + "Smoothness": "Suavidad", + "Soft B&W": "ByN suave", + "Soft Pastel": "Pastel suave", + "Soft Proofing": "Prueba en pantalla", + "Soft Skin": "Piel suave", + "Sort": "Ordenar", + "Sort by": "Ordenar por", + "Source": "Origen", + "Source code on GitHub": "Código fuente en GitHub", + "Spherical": "Esférica", + "Spiral": "Espiral", + "Split Stack": "Dividir pila", + "Spring Fresh": "Frescura primaveral", + "Square Grid": "Cuadrícula de cuadrados", + "Square Grid (G toggles)": "Cuadrícula de cuadrados (G alterna)", + "Square Grid: show file names and formats": "Cuadrícula de cuadrados: mostrar nombres y formatos de archivo", + "Stack": "Pila", + "Stack photos taken within this time of each other:": "Apilar fotos tomadas con esta diferencia de tiempo como máximo:", + "Stacks": "Pilas", + "Standard": "Estándar", + "Star rating (0–5)": "Valoración por estrellas (0–5)", + "Start at": "Empezar en", + "Start number": "Número inicial", + "Starting": "Iniciando", + "Startup": "Inicio", + "State / Province": "Estado / provincia", + "Stop": "Detener", + "Stop Building Previews": "Detener generación de vistas previas", + "Stop Using as Target Album": "Dejar de usar como álbum de destino", + "Stopping…": "Deteniendo…", + "Straighten": "Enderezar", + "Straighten Tool": "Herramienta Enderezar", + "Strong Contrast": "Contraste fuerte", + "Style": "Estilo", + "Subfolder": "Subcarpeta", + "Subfolder name": "Nombre de la subcarpeta", + "Subject": "Sujeto", + "Subtract": "Restar", + "Subtract from this mask": "Restar de esta máscara", + "Suggestions": "Sugerencias", + "Summer Haze": "Bruma estival", + "Sunday": "Domingo", + "Survey": "Encuesta", + "Survey (N)": "Encuesta (N)", + "Swap": "Intercambiar", + "Swap Aspect (X)": "Intercambiar aspecto (X)", + "Swap Before and After": "Intercambiar Antes y Después", + "Swap Before and After Settings": "Intercambiar ajustes de Antes y Después", + "Swap Compare Photos": "Intercambiar fotos comparadas", + "Switch light / dark theme": "Cambiar tema claro / oscuro", + "Sync Settings": "Sincronizar ajustes", + "System": "Sistema", + "System Info": "Información del sistema", + "System Info…": "Información del sistema…", + "System default": "Predeterminado del sistema", + "Table": "Tabla", + "Tags": "Etiquetas", + "Tags: see Tags beside the template. Files are renamed on disk (with their XMP sidecars); existing names get -1, -2…": "Etiquetas: ver Etiquetas junto a la plantilla. Los archivos cambian de nombre en el disco (con sus archivos XMP complementarios); los nombres existentes reciben -1, -2…", + "Take a snapshot": "Crear una instantánea", + "Targeted Adjustment": "Ajuste selectivo", + "Targeted adjustment: drag up/down on the photo to adjust the colours there": "Ajuste selectivo: arrastrar hacia arriba/abajo en la foto para ajustar los colores de ese punto", + "Targeted adjustment: drag up/down on the photo to adjust the curve there": "Ajuste selectivo: arrastrar hacia arriba/abajo en la foto para ajustar la curva en ese punto", + "Teal & Amber": "Verde azulado y ámbar", + "Teal & Orange": "Verde azulado y naranja", + "Temp": "Temp.", + "Temperature": "Temperatura", + "Template": "Plantilla", + "Temporary session — your library isn't open, and nothing you do here is saved.": "Sesión temporal: la biblioteca no está abierta y no se guarda nada de lo que se haga aquí.", + "Text": "Texto", + "Text direction": "Dirección del texto", + "Texture": "Textura", + "That includes the photos in the folders inside it.": "Esto incluye las fotos de las carpetas que contiene.", + "The SAM 3 model is installed: Object and Describe masks are ready.": "El modelo SAM 3 está instalado: las máscaras Objeto y Describir están listas.", + "The download didn't work:": "La descarga no funcionó:", + "The number at the end of the original name (IMG_0042 → 0042)": "El número al final del nombre original (IMG_0042 → 0042)", + "The original extension is always added; {ext} only puts it inside the name as well.": "La extensión original siempre se añade; {ext} solo la incluye también dentro del nombre.", + "The original files, unchanged, each with an XMP sidecar holding its edits.": "Los archivos originales, sin cambios, cada uno con un archivo XMP complementario que contiene sus ediciones.", + "The photo has these settings": "La foto tiene estos ajustes", + "Theme": "Tema", + "They can be restored from Recently Deleted until it is emptied.": "Se pueden restaurar desde Eliminadas recientemente hasta que se vacíe.", + "They move to Recently Deleted and can be restored; no file on disk is touched.": "Pasan a Eliminadas recientemente y se pueden restaurar; no se toca ningún archivo del disco.", + "Thirds": "Tercios", + "This Computer": "Este equipo", + "This build has no download location for the model yet: put the files in the folder above yourself (see docs/ai-masks.md).": "Esta compilación aún no tiene una ubicación de descarga para el modelo: hay que colocar los archivos manualmente en la carpeta de arriba (ver docs/ai-masks.md).", + "This folder is not available (drive disconnected?). Smart previews are not built or used until it is back or you choose another folder.": "Esta carpeta no está disponible (¿unidad desconectada?). Las vistas previas inteligentes no se generan ni se usan hasta que vuelva a estar disponible o se elija otra carpeta.", + "This photo can't be opened": "No se puede abrir esta foto", + "Threshold": "Umbral", + "Thumbnail cache": "Caché de miniaturas", + "Thursday": "Jueves", + "Time zone": "Zona horaria", + "Tint": "Matiz", + "Title": "Título", + "Title metadata": "Título en los metadatos", + "Toggle Keyword from Set": "Activar/desactivar palabra clave del conjunto", + "Toggle Mask": "Activar/desactivar máscara", + "Toggle Section": "Activar/desactivar sección", + "Toggle Zoom": "Alternar zoom", + "Tone Curve": "Curva de tonos", + "Tools": "Herramientas", + "Top Level": "Nivel superior", + "Transfer": "Transferencia", + "Treatment": "Tratamiento", + "Triangle": "Triángulo", + "Try Again": "Reintentar", + "Try Saving Again": "Volver a intentar guardar", + "Tuesday": "Martes", + "Tungsten": "Tungsteno", + "Turn Off": "Desactivar", + "Turn on Include subfolders, or pick another folder under Local": "Activar Incluir subcarpetas o elegir otra carpeta en Local", + "Type": "Tipo", + "Uncheck All": "Desmarcar todo", + "Undo": "Deshacer", + "Unedited": "Sin editar", + "Unedited raws": "RAW sin editar", + "Unflag": "Quitar marca", + "Unflagged": "Sin marca", + "Ungroup Stack": "Desagrupar pila", + "Unknown Date": "Fecha desconocida", + "Unknown tags stay as typed — check the preview for a {typo}.": "Las etiquetas desconocidas se quedan tal como se escribieron: revisar la vista previa por si hay un {typo}.", + "Unrated": "Sin valorar", + "Unreadable": "Ilegible", + "Update": "Actualizar", + "Update Mask Shape": "Actualizar forma de la máscara", + "Update Preset with Current Settings": "Actualizar ajuste preestablecido con los ajustes actuales", + "Update Rules from Current Filter": "Actualizar reglas desde el filtro actual", + "Update Version": "Actualizar versión", + "Update Version with Current Settings": "Actualizar versión con los ajustes actuales", + "Update with Current Settings": "Actualizar con los ajustes actuales", + "Updated the edits saved in the external editor": "Se actualizaron las ediciones guardadas en el editor externo", + "Upright": "Vertical", + "Urban": "Urbano", + "Use Keyword Set": "Usar conjunto de palabras clave", + "Use Library Folder": "Usar carpeta de la biblioteca", + "Use camera-specific defaults": "Usar valores predeterminados específicos de la cámara", + "Use the GPU for rendering": "Usar la GPU para renderizar", + "User Presets": "Ajustes preestablecidos del usuario", + "Username": "Nombre de usuario", + "Value": "Valor", + "Variance": "Varianza", + "Version": "Versión", + "Version name": "Nombre de la versión", + "Versions": "Versiones", + "Versions (Shift+V)": "Versiones (Mayús+V)", + "Vertical": "Vertical", + "Vertical text": "Texto vertical", + "Vibrance": "Intensidad", + "Videos": "Vídeos", + "View": "Vista", + "Viewing and sharing": "Visualización y uso compartido", + "Vignette": "Viñeta", + "Vignetting": "Viñeteado", + "Vintage": "Vintage", + "Virtual Copy": "Copia virtual", + "Visible": "Visible", + "Visualize Spots": "Visualizar manchas", + "Visualize Spots (A)": "Visualizar manchas (A)", + "Visualize range": "Visualizar rango", + "Vivid": "Vívido", + "Vivid Pop": "Colores vivos", + "Warm Film": "Película cálida", + "Warm Glow": "Resplandor cálido", + "Warm Print": "Copia cálida", + "Warm Table": "Mesa cálida", + "Watched folder": "Carpeta vigilada", + "Watermark": "Marca de agua", + "Wednesday": "Miércoles", + "What's New": "Novedades", + "When rated or hovered": "Al valorar o pasar el puntero", + "White Balance": "Balance de blancos", + "White Balance Selector": "Selector de balance de blancos", + "White Balance Selector (W)": "Selector de balance de blancos (W)", + "Whites": "Blancos", + "Width": "Ancho", + "Width (% of photo)": "Ancho (% de la foto)", + "Width (px)": "Ancho (px)", + "Width × Height": "Ancho × alto", + "Will be replaced": "Se reemplazará", + "Will be restored": "Se restaurará", + "Window": "Ventana", + "Winter Blue": "Azul invernal", + "XMP Sidecar Preferences": "Preferencias de archivos XMP complementarios", + "XMP sidecars": "Archivos XMP complementarios", + "Year": "Año", + "Yellow": "Amarillo", + "Yellow Hue": "Tono amarillo", + "Yellow Luminance": "Luminancia amarilla", + "Yellow Saturation": "Saturación amarilla", + "Yes": "Sí", + "You can close this: the download continues, and resumes if interrupted.": "Esta ventana se puede cerrar: la descarga continúa y se reanuda si se interrumpe.", + "Your Name": "Nombre y apellidos", + "Your library couldn't be opened": "No se pudo abrir la biblioteca", + "Zoom": "Zoom", + "Zoom 100%": "Zoom 100%", + "Zoom In": "Acercar", + "Zoom Out": "Alejar", + "Zoom to Fit": "Zoom para encajar", + "Zoom to page level": "Zoom a nivel de página", + "a literal %": "un % literal", + "a removed photo": "una foto eliminada", + "all": "todas", + "and": "y", + "any": "alguna", + "are empty": "están vacíos", + "aren't empty": "no están vacíos", + "blue": "azul", + "comma, separated": "separado, por comas", + "contains": "contiene", + "copyrighted": "con copyright", + "day 01–31": "día 01–31", + "days": "días", + "discarding smart previews": "descartando vistas previas inteligentes", + "doesn't contain": "no contiene", + "e.g. sky · the red car · car, road": "p. ej. sky · the red car · car, road (en inglés)", + "ends with": "termina con", + "false": "no", + "green": "verde", + "hour 00–23": "hora 00–23", + "hours": "horas", + "image": "imagen", + "includes the folders inside it": "incluye las carpetas que contiene", + "is": "es", + "is <": "es <", + "is >": "es >", + "is after": "es posterior a", + "is before": "es anterior a", + "is between": "está entre", + "is empty": "está vacío", + "is in the last": "está en los últimos", + "is unknown": "es desconocido", + "is ≤": "es ≤", + "is ≥": "es ≥", + "isn't": "no es", + "isn't empty": "no está vacío", + "isn't in the last": "no está en los últimos", + "keep names — or e.g. {date}_{seq:3}": "conservar nombres, o p. ej. {date}_{seq:3}", + "latitude, longitude": "latitud, longitud", + "memory only": "solo memoria", + "minute": "minuto", + "month 01–12": "mes 01–12", + "months": "meses", + "none": "ninguna", + "none (CPU rendering)": "ninguna (renderizado por CPU)", + "none of": "ninguna de", + "not available here": "no disponible aquí", + "of the following rules:": "de las siguientes reglas:", + "or": "o", + "pick": "seleccionada", + "previews": "vistas previas", + "publicDomain": "dominio público", + "purple": "púrpura", + "raw": "RAW", + "red": "rojo", + "reject": "rechazada", + "saved in": "guardado en", + "second": "segundo", + "smart previews": "vistas previas inteligentes", + "starts with": "empieza por", + "the photos in view": "las fotos de la vista", + "the selected photos": "las fotos seleccionadas", + "this photo": "esta foto", + "true": "sí", + "unknown": "desconocido", + "video": "vídeo", + "weeks": "semanas", + "year, 2 digits": "año, 2 dígitos", + "year, 4 digits": "año, 4 dígitos", + "years": "años", + "yellow": "amarillo", + "{date} is the capture time; a photo without one uses the time it was imported.": "{date} es la hora de captura; una foto sin ella usa la hora en que se importó.", + "© 2026 Your Name": "© 2026 Nombre y apellidos", + "© Your Name": "© Nombre y apellidos", + "− harder border · + softer border": "− borde más duro · + borde más suave", + "≤ at most": "≤ como máximo", + "≥ at least": "≥ como mínimo", + "日本語": "日本語", + "繁體中文(台灣)": "繁體中文(台灣)", + "Any camera": "Cualquier cámara", + "Any lens": "Cualquier objetivo", + "Any keyword": "Cualquier palabra clave", + "Label Set": "Conjunto de etiquetas", + ", picked": ", seleccionada", + ", rejected": ", rechazada", + "Preview only: editing the camera's embedded JPEG": "Solo vista previa: editando el JPEG incrustado de la cámara", + "Captured from": "Capturadas desde", + "Captured until": "Capturadas hasta", + "Only": "Solo" +} diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 76999b25..ccf810ee 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -121,6 +121,7 @@ language_table! { ZhHant, "zh-hant", "繁體中文(台灣)", "Hant", include_str!("../locales/zh-hant.json"); Ja, "ja", "日本語", "Jpan", include_str!("../locales/ja.json"); PtBr, "pt-br", "Português (Brasil)", "Latn", include_str!("../locales/pt-br.json"); + Es, "es", "Español", "Latn", include_str!("../locales/es.json"); De, "de", "Deutsch", "Latn", include_str!("../locales/de.json"); Ru, "ru", "Русский", "Cyrl", include_str!("../locales/ru.json"); } @@ -449,6 +450,9 @@ mod tests { for tag in ["ru", "ru-RU", "ru_RU.UTF-8", "ru-UA"] { assert_eq!(Locale::parse_tag(tag), Some(Locale::Ru), "{tag}"); } + for tag in ["es", "es-ES", "es_MX.UTF-8", "es-419"] { + assert_eq!(Locale::parse_tag(tag), Some(Locale::Es), "{tag}"); + } assert_eq!(Locale::parse_tag("xx"), None); } @@ -571,6 +575,7 @@ mod tests { ("app.language.simplifiedChinese", Locale::ZhHans), ("app.language.japanese", Locale::Ja), ("app.language.portuguese", Locale::PtBr), + ("app.language.spanish", Locale::Es), ("app.language.german", Locale::De), ("app.language.russian", Locale::Ru), ]; diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 88872e3d..6e78fa35 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -18,6 +18,7 @@ pub const LANGUAGE_COMMANDS: &[UiCommand] = &[ ("app.language.traditionalChinese", crate::i18n::Locale::ZhHant.name(), None, "Edit>Language"), ("app.language.japanese", crate::i18n::Locale::Ja.name(), None, "Edit>Language"), ("app.language.portuguese", crate::i18n::Locale::PtBr.name(), None, "Edit>Language"), + ("app.language.spanish", crate::i18n::Locale::Es.name(), None, "Edit>Language"), ("app.language.german", crate::i18n::Locale::De.name(), None, "Edit>Language"), ("app.language.russian", crate::i18n::Locale::Ru.name(), None, "Edit>Language"), ]; @@ -37,6 +38,7 @@ pub fn language_from_command(id: &str) -> Option { "app.language.traditionalChinese" => Some(crate::i18n::Locale::ZhHant), "app.language.japanese" => Some(crate::i18n::Locale::Ja), "app.language.portuguese" => Some(crate::i18n::Locale::PtBr), + "app.language.spanish" => Some(crate::i18n::Locale::Es), "app.language.german" => Some(crate::i18n::Locale::De), "app.language.russian" => Some(crate::i18n::Locale::Ru), _ => None, diff --git a/docs/localization-es.md b/docs/localization-es.md new file mode 100644 index 00000000..d8aeaa78 --- /dev/null +++ b/docs/localization-es.md @@ -0,0 +1,47 @@ +# LightCraft en español + +Elegir **Editar → Idioma → Español** o **Ajustes → General → Idioma**. El cambio se aplica al +momento y se conserva para el próximo inicio (`language: "es"` en `ui.json`). `LIGHTCRAFT_LANGUAGE` +acepta cualquier variante del español (`es`, `es-ES`, `es_MX.UTF-8`, `es-419`…). + +El catálogo en español cubre los menús, los métodos abreviados de teclado, la biblioteca, el +revelado, el recorte, las máscaras, la importación y la exportación, los ajustes, los ajustes +preestablecidos y perfiles integrados, el historial, las fechas y los mensajes de progreso. Como en +los demás idiomas, los errores técnicos de los niveles inferiores siguen en inglés, y los textos +que aún no están en el catálogo se muestran en inglés. + +Los nombres de archivo, los identificadores de comandos, las plantillas y los álbumes, ajustes +preestablecidos y metadatos con nombres propios no se traducen. La fuente de la interfaz, Inter, +incluye todos los caracteres del español; no hacen falta fuentes adicionales. + +## Terminología + +Se usan infinitivos o formas impersonales en las instrucciones («Hacer clic…», «Elegir…»), sin +tratamiento de usted. Algunos términos que conviene mantener coherentes: + +| Inglés | Español | +|---|---| +| Highlights / Shadows | Iluminaciones / Sombras | +| Dehaze | Eliminar neblina | +| Vibrance | Intensidad | +| Tint | Matiz | +| Hue (HSL) | Tono | +| Color Grading | Gradación de color | +| Preset | Ajuste preestablecido | +| Pick / Reject | Seleccionada / Rechazada | +| Upright | Vertical | +| Lens Corrections | Correcciones de lente | + +Las correcciones de hablantes nativos son bienvenidas: abrir un issue o un pull request con el +texto inglés, la traducción actual y la propuesta. + +## Mantenimiento y comprobación + +Textos fijos: `crates/ui-egui/locales/es.json`. Mensajes con valores: +`crates/ui-egui/locales/es-formats.json`. Las claves en inglés no cambian; el compilador de Rust +comprueba los marcadores al compilar. Las terminaciones de plural del inglés se ocultan con `{:.0}`. + +```sh +cargo test -p lightcraft-ui-egui i18n::tests -- --nocapture +LIGHTCRAFT_LANGUAGE=es lightcraft-cli snapshot --demo -o espanol.png --size 1600x1000 +``` diff --git a/docs/localization.md b/docs/localization.md index 9965dec8..123dcaba 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -2,9 +2,9 @@ The interface ships in the language it is written in (English) plus every language in the table in `crates/ui-egui/src/i18n.rs`. Today that is English, Simplified Chinese (`zh-hans`), Traditional -Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), German (`de`) and Russian (`ru`). This file is the reference for **adding or maintaining a language**; the per-language notes +Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), Spanish (`es`), German (`de`) and Russian (`ru`). This file is the reference for **adding or maintaining a language**; the per-language notes are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-hant.md`](localization-zh-hant.md), -[`localization-ja.md`](localization-ja.md), [`localization-pt-br.md`](localization-pt-br.md), +[`localization-ja.md`](localization-ja.md), [`localization-pt-br.md`](localization-pt-br.md), [`localization-es.md`](localization-es.md), [`localization-de.md`](localization-de.md) and [`localization-ru.md`](localization-ru.md). ## Adding a language diff --git a/docs/parity.md b/docs/parity.md index 72401646..ac7bd5ec 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -520,8 +520,8 @@ under the UI command that wraps it (a no-op error); the UI command now wins. | LR-BEHAV-EMPTY-STATES | Empty states | P1 | ✅ | `crates/ui-egui/src/panels/mod.rs` (`empty_message`) | | | LR-BEHAV-TOOLTIPS | Tooltips with shortcuts | P0 | ✅ | `crates/ui-egui/src/panels/bottombar.rs` | | | LR-BEHAV-ACCESS | Accessibility | P2 | 🟡 | `crates/ui-egui/src/widgets.rs`, `crates/ui-egui/src/panels/grid.rs` | AccessKit (VoiceOver / Narrator / AT-SPI): sliders announce control and value, buttons / icon buttons / dropdowns / section headers / sources their labels and state, grid thumbnails file name, rating, flag and label; the canvas tools (crop, masks) are pointer-only; not audited with a screen reader | -| LR-BEHAV-LOCALE | Language options | P2 | ✅ | `cmd:app.language.english`, `cmd:app.language.simplifiedChinese`, `cmd:app.language.traditionalChinese`, `cmd:app.language.japanese`, `cmd:app.language.portuguese`, `cmd:app.language.german`, `cmd:app.language.russian` | Edit > Language lists every language in the table and the active one is checked; shared with Settings > General (docs/localization.md) | -| LR-BEHAV-LOCALIZE | Localisation | P2 | 🟡 | `crates/ui-egui/src/i18n.rs`, `crates/ui-egui/locales/`, `docs/localization.md`, `docs/localization-zh-hans.md`, `docs/localization-zh-hant.md`, `docs/localization-ja.md`, `docs/localization-pt-br.md`, `docs/localization-de.md`, `docs/localization-ru.md` | One table entry per language (code, endonym, ISO 15924 script, catalog): English, Simplified Chinese, Traditional Chinese (Taiwan), Japanese, Brazilian Portuguese, German and Russian UI with a persisted language preference and craft-fonts regular/bold faces per script; menus, edit, crop/masks, settings, import/export and primary progress messages. Adding a language is a table entry plus two catalogs. Stock presets/profiles, history steps, library headings, date headings and capture times are localised (user-named items stay verbatim); the German catalog covers every existing catalog key, command/control label and rule-editor label; the other catalogs fall back to English for messages they lack. Technical errors remain English; release notes are translated in German; Traditional Chinese borrows the Simplified Chinese face (no TC face in craft-fonts yet), and the web build embeds only the Japanese face. | +| LR-BEHAV-LOCALE | Language options | P2 | ✅ | `cmd:app.language.english`, `cmd:app.language.simplifiedChinese`, `cmd:app.language.traditionalChinese`, `cmd:app.language.japanese`, `cmd:app.language.portuguese`, `cmd:app.language.spanish`, `cmd:app.language.german`, `cmd:app.language.russian` | Edit > Language lists every language in the table and the active one is checked; shared with Settings > General (docs/localization.md) | +| LR-BEHAV-LOCALIZE | Localisation | P2 | 🟡 | `crates/ui-egui/src/i18n.rs`, `crates/ui-egui/locales/`, `docs/localization.md`, `docs/localization-zh-hans.md`, `docs/localization-zh-hant.md`, `docs/localization-ja.md`, `docs/localization-pt-br.md`, `docs/localization-es.md`, `docs/localization-de.md`, `docs/localization-ru.md` | One table entry per language (code, endonym, ISO 15924 script, catalog): English, Simplified Chinese, Traditional Chinese (Taiwan), Japanese, Brazilian Portuguese, Spanish, German and Russian UI with a persisted language preference and craft-fonts regular/bold faces per script; menus, edit, crop/masks, settings, import/export and primary progress messages. Adding a language is a table entry plus two catalogs. Stock presets/profiles, history steps, library headings, date headings and capture times are localised (user-named items stay verbatim); the German catalog covers every existing catalog key, command/control label and rule-editor label; the other catalogs fall back to English for messages they lack. Technical errors remain English; release notes are translated in German; Traditional Chinese borrows the Simplified Chinese face (no TC face in craft-fonts yet), and the web build embeds only the Japanese face. | | LR-BEHAV-LEARN | Tutorials | OOS | 🚫 | | | | LR-BEHAV-WHATSNEW | What's new | P2 | ✅ | `cmd:app.whatsNew`, `docs/whats-new.md` | Help ▸ What's New: release highlights | | LR-BEHAV-AI-EA | Early-access badges | P2 | ⬜ | | | diff --git a/docs/whats-new.md b/docs/whats-new.md index d02e7f22..fc5f2f04 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -49,8 +49,8 @@ or unchecks a range of photos (issue #338). ### Languages -- The interface is available in German and Russian (Edit ▸ Language), alongside English, Chinese (Simplified and - Traditional), Japanese and Brazilian Portuguese. +- The interface is available in Spanish (issue #371), German and Russian (Edit ▸ Language), alongside English, + Chinese (Simplified and Traditional), Japanese and Brazilian Portuguese. ### Editing - Type an exact value into any slider (issue #322): click the number next to its name, type (`1.5`, `-20`, `5600`) From 305ff714745629051451c97f0fdd5f07c4acf246 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 20:20:30 +0000 Subject: [PATCH 4/5] Part of #371: Spanish for the SAM 3 install dialog (#366) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8 (cherry picked from commit 73b22c8524ef17b209ff2fffbb16c248524e2e96) --- crates/ui-egui/locales/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/locales/es.json b/crates/ui-egui/locales/es.json index 79eb04a0..a4eeffe1 100644 --- a/crates/ui-egui/locales/es.json +++ b/crates/ui-egui/locales/es.json @@ -616,6 +616,7 @@ "Horizontal": "Horizontal", "Horizontal text": "Texto horizontal", "Hours": "Horas", + "How to install the model": "Cómo instalar el modelo", "Hue": "Tono", "Hue Range": "Rango de tono", "ISO Speed": "Sensibilidad ISO", @@ -656,6 +657,7 @@ "Info overlay": "Superposición de información", "Insert at the cursor": "Insertar en el cursor", "Inspect Photo": "Inspeccionar foto", + "Install the SAM 3 Model": "Instalar el modelo SAM 3", "Instant ’70s": "Instantánea de los 70", "Interface": "Interfaz", "Interface language": "Idioma de la interfaz", @@ -1391,7 +1393,7 @@ "They move to Recently Deleted and can be restored; no file on disk is touched.": "Pasan a Eliminadas recientemente y se pueden restaurar; no se toca ningún archivo del disco.", "Thirds": "Tercios", "This Computer": "Este equipo", - "This build has no download location for the model yet: put the files in the folder above yourself (see docs/ai-masks.md).": "Esta compilación aún no tiene una ubicación de descarga para el modelo: hay que colocar los archivos manualmente en la carpeta de arriba (ver docs/ai-masks.md).", + "This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "Esta versión aún no puede descargar el modelo. Para instalarlo a mano, poner model.safetensors, vocab.json y merges.txt de facebook/sam3 (Meta) en la carpeta de arriba: Objeto y Describir funcionan en cuanto estén allí.", "This folder is not available (drive disconnected?). Smart previews are not built or used until it is back or you choose another folder.": "Esta carpeta no está disponible (¿unidad desconectada?). Las vistas previas inteligentes no se generan ni se usan hasta que vuelva a estar disponible o se elija otra carpeta.", "This photo can't be opened": "No se puede abrir esta foto", "Threshold": "Umbral", From cad024fbff42284b078068aa0f5e0570d0f74cb1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 20:47:07 +0000 Subject: [PATCH 5/5] Part of #372: painted-text layout tests from #376 The tests from @QuirkyTurtle94's #376, a duplicate fix for #372 opened while #379 was in review: they read the painted text shapes (not the registered rects) at 1x and 2x scale, in English, German and Brazilian Portuguese, at panel widths 250-520, and check that tile labels fit unelided inside their tiles, that a long mask name is elided before the eye, and that a long Describe prompt keeps Select inside the panel. They pass unchanged against main's implementation. Co-Authored-By: Elliot Graham Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8 --- crates/ui-egui/src/lib.rs | 2 + crates/ui-egui/src/tests_masking_layout.rs | 92 ++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 crates/ui-egui/src/tests_masking_layout.rs diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index a76f167f..d64cf899 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -39,6 +39,8 @@ mod tests_library_problem; #[cfg(test)] mod tests_masking; #[cfg(test)] +mod tests_masking_layout; +#[cfg(test)] mod tests_offline; #[cfg(test)] mod tests_panels; diff --git a/crates/ui-egui/src/tests_masking_layout.rs b/crates/ui-egui/src/tests_masking_layout.rs new file mode 100644 index 00000000..74313ed3 --- /dev/null +++ b/crates/ui-egui/src/tests_masking_layout.rs @@ -0,0 +1,92 @@ +//! Check painted text as well as widget rectangles: custom painters can overflow a valid widget. + +use egui::{Rect, pos2, vec2}; +use serde_json::json; + +use crate::{LightcraftApp, Services, headless::HeadlessView, i18n::Locale}; + +const LONG_NAME: &str = "The person standing beside the very long fence across the background"; + +fn text_shapes(shape: &egui::Shape, out: &mut Vec) { + match shape { + egui::Shape::Text(text) => out.push(text.clone()), + egui::Shape::Vec(shapes) => shapes.iter().for_each(|s| text_shapes(s, out)), + _ => {} + } +} + +fn panel(width: f32, scale: f32, locale: Locale, describe: bool) -> (Vec, Vec<(String, Rect)>) { + let mut app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services::default()); + app.run("mask.add", json!({"kind": "subject", "name": LONG_NAME})).unwrap(); + app.ui.describe = describe.then(|| ("new".into(), LONG_NAME.repeat(4))); + app.ui.detail_due = Some((100.0, 1)); + let photo = app.session.active().unwrap(); + let ctx = egui::Context::default(); + crate::i18n::set_language(locale); + crate::theme::install_fonts(&ctx); + crate::theme::apply(&ctx); + let mut texts = Vec::new(); + let mut widgets = Vec::new(); + for frame in 0..3 { + let raw = HeadlessView::raw_input(vec2(width, 1600.0), scale, frame as f64 / 60.0, vec![]); + let mut out = ctx.run_ui(raw, |ui| { + egui::CentralPanel::default().frame(egui::Frame::NONE).show(ui, |ui| { + crate::panels::masking::show(&mut app, ui, photo); + }); + }); + out.textures_delta.clear(); + texts.clear(); + for shape in &out.shapes { + text_shapes(&shape.shape, &mut texts); + } + widgets = crate::widgets::take_registry(&ctx); + } + if describe { + assert_eq!(app.ui.describe.as_ref().unwrap().1, LONG_NAME.repeat(4), "layout must not shorten the actual prompt"); + } + crate::i18n::set_language(Locale::En); + (texts, widgets) +} + +#[test] +fn masking_tile_labels_fit_without_eliding_at_supported_panel_widths() { + for locale in [Locale::En, Locale::De, Locale::PtBr] { + for width in [250.0, 270.0, 330.0, 520.0] { + for scale in [1.0, 2.0] { + let (texts, widgets) = panel(width, scale, locale, false); + for (kind, label) in [("background", "Background"), ("luminanceRange", "Luminance"), ("prompt", "Describe")] { + crate::i18n::set_language(locale); + let label = crate::i18n::tr(label).to_string(); + crate::i18n::set_language(Locale::En); + let tile = widgets.iter().find(|(id, _)| id == &format!("maskNew:{kind}")).unwrap().1; + let text = texts.iter().find(|t| t.galley.job.text == label && t.pos.y < tile.bottom()).unwrap(); + assert!(!text.galley.elided, "{locale:?} {width} {label} should be readable in full"); + assert!( + tile.shrink2(vec2(2.0, 0.0)).contains_rect(text.visual_bounding_rect()), + "{locale:?} {width} {scale}: {label} exceeds {tile:?}" + ); + } + } + } + } +} + +#[test] +fn masking_long_names_and_describe_controls_stay_inside_the_panel() { + for locale in [Locale::En, Locale::De, Locale::PtBr] { + for width in [250.0, 270.0, 520.0] { + let (texts, widgets) = panel(width, 2.0, locale, true); + let row = widgets.iter().find(|(id, _)| id == "mask:1").unwrap().1; + let name = texts.iter().find(|t| t.galley.job.text == LONG_NAME).unwrap(); + assert!(name.galley.elided, "long mask names should be elided"); + assert!( + Rect::from_min_max(pos2(row.left() + 32.0, row.top()), pos2(row.right() - 28.0, row.bottom())) + .contains_rect(name.visual_bounding_rect()) + ); + let field = widgets.iter().find(|(id, _)| id == "maskDescribe").unwrap().1; + let button = widgets.iter().find(|(id, _)| id == "button:maskDescribeGo").unwrap().1; + assert!(field.right() <= button.left(), "the prompt must not overlap Select"); + assert!(field.left() >= 24.0 && button.right() <= width - 22.0, "{locale:?} {width}: {field:?} {button:?}"); + } + } +}