Browse / github.com/storytold/lightcraft / patch
Issue #2589: a Lightroom import says where each catalog photo went
Issue #2589: a Lightroom import says where each catalog photo went Part of #2589 Refs #1584 Stacked on #3141; this PR's own commit is the top one, 2d06a1a5 ("a Lightroom import says where each catalog photo went"). ## Problem After a Lightroom catalog import, people can't tell where their photos went. A 45,091-photo catalog finished with "16,521 photos added" and nothing about the rest (#2589); an import of photos already in the library said "0 photos added", which reads as "nothing happened" (#1584). Photos leave the count in several ways the result never totalled: already in the library, in Recently Deleted, not a file type LightCraft reads (videos), an
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- THEtheChad (imported from pull request #3142, open)
- Base
662df66f1c08bdb1b1f1d0c1c638c4cef936d0b0- Size
- +995 −141 in 27 files
- Review
- pending
- Shared
- 2026-10-28
- Id
a6a309ea0f323d0224452b91
How it went
No other operator has reported using it yet.
Diff
raw · crates/engine/src/import/commit.rs crates/engine/src/import/job.rs crates/engine/src/import/types.rs crates/engine/src/lightroom_archive.rs crates/engine/src/lightroom_catalog.rs crates/engine/src/lightroom_catalog/tests.rs crates/engine/src/lightroom_job.rs crates/ui-egui/locales/cs-formats.json crates/ui-egui/locales/de-formats.json crates/ui-egui/locales/es-formats.json crates/ui-egui/locales/fi-formats.json crates/ui-egui/locales/fr-formats.json crates/ui-egui/locales/id-formats.json crates/ui-egui/locales/it-formats.json crates/ui-egui/locales/ja-formats.json crates/ui-egui/locales/ka-formats.json crates/ui-egui/locales/pl-formats.json crates/ui-egui/locales/pt-br-formats.json crates/ui-egui/locales/ru-formats.json crates/ui-egui/locales/sv-formats.json crates/ui-egui/locales/uk-formats.json crates/ui-egui/locales/vi-formats.json crates/ui-egui/locales/zh-hans-formats.json crates/ui-egui/locales/zh-hant-formats.json crates/ui-egui/src/lightroom_import.rs docs/lightroom-catalog-import.md docs/whats-new.md
@@ -213,7 +213,7 @@ pub fn commit_prepared(s: &mut Session, opts: &ImportOptions, now: &str, prepare } report.imported.push(id.0); }- PreparedItem::Duplicate { path, existing, reason, hash } => {+ PreparedItem::Duplicate { path, existing, reason, hash, file_meta } => { let existing = existing.or_else(|| { let h = hash?; new_hash.get(&h).copied().or_else(|| s.catalog.photos().find(|p| p.content_hash.as_deref() == Some(h.as_str())).map(|p| p.id))@@ -232,7 +232,13 @@ pub fn commit_prepared(s: &mut Session, opts: &ImportOptions, now: &str, prepare report.restored.push(id.0); } }- _ => report.duplicates.push(Duplicate { path, existing: existing.map(|i| i.0), reason, existing_deleted: trashed.is_some() }),+ _ => report.duplicates.push(Duplicate {+ path,+ existing: existing.map(|i| i.0),+ reason,+ existing_deleted: trashed.is_some(),+ file_meta,+ }), } } PreparedItem::Failed(path, e) => report.failed.push((path, e)),@@ -73,6 +73,8 @@ pub(super) enum PreparedItem { existing: Option<PhotoId>, reason: &'static str, hash: Option<String>,+ /// The metadata the file itself carries, when it was read ([`Duplicate::file_meta`]).+ file_meta: Option<Box<lightcraft_catalog::Meta>>, }, Failed(String, String), Kept(Kept),@@ -141,6 +143,7 @@ impl Prepared { existing: Some(existing), reason: if existing_path.is_some() { "path" } else { "content" }, hash: info.content_hash,+ file_meta: Some(Box::new(info.meta)), }); } else { items.push(PreparedItem::Ready(Box::new(ReadyFile { path, stored, info, sidecar, placed })));@@ -261,7 +264,7 @@ impl ImportJob { self.done.fetch_add(1, Relaxed); } Some((id, _)) => {- out.items.push(PreparedItem::Duplicate { path: f, existing: Some(id), reason: "path", hash: None });+ out.items.push(PreparedItem::Duplicate { path: f, existing: Some(id), reason: "path", hash: None, file_meta: None }); self.done.fetch_add(1, Relaxed); } None => todo.push(f),@@ -326,7 +329,13 @@ impl ImportJob { && let Some(existing) = self.by_hash.get(h) { self.done.fetch_add(1, Relaxed);- plan.slots.push(Slot::Item(PreparedItem::Duplicate { path, existing: *existing, reason: "content", hash: Some(h.clone()) }));+ plan.slots.push(Slot::Item(PreparedItem::Duplicate {+ path,+ existing: *existing,+ reason: "content",+ hash: Some(h.clone()),+ file_meta: Some(Box::new(info.meta)),+ })); continue; } // the XMP sidecar (or a raw's embedded XMP), read before copying: its capture time files@@ -146,6 +146,12 @@ pub struct Duplicate { /// That photo is in Recently Deleted (see [`OnDeleted`]). #[serde(rename = "existingDeleted", skip_serializing_if = "std::ops::Not::not")] pub existing_deleted: bool,+ /// The metadata the file itself carries (EXIF, IPTC, embedded XMP), as a file import reads it;+ /// `None` when the file wasn't read (a path match). A Lightroom import makes a record of its+ /// own for a duplicate by content and starts it from these, not from the other photo's record,+ /// whose metadata may have been edited (#2589).+ #[serde(skip)]+ pub file_meta: Option<Box<lightcraft_catalog::Meta>>, } #[derive(Clone, Debug, Default, Serialize)]@@ -525,6 +525,8 @@ mod tests { collection_content: Vec::new(), warnings: Vec::new(), skipped: Vec::new(),+ images: 0,+ not_supported: Default::default(), } } @@ -549,6 +551,8 @@ mod tests { collection_content: vec![BTreeMap::from([("rule".into(), serde_json::json!("rating > 3"))])], warnings: vec!["one warning".into()], skipped: Vec::new(),+ images: 1,+ not_supported: Default::default(), } } @@ -185,6 +185,15 @@ pub struct CatalogImport { /// so each front end can say so in its own language. #[serde(default)] pub skipped: Vec<SkippedImage>,+ /// How many images the catalog has: its `Adobe_images` rows, masters and virtual copies (as+ /// Lightroom's All Photographs counts them). The import report accounts for each one.+ #[serde(default)]+ pub images: usize,+ /// Catalog images left out because their original isn't a file type LightCraft reads (videos,+ /// for one), counted by the original's extension in lower case ("" for none); a virtual copy+ /// counts under its master's. Their ids are in [`CatalogImport::warnings`].+ #[serde(default)]+ pub not_supported: BTreeMap<String, usize>, } /// A catalog image that was not imported, and why.@@ -538,8 +547,7 @@ pub fn read_with_progress(path: &Path, cancel: &AtomicBool, total: &AtomicUsize, None => absolute.to_string(), }; if let Err(crate::doc_path::NotFollowed::Refused(why)) = crate::doc_path::resolve(&resolved, Some(path)) {- warnings- .push(format!("root folder {}: {resolved} is not opened: {why}; its photos are listed as missing and not added", text(root, "name")));+ warnings.push(format!("root folder {}: {resolved} is not opened: {why}; its photos are not imported", text(root, "name"))); refused_roots.insert(id); } root_paths.insert(id, resolved);@@ -554,12 +562,16 @@ pub fn read_with_progress(path: &Path, cancel: &AtomicBool, total: &AtomicUsize, collection_content: optional_table("AgLibraryCollectionContent", &mut warnings)?, warnings, skipped: Vec::new(),+ images: 0,+ not_supported: BTreeMap::new(), }; // images whose file, folder or root record is gone, or whose path is empty (Lightroom leaves // these behind and ignores them); they and their virtual copies are skipped and reported // instead of failing the import let mut orphans = HashSet::new();- for image in table("Adobe_images", true)? {+ let images = table("Adobe_images", true)?;+ out.images = images.len();+ for image in images { ensure_read_active(cancel)?; let id = number(&image, "id_local"); let record = files.get(&number(&image, "rootFile")).ok_or(SkipReason::MissingFile).and_then(|file| {@@ -588,7 +600,7 @@ pub fn read_with_progress(path: &Path, cancel: &AtomicBool, total: &AtomicUsize, if let Original::Refused(why) = &original && !refused_roots.contains(&root_id) {- out.warnings.push(format!("image {id}: {photo_path} is not opened: {why}; listed as missing and not added"));+ out.warnings.push(format!("image {id}: {photo_path} is not opened: {why}; it and its virtual copies are not imported")); } let packet = match xmp(metadata.get(&id).and_then(|r| r.get("xmp"))) { Ok(s) => s,@@ -641,7 +653,7 @@ fn drop_copies_of(data: &mut CatalogImport, masters: &HashSet<i64>) { } /// Drop the catalog's images whose original is not a file type LightCraft reads, saying so in the-/// import's warnings.+/// import's warnings and counting them in [`CatalogImport::not_supported`]. /// /// The catalog decides these paths, and a record it leaves behind is one click from the system /// opener (Photo ▸ Edit In ▸ Open Original in Other Editor). A record for `payload.exe`,@@ -653,34 +665,38 @@ fn drop_copies_of(data: &mut CatalogImport, masters: &HashSet<i64>) { /// can keep one; the second pass finds nothing left to say. pub(crate) fn refuse_non_media(data: &mut CatalogImport) { // masters first: a virtual copy may come before its master in the table- let refused: HashSet<i64> = data+ // each refused master's extension, which its virtual copies count under+ let refused: HashMap<i64, String> = data .photos .iter() .filter(|p| number(&p.image, "masterImage") == 0 && !crate::open_path::is_media(Path::new(&p.path)))- .map(|p| p.source_id)+ .map(|p| (p.source_id, Path::new(&p.path).extension().map(|e| e.to_string_lossy().to_lowercase()).unwrap_or_default())) .collect(); if refused.is_empty() { return; } let mut warnings = Vec::new();+ let mut counted = Vec::new(); data.photos.retain(|p| { let master = number(&p.image, "masterImage"); // `masterImage` is 0 for a master, so a refused id of 0 (a malformed catalog) must not // take every master of the catalog with it if master != 0 {- if !refused.contains(&master) {- return true;- }+ let Some(extension) = refused.get(&master) else { return true }; warnings.push(format!("image {}: not imported; its original was not imported", p.source_id));+ counted.push(extension.clone()); return false; }- if !refused.contains(&p.source_id) {- return true;- }+ let Some(extension) = refused.get(&p.source_id) else { return true }; warnings.push(format!("image {}: not imported; {} isn't one of the file types LightCraft reads", p.source_id, p.path));+ counted.push(extension.clone()); false }); data.warnings.extend(warnings);+ for extension in counted {+ let count = data.not_supported.entry(extension).or_default();+ *count = count.saturating_add(1);+ } } fn ensure_read_active(cancel: &AtomicBool) -> Result<(), String> {@@ -860,13 +876,22 @@ pub(crate) struct ApplyPrepared<'a> { /// Turn the read catalog into library records in one undo step and return the import report /// (added, missing, failed, warnings and skipped images).+///+/// The report accounts for every image of the catalog (`inCatalog`, masters and virtual copies):+/// each one is `added` (a new record, or a record this import's file import made or a browsed+/// Local photo that joins the library), `alreadyInLibrary` (an existing record, updated in place),+/// `inRecentlyDeleted` (an existing record in Recently Deleted, left there), `notSupported` (not a+/// file type LightCraft reads, by type in `notSupportedTypes`), `pathRefused` (an original the+/// import doesn't look at, see [`Original::Refused`]: no record) or `incomplete` (`skipped`).+/// Should they not add up, a warning says by how much. pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, context: ApplyPrepared<'_>) -> crate::Result<AppliedImport> { let ApplyPrepared { update_existing, files, index, archive_path, missing_sources, undo0 } = context; // nothing but a file type LightCraft reads becomes a record, whichever route got here (#2135) refuse_non_media(&mut data); validate(&data).map_err(error)?; let mut warnings = data.warnings.clone();- let duplicates: HashMap<_, _> = files.duplicates.iter().filter_map(|d| d.existing.map(|id| (key(&d.path), PhotoId(id)))).collect();+ let duplicates: HashMap<_, _> =+ files.duplicates.iter().filter_map(|d| d.existing.map(|id| (key(&d.path), (PhotoId(id), d.file_meta.as_deref())))).collect(); let mut by_path: HashMap<_, _> = s.catalog.photos().filter(|p| p.copy_of.is_none()).filter_map(|p| source_path(p).map(|path| (key(path), p.id))).collect(); let mut ids = HashMap::new();@@ -876,9 +901,16 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co // Normal import counts probed originals; catalog import also adds offline records, // distinct originals with identical bytes, and virtual copies. let mut imported = files.imported.len();+ // The records the file import just made: the catalog photo that finds one was added, not+ // already in the library.+ let mut fresh: HashSet<u64> = files.imported.iter().copied().collect(); let mut promoted = HashSet::new(); let mut unmapped = BTreeMap::new(); let mut preserved = 0;+ // Where each catalog photo went (the report's breakdown, see above). A library photo in Recently+ // Deleted that a catalog photo matched stays there (only an import of the files themselves+ // brings a photo back, see `OnDeleted`), and the report says so.+ let (mut added, mut already, mut in_trash, mut path_refused) = (0usize, 0usize, 0usize, 0usize); // Masters precede their virtual copies. let mut photos: Vec<_> = data.photos.iter().collect(); photos.sort_by_key(|p| number(&p.image, "masterImage") != 0);@@ -887,14 +919,16 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co let mut planned: HashMap<PhotoId, Photo> = HashMap::new(); for src in photos { let master = number(&src.image, "masterImage");- if master == 0 && missing_sources.contains(&key(&src.path)) {- missing.push(src.path.clone());- } // A refused path must not become a library record the app would look at later; its- // virtual copies go with it.+ // virtual copies go with it. Having no record, it isn't missing (Find Missing Photos has+ // nothing to relink): it is counted on its own. if refused.contains(&src.source_id) || refused.contains(&master) {+ path_refused += 1; continue; }+ if master == 0 && missing_sources.contains(&key(&src.path)) {+ missing.push(src.path.clone());+ } let indexed = index .photos .get(&identity(&data, src))@@ -902,7 +936,9 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co .map(PhotoId) .filter(|id| s.catalog.photo(*id).is_some_and(|p| source_path(p).is_some_and(|path| key(path) == key(&src.path)))); let existing_id = indexed.or_else(|| if master == 0 { by_path.get(&key(&src.path)).copied() } else { None });+ let mut trashed = false; let mut p = if let Some(id) = existing_id.and_then(|id| s.catalog.photo(id)) {+ trashed = id.deleted; (**id).clone() } else if master != 0 { let master_id = ids.get(&master).copied().ok_or_else(|| error(format!("virtual copy {}: missing master", src.source_id)))?;@@ -914,13 +950,53 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co p.id = s.catalog.alloc_photo_id(); p.copy_of = Some(master_id); p.copy_name = Some(text(&src.image, "copyName").to_string());+ // a new copy of a master in Recently Deleted is imported, not trashed with it+ p.deleted = false; p- } else if let Some(duplicate) = duplicates.get(&key(&src.path)).and_then(|id| s.catalog.photo(*id)) {+ } else if let Some((duplicate, file_meta)) =+ duplicates.get(&key(&src.path)).and_then(|(id, meta)| s.catalog.photo(*id).cloned().map(|p| (p, *meta)))+ { // Catalogs can contain distinct originals with identical bytes and independent edits.- let mut p = (**duplicate).clone();- p.id = s.catalog.alloc_photo_id();- p.source = Source::File { path: src.path.clone() };- p.file_name = Path::new(&src.path).file_name().map(|n| n.to_string_lossy().into()).unwrap_or_else(|| src.path.clone());+ // The new record takes only what the bytes determine from the photo with the same+ // bytes, never that photo's own state: not its Recently Deleted (#2589), virtual copy,+ // edits, history, versions, ratings or the metadata a person writes. Its metadata is+ // what its own file carries, as a file import reads it (copyright, creator…).+ let name = Path::new(&src.path).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_else(|| src.path.clone());+ let mut p = Photo::new(+ s.catalog.alloc_photo_id(),+ Source::File { path: src.path.clone() },+ &name,+ &duplicate.format,+ duplicate.width,+ duplicate.height,+ &now,+ );+ p.kind = duplicate.kind;+ p.file_size = duplicate.file_size;+ p.captured = duplicate.captured.clone();+ p.duration = duplicate.duration;+ p.as_shot_wb = duplicate.as_shot_wb;+ p.content_hash = duplicate.content_hash.clone();+ p.embedded_lens = duplicate.embedded_lens;+ p.preview_only = duplicate.preview_only.clone();+ p.preview_checked = duplicate.preview_checked.clone();+ if let Some(meta) = file_meta {+ p.meta = meta.clone();+ } else {+ // (the file wasn't read: only the exposure description, which no command edits;+ // not GPS, which placing a photo on the map does)+ let m = &duplicate.meta;+ p.meta.camera = m.camera.clone();+ p.meta.lens = m.lens.clone();+ p.meta.focal_mm = m.focal_mm;+ p.meta.aperture = m.aperture;+ p.meta.shutter = m.shutter.clone();+ p.meta.iso = m.iso;+ p.meta.focus = m.focus.clone();+ }+ // the settings a file import gives it (as-shot white balance, embedded lens+ // corrections, the default preset); the catalog's settings apply on top+ crate::import::apply_import_defaults(s, &mut p); p } else { let id = s.catalog.alloc_photo_id();@@ -1004,10 +1080,18 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co planned.insert(photo_id, p.clone()); } if s.catalog.photo(p.id).is_some() {- if was_local && promoted.insert(p.id) {+ let promote = was_local && promoted.insert(p.id);+ if promote { imported += 1; ops.push(Op::SetLocal { id: p.id, local: false }); }+ if trashed {+ in_trash += 1;+ } else if promote || fresh.remove(&p.id.0) {+ added += 1;+ } else {+ already += 1;+ } ops.extend([ Op::SetRating { id: p.id, rating: p.rating }, Op::SetFlag { id: p.id, flag: p.flag },@@ -1018,6 +1102,7 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co ops.push(Op::SetDevelop { id: p.id, settings: p.develop.clone(), label: "Lightroom catalog".into(), edited: p.edited.clone() }); } else { imported += 1;+ added += 1; ops.push(Op::AddPhoto { photo: Box::new(p) }); } by_path.entry(key(&src.path)).or_insert(photo_id);@@ -1092,8 +1177,19 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co s.commit("Import Lightroom Catalog", Op::Batch { ops })?; } s.merge_undo(s.undo.len().saturating_sub(undo0), "Import Lightroom Catalog");+ let not_supported = data.not_supported.values().fold(0usize, |sum, n| sum.saturating_add(*n));+ let accounted = [added, already, in_trash, not_supported, path_refused, data.skipped.len()].into_iter().fold(0usize, usize::saturating_add);+ if accounted != data.images {+ warnings.push(format!(+ "the catalog has {} images, but this report accounts for {accounted} (a difference of {})",+ data.images,+ data.images.abs_diff(accounted)+ ));+ } Ok(AppliedImport {- report: json!({"source":data.source,"photos":ids.len(),"imported":imported,"library":s.library.as_ref().filter(|l| l.on_disk).map(|l| l.dir.to_string_lossy()),"collections":albums.len(),"preservedExistingEdits":preserved,"missing":missing,"failed":files.failed,"warnings":warnings,"skipped":data.skipped,"unmapped":unmapped,"archive":archive_path,"mapping":ids}),+ report: json!({"source":data.source,"photos":ids.len(),"imported":imported,"library":s.library.as_ref().filter(|l| l.on_disk).map(|l| l.dir.to_string_lossy()),"collections":albums.len(),"preservedExistingEdits":preserved,+ "inCatalog":data.images,"added":added,"alreadyInLibrary":already,"inRecentlyDeleted":in_trash,"notSupported":not_supported,"notSupportedTypes":data.not_supported,"pathRefused":path_refused,"incomplete":data.skipped.len(),+ "missing":missing,"failed":files.failed,"warnings":warnings,"skipped":data.skipped,"unmapped":unmapped,"archive":archive_path,"mapping":ids}), index: index.clone(), }) }@@ -36,6 +36,8 @@ fn sample() -> CatalogImport { collection_content: Vec::new(), warnings: Vec::new(), skipped: Vec::new(),+ images: 2,+ not_supported: BTreeMap::new(), } } /// A crafted `.lrcat` that names a program as a photo's original gets no record for it, so@@ -157,6 +159,319 @@ fn imports_copies_collections_ratings_and_mapped_edits_in_one_undo_step() { assert!(s.catalog.albums().next().is_none()); assert!(data.photos[1].settings.contains("CameraProfile")); // unmapped source stays intact }+/// A library photo at `path`, already committed.+fn library_photo(s: &mut crate::Session, path: &str, edit: impl FnOnce(&mut Photo)) -> PhotoId {+ let id = s.catalog.alloc_photo_id();+ let mut p = Photo::new(id, Source::File { path: path.into() }, "a.ARW", "ARW", 20, 10, "before");+ p.content_hash = Some("same-bytes".into());+ edit(&mut p);+ s.commit("Add", Op::AddPhoto { photo: Box::new(p) }).unwrap();+ id+}+/// A catalog photo whose file has the same bytes as a library photo in Recently Deleted (or a+/// virtual copy) is a new record of its own: it was counted as imported but landed in Recently+/// Deleted (or became a "copy" of nothing it was copied from), so a large import into a library+/// with a trash went missing photo by photo (#2589).+#[test]+fn a_content_duplicate_of_a_trashed_photo_or_virtual_copy_is_a_new_live_photo() {+ let trashed: fn(&mut Photo) = |p| p.deleted = true;+ let virtual_copy: fn(&mut Photo) = |p| {+ p.copy_of = Some(PhotoId(999));+ p.copy_name = Some("Copy 1".into());+ };+ for (case, edit) in [("trashed", trashed), ("virtual copy", virtual_copy)] {+ let mut s = crate::Session::new();+ let other = library_photo(&mut s, "/elsewhere/a.ARW", |p| {+ edit(p);+ // the other photo's own state, which the new record must not inherit+ let mut d = (*p.develop).clone();+ d.light.shadows = 77.0;+ p.develop = std::sync::Arc::new(d);+ p.edited = Some("earlier".into());+ p.history.push(lightcraft_catalog::HistoryStep { label: "Other photo's edit".into(), settings: p.develop.clone() });+ p.versions.push(lightcraft_catalog::Version {+ name: "Other photo's version".into(),+ created: "before".into(),+ settings: p.develop.clone(),+ auto: false,+ });+ p.meta.title = "Other photo's title".into();+ p.meta.keywords = vec!["other-photo".into()];+ p.local_baseline = Some(7);+ // what the bytes determine is shared+ p.meta.camera = "Same Camera".into();+ p.file_size = 4096;+ });+ let mut data = sample();+ data.photos.retain(|p| number(&p.image, "masterImage") == 0);+ let files = crate::import::ImportReport {+ duplicates: vec![crate::import::Duplicate {+ path: "/missing/test.ARW".into(),+ existing: Some(other.0),+ reason: "content",+ existing_deleted: case == "trashed",+ // what the file itself carries+ file_meta: Some(Box::new(lightcraft_catalog::Meta { camera: "Same Camera".into(), ..Default::default() })),+ }],+ ..Default::default()+ };+ let undo0 = s.undo.len();+ let r = apply_prepared(+ &mut s,+ data,+ ApplyPrepared {+ update_existing: false,+ files,+ index: &mut ImportIndex::default(),+ archive_path: None,+ missing_sources: &HashSet::new(),+ undo0,+ },+ )+ .unwrap()+ .report;+ assert_eq!(r["imported"], 1, "{case}: {r}");+ assert_eq!(r["inRecentlyDeleted"], 0, "{case}: {r}");+ let new = s.catalog.photo(PhotoId(r["mapping"]["1"].as_u64().unwrap())).unwrap();+ assert_ne!(new.id, other, "{case}");+ assert!(!new.deleted, "{case}: the new record must not be in Recently Deleted");+ assert_eq!((new.copy_of, new.copy_name.as_deref()), (None, None), "{case}: the new record is no virtual copy");+ assert_ne!(new.imported, "before", "{case}: imported now, not when the other photo was");+ assert_eq!(new.rating, 5, "{case}: the catalog's own metadata applies");+ assert_ne!(new.develop.light.shadows, 77.0, "{case}: no edits from the other photo");+ assert!(new.history.iter().all(|h| h.label != "Other photo's edit"), "{case}: no history from the other photo");+ assert!(new.versions.is_empty(), "{case}: no versions from the other photo");+ assert_ne!(new.meta.title, "Other photo's title", "{case}");+ assert!(!new.meta.keywords.iter().any(|k| k == "other-photo"), "{case}");+ assert_eq!(new.local_baseline, None, "{case}");+ assert_eq!((new.meta.camera.as_str(), new.file_size), ("Same Camera", 4096), "{case}: what the bytes determine is kept");+ assert_eq!(new.content_hash.as_deref(), Some("same-bytes"), "{case}");+ assert_eq!(s.catalog.photo(other).unwrap().deleted, case == "trashed", "{case}: the other photo is left as it was");+ }+}+/// A content duplicate starts from the settings a file import gives a raw (its as-shot white+/// balance and embedded lens corrections), as its file would on its own, before the catalog's+/// settings go on top; not from the generic defaults (#2589).+#[test]+fn a_content_duplicate_raw_starts_from_its_camera_defaults() {+ let mut s = crate::Session::new();+ let other = library_photo(&mut s, "/elsewhere/a.DNG", |p| {+ p.deleted = true;+ p.format = "DNG".into();+ p.kind = lightcraft_catalog::MediaKind::Raw;+ p.as_shot_wb = Some((5200.0, 4.0));+ p.embedded_lens = Some(lightcraft_develop::EmbeddedLens::default());+ });+ let mut data = sample();+ data.photos.retain(|p| number(&p.image, "masterImage") == 0);+ // a catalog row with no develop settings of its own+ data.photos[0].settings.clear();+ let files = crate::import::ImportReport {+ duplicates: vec![crate::import::Duplicate {+ path: "/missing/test.ARW".into(),+ existing: Some(other.0),+ reason: "content",+ existing_deleted: true,+ file_meta: None,+ }],+ ..Default::default()+ };+ let undo0 = s.undo.len();+ let r = apply_prepared(+ &mut s,+ data,+ ApplyPrepared {+ update_existing: false,+ files,+ index: &mut ImportIndex::default(),+ archive_path: None,+ missing_sources: &HashSet::new(),+ undo0,+ },+ )+ .unwrap()+ .report;+ let new = s.catalog.photo(PhotoId(r["mapping"]["1"].as_u64().unwrap())).unwrap();+ assert_eq!(*new.develop, new.camera_defaults(), "{r}");+ assert_eq!((new.develop.wb.temp, new.develop.wb.tint), (5200.0, 4.0), "the file's as-shot white balance");+ assert!(new.develop.optics.lens_profile, "the file's embedded lens corrections are on");+}+/// A content duplicate keeps the metadata its file carries (copyright, rights, creator), as a file+/// import of it would, when the catalog's XMP doesn't restate it; it reads the file's, not the+/// other photo's, which a person may have edited (#2589).+#[test]+fn a_content_duplicate_keeps_the_rights_metadata_in_its_file() {+ let dir = std::env::temp_dir().join(format!("lightcraft-lrcat-duplicate-rights-{}", std::process::id()));+ let _ = std::fs::remove_dir_all(&dir);+ std::fs::create_dir_all(dir.join("Photos")).unwrap();+ let source = dir.join("source.lrcat");+ std::fs::write(dir.join("Photos/test.ARW"), b"same bytes").unwrap();+ std::fs::write(&source, native_catalog(&photo_tables((dir.to_str().unwrap(), "")))).unwrap();+ let mut s = crate::Session::new();+ let other = library_photo(&mut s, "/elsewhere/a.ARW", |p| {+ p.deleted = true;+ // edited on the other record after its import+ p.meta.copyright = "Other photo's notice".into();+ p.meta.creator = "Other photo's creator".into();+ p.meta.gps = Some((1.0, 2.0));+ });+ let file_meta = lightcraft_catalog::Meta {+ camera: "Same Camera".into(),+ copyright: "© Jo Example".into(),+ copyright_status: lightcraft_catalog::CopyrightStatus::Copyrighted,+ usage_terms: "No reuse".into(),+ creator: "Jo Example".into(),+ ..Default::default()+ };+ let probed = file_meta.clone();+ s.media.file_probe = Some(Arc::new(move |_| {+ Ok(crate::media::ProbeInfo {+ format: "ARW".into(),+ width: 20,+ height: 10,+ content_hash: Some("same-bytes".into()),+ meta: probed.clone(),+ ..Default::default()+ })+ }));+ let mut job = crate::lightroom_job::LightroomJob::new(&mut s, source, false).unwrap();+ let prepared = job.prepare(&AtomicBool::new(false)).unwrap();+ let r = crate::lightroom_job::commit_prepared(&mut s, prepared).unwrap().report;+ let new = s.catalog.photos().find(|p| p.id != other).unwrap_or_else(|| panic!("no new record: {r}"));+ assert_eq!(new.content_hash.as_deref(), Some("same-bytes"), "{r}");+ assert_eq!(new.rating, 5, "the catalog's own metadata applies: {r}");+ let m = &new.meta;+ assert_eq!(+ (m.copyright.as_str(), m.copyright_status, m.usage_terms.as_str(), m.creator.as_str(), m.camera.as_str()),+ ("© Jo Example", file_meta.copyright_status, "No reuse", "Jo Example", "Same Camera"),+ "the file's own metadata: {r}"+ );+ assert_eq!(m.gps, None, "nothing from the other photo's record");+ std::fs::remove_dir_all(dir).unwrap();+}+/// A catalog photo matched (by path) to a library photo in Recently Deleted leaves it there, as+/// the import was set up to do (a Lightroom import doesn't restore, see `OnDeleted`), and the report+/// counts it so the photos don't go missing unexplained (#2589). A new virtual copy of it is+/// imported, not trashed with its master.+#[test]+fn a_catalog_photo_matching_a_trashed_record_stays_there_and_is_counted() {+ let mut s = crate::Session::new();+ let trashed = library_photo(&mut s, "/missing/test.ARW", |p| p.deleted = true);+ let r = apply(&mut s, sample(), false).unwrap();+ assert_eq!(r["mapping"]["1"], trashed.0, "{r}");+ assert!(s.catalog.photo(trashed).unwrap().deleted, "the photo stays in Recently Deleted");+ assert_eq!(r["inRecentlyDeleted"], 1, "{r}");+ let copy = s.catalog.photos().find(|p| p.copy_of == Some(trashed)).unwrap();+ assert!(!copy.deleted, "the new virtual copy is imported, not trashed");+ assert_eq!(r["imported"], 1, "only the copy was added: {r}");+}+/// Importing the catalog again after its master and virtual copy were both moved to Recently+/// Deleted matches each through the import index: each is counted once, and neither is restored.+#[test]+fn a_reimport_counts_each_trashed_match_once() {+ let mut s = crate::Session::new();+ // one import index across both imports, as a library on disk keeps in Interop/+ let mut index = ImportIndex::default();+ let mut import = |s: &mut crate::Session| {+ let undo0 = s.undo.len();+ apply_prepared(+ s,+ sample(),+ ApplyPrepared {+ update_existing: false,+ files: Default::default(),+ index: &mut index,+ archive_path: None,+ missing_sources: &HashSet::new(),+ undo0,+ },+ )+ .unwrap()+ .report+ };+ let first = import(&mut s);+ let ids: Vec<PhotoId> = ["1", "2"].iter().map(|k| PhotoId(first["mapping"][k].as_u64().unwrap())).collect();+ for id in &ids {+ s.commit("Delete", Op::SetDeleted { id: *id, deleted: true }).unwrap();+ }+ let r = import(&mut s);+ assert_eq!(r["mapping"], first["mapping"], "{r}");+ assert_eq!(r["inRecentlyDeleted"], 2, "{r}");+ assert_eq!(r["imported"], 0, "{r}");+ assert!(ids.iter().all(|id| s.catalog.photo(*id).unwrap().deleted));+}++/// The report says where each of the catalog's images went, one count per outcome, and the counts+/// add up to the catalog's images (virtual copies included), so a large import that adds fewer+/// photos than the catalog has explains the rest (#2589, #1584).+#[test]+fn the_report_accounts_for_every_catalog_image_once() {+ let mut s = crate::Session::new();+ library_photo(&mut s, "/library/kept.ARW", |_| {});+ library_photo(&mut s, "/library/trashed.ARW", |p| p.deleted = true);+ let mut data = sample();+ let master = data.photos[1].clone();+ let photo = |id: i64, path: &str, master_image: i64| {+ let mut p = master.clone();+ p.source_id = id;+ p.uuid = format!("image-{id}");+ p.path = path.into();+ p.image = row(json!({"id_local": id, "masterImage": master_image, "fileFormat": "ARW"}));+ p+ };+ let mut far = photo(7, "//server/share/far.ARW", 0);+ far.original = Original::Refused("it is on another computer".into());+ data.photos.extend([+ photo(3, "/library/kept.ARW", 0),+ photo(4, "/library/trashed.ARW", 0),+ // a video and its virtual copy+ photo(5, "/Videos/clip.MP4", 0),+ photo(6, "/Videos/clip.MP4", 5),+ far,+ ]);+ data.skipped.push(SkippedImage { image: 8, reason: SkipReason::MissingFile });+ data.images = 8;+ // one index across both imports, as a library on disk keeps it+ let mut index = ImportIndex::default();+ let missing_sources: HashSet<_> = data.photos.iter().filter(|p| !p.original_is_file()).map(|p| key(&p.path)).collect();+ let mut import = |s: &mut crate::Session| {+ let undo0 = s.undo.len();+ let context = ApplyPrepared {+ update_existing: false,+ files: Default::default(),+ index: &mut index,+ archive_path: None,+ missing_sources: &missing_sources,+ undo0,+ };+ apply_prepared(s, data.clone(), context).unwrap().report+ };+ let r = import(&mut s);+ let counts = ["added", "alreadyInLibrary", "inRecentlyDeleted", "notSupported", "pathRefused", "incomplete"].map(|k| r[k].as_u64().unwrap());+ assert_eq!(counts, [2, 1, 1, 2, 1, 1], "the offline master and its copy are added: {r}");+ assert_eq!(r["inCatalog"], 8, "{r}");+ assert_eq!(counts.iter().sum::<u64>(), 8, "{r}");+ assert_eq!(r["notSupportedTypes"], json!({"mp4": 2}), "{r}");+ assert_eq!(r["imported"], r["added"], "{r}");+ let missing = r["missing"].to_string();+ assert!(!missing.contains("far.ARW"), "a refused path has no record to relink, so it isn't missing: {missing}");+ assert!(!r["warnings"].to_string().contains("accounts for"), "{r}");+ // Imported again, the photos it added are already in the library.+ let again = import(&mut s);+ let counts = ["added", "alreadyInLibrary", "inRecentlyDeleted"].map(|k| again[k].as_u64().unwrap());+ assert_eq!(counts, [0, 3, 1], "{again}");+}++/// Counts that don't add up to the catalog's images say so instead of passing silently.+#[test]+fn a_report_that_does_not_add_up_says_by_how_much() {+ let mut data = sample();+ data.images = 5;+ let r = apply(&mut crate::Session::new(), data, false).unwrap();+ let warnings = r["warnings"].to_string();+ assert!(warnings.contains("the catalog has 5 images, but this report accounts for 2 (a difference of 3)"), "{warnings}");+}+ #[test] fn preserves_existing_edits_and_rejects_invalid_structure_before_mutation() { let mut s = crate::Session::new();@@ -705,6 +1020,9 @@ fn probed_original_is_counted_once_and_reimport_adds_nothing() { let prepared = job.prepare(&AtomicBool::new(false)).unwrap(); let completion = crate::lightroom_job::commit_prepared(&mut s, prepared).unwrap(); assert_eq!(completion.report["imported"], expected);+ // the record the file import made is added; the second time it is already in the library+ let outcome = (completion.report["added"].as_u64(), completion.report["alreadyInLibrary"].as_u64());+ assert_eq!(outcome, (Some(expected), Some(1 - expected)), "{}", completion.report); assert_eq!(completion.report["missing"], json!([])); assert_eq!(completion.report["failed"], json!([])); assert_eq!(s.catalog.len(), 1);@@ -748,7 +1066,13 @@ fn originals_on_other_computers_are_never_looked_at_or_added() { std::fs::write(&source, native_catalog(&photo_tables((root, "")))).unwrap(); let catalog = read(&source).unwrap(); assert!(matches!(catalog.photos[0].original, Original::Refused(_)), "{root}: {:?}", catalog.photos[0]);- assert!(catalog.warnings.iter().any(|w| w.contains("not opened")), "{root}: {:?}", catalog.warnings);+ // counted as `pathRefused`, not as missing (#2589)+ assert!(+ catalog.warnings.iter().any(|w| w.contains("not opened") && w.ends_with("its photos are not imported")),+ "{root}: {:?}",+ catalog.warnings+ );+ assert!(!catalog.warnings.iter().any(|w| w.contains("missing")), "{root}: {:?}", catalog.warnings); let mut s = crate::Session::new(); let probes = Arc::new(AtomicUsize::new(0)); let calls = probes.clone();@@ -758,14 +1082,19 @@ fn originals_on_other_computers_are_never_looked_at_or_added() { })); let report = import(&mut s, &source, false).unwrap(); assert_eq!(probes.load(Ordering::Relaxed), 0, "{root}");- assert_eq!(report["missing"].as_array().map(Vec::len), Some(1), "{root}: {report}");+ // no record, so nothing for Find Missing Photos: counted on its own+ assert_eq!(report["missing"], json!([]), "{root}: {report}");+ assert_eq!((report["pathRefused"].as_u64(), report["inCatalog"].as_u64()), (Some(1), Some(1)), "{root}: {report}"); assert!(s.catalog.is_empty(), "{root}"); } // An absolute folder path would replace the root. let mut tables = photo_tables((dir.to_str().unwrap(), "")); tables[1].2[2] = SqlValue::Text(format!("{}/Photos/", dir.display())); std::fs::write(&source, native_catalog(&tables)).unwrap();- assert!(matches!(read(&source).unwrap().photos[0].original, Original::Refused(_)));+ let catalog = read(&source).unwrap();+ assert!(matches!(catalog.photos[0].original, Original::Refused(_)));+ assert!(catalog.warnings.iter().any(|w| w.ends_with("it and its virtual copies are not imported")), "{:?}", catalog.warnings);+ assert!(!catalog.warnings.iter().any(|w| w.contains("missing")), "{:?}", catalog.warnings); // A root beside the catalog is followed. std::fs::write(&source, native_catalog(&photo_tables((dir.to_str().unwrap(), "")))).unwrap(); let catalog = read(&source).unwrap();@@ -356,6 +356,8 @@ mod tests { collection_content: Vec::new(), warnings: Vec::new(), skipped: Vec::new(),+ images: 0,+ not_supported: Default::default(), } } @@ -401,6 +403,8 @@ mod tests { collection_content: Vec::new(), warnings: Vec::new(), skipped: Vec::new(),+ images: 1,+ not_supported: Default::default(), }, normal: Prepared::default(), index: ImportIndex::default(),@@ -94,9 +94,6 @@ "Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Geometrie Lightroom pro {path} se nezachovala; uložená transformace narovnání perspektivy není podporována.", "Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Geometrie Lightroom pro {path} se nezachovala; uložená transformace se ignoruje, protože narovnání perspektivy je vypnuté.", "Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Geometrie Lightroom pro {path} se nezachovala; {mode} se přepočítá.",- "Lightroom import complete: {imported} photos added": "Import z Lightroom byl dokončen: přidaných fotografií: {imported}",- "Lightroom import complete: {imported} photos added, {failed} failed": "Import z Lightroom byl dokončen: přidaných fotografií: {imported}, neúspěšných: {failed}",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Import z Lightroom byl dokončen: přidaných fotografií: {imported}; varování indexu: {warning}", "Lightroom import failed: {e}{warning}": "Import z Lightroom selhal: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Prohlížení katalogu Lightroom selhalo: {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT NEBO Apache-2.0. Písma: {} (OFL). Ikony: původní tvorba.",@@ -267,8 +264,23 @@ "Update “{name}” with Current Settings": "Aktualizovat „{name}“ podle aktuálního nastavení", "{n} preset{}": "Přednastavení: {n}{:.0}", "{count} more warnings.": "Další varování: {count}.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Fotografie katalogu v nedávno odstraněných této knihovny: {in_trash}. Zůstávají tam; chcete-li je vidět, obnovte je z nedávno odstraněných.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Import z Lightroom byl dokončen. Fotografií v katalogu: {in_catalog} – {parts}",+ "{added} added": "přidáno: {added}",+ "{already} already in the library": "již v knihovně: {already}",+ "{in_trash} in Recently Deleted": "v nedávno odstraněných: {in_trash}",+ "{not_supported} not supported": "nepodporováno: {not_supported}",+ "{path_refused} on refused paths": "s odmítnutou cestou: {path_refused}",+ "{incomplete} incomplete": "neúplné: {incomplete}",+ "{failed} failed": "neúspěšné: {failed}",+ "{message}; index warning: {warning}": "{message}; varování indexu: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "Fotografií v katalogu včetně virtuálních kopií: {in_catalog}; kolekcí: {collections}.",+ "{added} added to the library.": "Přidáno do knihovny: {added}.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "Fotografie katalogu, které už v této knihovně byly a byly aktualizovány z katalogu: {already}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "Neimportované fotografie katalogu, jejichž typy souborů LightCraft nečte: {not_supported}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "Neimportované fotografie katalogu, jejichž typy souborů LightCraft nečte ({types}): {not_supported}.",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "Neimportované fotografie katalogu, jejichž originály jsou na jiném počítači nebo zařízení nebo jejichž cesty v katalogu nejsou platné: {path_refused}. Varování je vypisují.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "Neimportované obrázky katalogu: {skipped}. Jejich záznamy v katalogu jsou neúplné.",- "{photos} catalog records processed; {collections} collections.": "Zpracováno záznamů katalogu: {photos}; kolekcí: {collections}.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Chybějící originály: {missing}. Jejich záznamy jsou v katalogu dostupné; použijte Najít chybějící fotografie a znovu je propojte.", "Applied {name}": "Použito: {name}", "Delete “{name}”?": "Odstranit „{name}“?",@@ -79,9 +79,6 @@ "LightCraft could not open its window: {e}": "LightCraft konnte sein Fenster nicht öffnen: {e}", "LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft konnte sein Fenster nicht öffnen: {e}\n\nEinzelheiten stehen in der Protokolldatei: {path}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Lightroom-Katalog geprüft: {photos} Fotos, {collections} Sammlungen, {missing} fehlen",- "Lightroom import complete: {imported} photos added": "Lightroom-Import abgeschlossen: {imported} Fotos hinzugefügt",- "Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom-Import abgeschlossen: {imported} Fotos hinzugefügt, {failed} fehlgeschlagen",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom-Import abgeschlossen: {imported} Fotos hinzugefügt; Index-Warnung: {warning}", "Lightroom import failed: {e}{warning}": "Lightroom-Import fehlgeschlagen: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Lightroom-Prüfung fehlgeschlagen: {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT ODER Apache-2.0. Schriften: {} (OFL). Symbole: eigene Gestaltung.",@@ -269,8 +266,23 @@ "{n} originals opened in the other editor": "{n} Originale im anderen Editor geöffnet", "· {in_source} identical copies": "· {in_source} identische Kopien", "{count} more warnings.": "{count} weitere Warnungen.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} Katalogfotos liegen in dieser Bibliothek unter „Zuletzt gelöscht“ und bleiben dort; stelle sie dort wieder her, um sie zu sehen.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Lightroom-Import abgeschlossen. {in_catalog} Fotos im Katalog: {parts}",+ "{added} added": "{added} hinzugefügt",+ "{already} already in the library": "{already} schon in der Bibliothek",+ "{in_trash} in Recently Deleted": "{in_trash} in „Zuletzt gelöscht“",+ "{not_supported} not supported": "{not_supported} nicht unterstützt",+ "{path_refused} on refused paths": "{path_refused} mit abgelehnten Pfaden",+ "{incomplete} incomplete": "{incomplete} unvollständig",+ "{failed} failed": "{failed} fehlgeschlagen",+ "{message}; index warning: {warning}": "{message}; Index-Warnung: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "{in_catalog} Fotos im Katalog, virtuelle Kopien eingeschlossen; {collections} Sammlungen.",+ "{added} added to the library.": "{added} zur Bibliothek hinzugefügt.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "{already} Katalogfotos waren schon in dieser Bibliothek und wurden aus dem Katalog aktualisiert.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "{not_supported} Katalogfotos wurden nicht importiert: LightCraft liest ihre Dateitypen nicht.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "{not_supported} Katalogfotos wurden nicht importiert: LightCraft liest ihre Dateitypen nicht ({types}).",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "{path_refused} Katalogfotos wurden nicht importiert: Ihre Originale liegen auf einem anderen Computer oder einem Gerät, oder ihre Pfade im Katalog sind ungültig. Die Warnungen führen sie auf.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} Katalogbilder wurden nicht importiert, weil ihre Einträge im Katalog unvollständig sind.",- "{photos} catalog records processed; {collections} collections.": "{photos} Katalogeinträge verarbeitet; {collections} Sammlungen.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} Originale fehlen. Ihre Katalogeinträge sind verfügbar; verwende „Fehlende Fotos suchen“, um sie neu zu verknüpfen.", "Applied {name}": "{name} angewendet", "Delete “{name}”?": "„{name}“ löschen?",@@ -79,9 +79,6 @@ "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}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Catálogo de Lightroom inspeccionado: {photos} fotos, {collections} colecciones, {missing} no encontradas",- "Lightroom import complete: {imported} photos added": "Importación de Lightroom completada: {imported} fotos añadidas",- "Lightroom import complete: {imported} photos added, {failed} failed": "Importación de Lightroom completada: {imported} fotos añadidas, {failed} con errores",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Importación de Lightroom completada: {imported} fotos añadidas; aviso del índice: {warning}", "Lightroom import failed: {e}{warning}": "Error al importar de Lightroom: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Error al inspeccionar Lightroom: {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Fuentes: {} (OFL). Iconos: originales.",@@ -269,8 +266,23 @@ "{n} originals opened in the other editor": "{n} originales abiertos en el otro editor", "· {in_source} identical copies": "· {in_source} copias idénticas", "{count} more warnings.": "{count} advertencias más.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} fotos del catálogo están en Eliminadas recientemente de esta biblioteca y permanecen allí; restáuralas desde Eliminadas recientemente para verlas.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Importación de Lightroom completada. {in_catalog} fotos en el catálogo: {parts}",+ "{added} added": "{added} añadidas",+ "{already} already in the library": "{already} ya en la biblioteca",+ "{in_trash} in Recently Deleted": "{in_trash} en Eliminadas recientemente",+ "{not_supported} not supported": "{not_supported} no compatibles",+ "{path_refused} on refused paths": "{path_refused} con rutas rechazadas",+ "{incomplete} incomplete": "{incomplete} incompletas",+ "{failed} failed": "{failed} con errores",+ "{message}; index warning: {warning}": "{message}; aviso del índice: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "{in_catalog} fotos en el catálogo, copias virtuales incluidas; {collections} colecciones.",+ "{added} added to the library.": "{added} añadidas a la biblioteca.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "{already} fotos del catálogo ya estaban en esta biblioteca y se actualizaron desde el catálogo.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "No se importaron {not_supported} fotos del catálogo: LightCraft no lee sus tipos de archivo.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "No se importaron {not_supported} fotos del catálogo: LightCraft no lee sus tipos de archivo ({types}).",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "No se importaron {path_refused} fotos del catálogo: sus originales están en otro ordenador o en un dispositivo, o sus rutas en el catálogo no son válidas. Los avisos las enumeran.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "No se importaron {skipped} imágenes del catálogo porque sus registros en el catálogo están incompletos.",- "{photos} catalog records processed; {collections} collections.": "{photos} registros del catálogo procesados; {collections} colecciones.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Faltan {missing} originales. Sus registros del catálogo están disponibles; usa Buscar fotos que faltan para volver a vincularlos.", "Applied {name}": "Se aplicó {name}", "Delete “{name}”?": "¿Eliminar «{name}»?",@@ -116,9 +116,6 @@ "Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Lightroomin geometriaa ei säilytetty kohteelle {path}; tallennettua Upright-muunnosta ei tueta.", "Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Lightroomin geometriaa ei säilytetty kohteelle {path}; tallennettu muunnos ohitetaan, koska Upright on pois päältä.", "Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Lightroomin geometriaa ei säilytetty kohteelle {path}; {mode} lasketaan uudelleen.",- "Lightroom import complete: {imported} photos added": "Lightroom-tuonti valmis: lisättyjä kuvia {imported}",- "Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom-tuonti valmis: lisättyjä kuvia {imported}, epäonnistuneita {failed}",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom-tuonti valmis: lisättyjä kuvia {imported}; hakemistovaroitus: {warning}", "Lightroom import failed: {e}{warning}": "Lightroom-tuonti epäonnistui: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Lightroom-tarkastelu epäonnistui: {e}{warning}", "Listing folders — folders: {folders}, files: {files}": "Listing folders — folders: {folders}, files: {files}",@@ -253,10 +250,25 @@ "{n} tasks are still running: {names}. Quitting stops them.": "Käynnissä olevia tehtäviä: {n}: {names}. Lopettaminen pysäyttää ne.", "{n} {noun}": "{n} {noun}", "{pct} %": "{pct} %",- "{photos} catalog records processed; {collections} collections.": "Käsiteltyjä katalogitietueita: {photos}; kokoelmia: {collections}.", "{pid} (missing)": "{pid} (puuttuu)", "{sel_n} selected · {counted}": "Valittu: {sel_n} · {counted}", "{size} GB": "{size} Gt",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Katalogin kuvia tämän kirjaston Äskettäin poistetuissa: {in_trash}. Ne jäävät sinne; palauta ne Äskettäin poistetuista nähdäksesi ne.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Lightroom-tuonti valmis. Kuvia katalogissa {in_catalog}: {parts}",+ "{added} added": "lisätty {added}",+ "{already} already in the library": "jo kirjastossa {already}",+ "{in_trash} in Recently Deleted": "Äskettäin poistetuissa {in_trash}",+ "{not_supported} not supported": "ei tuettu {not_supported}",+ "{path_refused} on refused paths": "torjutuissa poluissa {path_refused}",+ "{incomplete} incomplete": "puutteellisia {incomplete}",+ "{failed} failed": "epäonnistuneita {failed}",+ "{message}; index warning: {warning}": "{message}; hakemistovaroitus: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "Kuvia katalogissa virtuaalikopiot mukaan lukien: {in_catalog}; kokoelmia: {collections}.",+ "{added} added to the library.": "Lisätty kirjastoon: {added}.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "Katalogin kuvia, jotka olivat jo tässä kirjastossa ja päivitettiin katalogista: {already}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "Katalogin kuvia ei tuotu, koska LightCraft ei lue niiden tiedostotyyppejä: {not_supported}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "Katalogin kuvia ei tuotu, koska LightCraft ei lue niiden tiedostotyyppejä ({types}): {not_supported}.",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "Katalogin kuvia ei tuotu, koska niiden alkuperäiset ovat toisella tietokoneella tai laitteella tai niiden polut katalogissa eivät kelpaa: {path_refused}. Varoitukset luettelevat ne.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} catalog images were not imported because their records in the catalog are incomplete.", "{stage}… {:.0}%": "{stage}… {:.0}%", "{s} s of AV1 video": "{s} s AV1-videota",@@ -79,9 +79,6 @@ "LightCraft could not open its window: {e}": "LightCraft n’a pas pu ouvrir sa fenêtre : {e}", "LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft n’a pas pu ouvrir sa fenêtre : {e}\n\nConsultez le fichier journal pour plus de détails : {path}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Catalogue Lightroom analysé : {photos} photos, {collections} collections, {missing} manquante(s)",- "Lightroom import complete: {imported} photos added": "Importation Lightroom terminée : {imported} photos ajoutées",- "Lightroom import complete: {imported} photos added, {failed} failed": "Importation Lightroom terminée : {imported} photos ajoutées, {failed} échec(s)",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Importation Lightroom terminée : {imported} photos ajoutées ; avertissement d’index : {warning}", "Lightroom import failed: {e}{warning}": "Échec de l’importation Lightroom : {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Échec de l’analyse Lightroom : {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT ou Apache-2.0. Polices : {} (OFL). Icônes : originales.",@@ -269,8 +266,23 @@ "{n} originals opened in the other editor": "{n} originaux ouverts dans l’autre éditeur", "· {in_source} identical copies": "· {in_source} copies identiques", "{count} more warnings.": "{count} avertissements supplémentaires.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} photos du catalogue sont dans « Supprimés récemment » de cette photothèque et y restent ; restaurez-les depuis « Supprimés récemment » pour les voir.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Importation Lightroom terminée. {in_catalog} photos dans le catalogue : {parts}",+ "{added} added": "{added} ajoutées",+ "{already} already in the library": "{already} déjà dans la photothèque",+ "{in_trash} in Recently Deleted": "{in_trash} dans « Supprimés récemment »",+ "{not_supported} not supported": "{not_supported} non prises en charge",+ "{path_refused} on refused paths": "{path_refused} aux chemins refusés",+ "{incomplete} incomplete": "{incomplete} incomplètes",+ "{failed} failed": "{failed} échec(s)",+ "{message}; index warning: {warning}": "{message} ; avertissement d’index : {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "{in_catalog} photos dans le catalogue, copies virtuelles comprises ; {collections} collections.",+ "{added} added to the library.": "{added} ajoutées à la photothèque.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "{already} photos du catalogue étaient déjà dans cette photothèque et ont été mises à jour depuis le catalogue.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "{not_supported} photos du catalogue n’ont pas été importées : LightCraft ne lit pas leurs types de fichiers.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "{not_supported} photos du catalogue n’ont pas été importées : LightCraft ne lit pas leurs types de fichiers ({types}).",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "{path_refused} photos du catalogue n’ont pas été importées : leurs originaux sont sur un autre ordinateur ou un périphérique, ou leurs chemins dans le catalogue ne sont pas valides. Les avertissements les listent.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} images du catalogue n’ont pas été importées, car leurs entrées dans le catalogue sont incomplètes.",- "{photos} catalog records processed; {collections} collections.": "{photos} entrées du catalogue traitées ; {collections} collections.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} originaux sont manquants. Leurs entrées de catalogue sont disponibles ; utilisez Rechercher les photos manquantes pour rétablir les liens.", "Applied {name}": "{name} appliqué", "Delete “{name}”?": "Supprimer « {name} » ?",@@ -127,9 +127,6 @@ "Build Standard-Sized Previews ({scope})": "Buat pratinjau ukuran standar ({scope})", "Discard Smart Previews ({scope})": "Buang pratinjau pintar ({scope})", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Katalog Lightroom diperiksa: {photos} foto, {collections} koleksi, {missing} hilang",- "Lightroom import complete: {imported} photos added": "Impor Lightroom selesai: {imported} foto ditambahkan",- "Lightroom import complete: {imported} photos added, {failed} failed": "Impor Lightroom selesai: {imported} foto ditambahkan, {failed} gagal",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Impor Lightroom selesai: {imported} foto ditambahkan; peringatan indeks: {warning}", "Lightroom import failed: {e}{warning}": "Impor Lightroom gagal: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Pemeriksaan Lightroom gagal: {e}{warning}", "Label Set: {name}": "Set label: {name}",@@ -272,7 +269,6 @@ "{n} originals opened in the other editor": "{n} file asli dibuka di editor lain", "· {in_source} identical copies": "· {in_source} salinan identik", "{count} more warnings.": "{count} peringatan lainnya.",- "{photos} catalog records processed; {collections} collections.": "{photos} entri katalog diproses; {collections} koleksi.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} file asli hilang. Data katalognya masih tersedia; gunakan “Cari foto hilang” untuk menautkannya ulang.", "Applied {name}": "{name} diterapkan", "Delete “{name}”?": "Hapus “{name}”?",@@ -296,6 +292,22 @@ "“{to}” is already a person. Combining them makes one person; Undo separates them again.": "“{to}” sudah terdaftar sebagai orang. Jika digabungkan, keduanya menjadi satu orang; Urungkan akan memisahkannya lagi.", "{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has": "Hasil penggabungan {what} ditambahkan dengan resolusi {mp}: resolusi penuhnya ({full}) memerlukan memori melebihi kapasitas komputer ini", "{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "Hasil penggabungan {what} ditambahkan dengan resolusi {mp}, resolusi maksimum untuk panorama (resolusi penuhnya {full})",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} foto katalog ada di Baru dihapus pada pustaka ini dan tetap di sana; pulihkan dari Baru dihapus untuk melihatnya.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Impor Lightroom selesai. {in_catalog} foto di katalog: {parts}",+ "{added} added": "{added} ditambahkan",+ "{already} already in the library": "{already} sudah ada di pustaka",+ "{in_trash} in Recently Deleted": "{in_trash} di Baru dihapus",+ "{not_supported} not supported": "{not_supported} tidak didukung",+ "{path_refused} on refused paths": "{path_refused} di jalur yang ditolak",+ "{incomplete} incomplete": "{incomplete} tidak lengkap",+ "{failed} failed": "{failed} gagal",+ "{message}; index warning: {warning}": "{message}; peringatan indeks: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "{in_catalog} foto di katalog, termasuk salinan virtual; {collections} koleksi.",+ "{added} added to the library.": "{added} ditambahkan ke pustaka.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "{already} foto katalog sudah ada di pustaka ini dan diperbarui dari katalog.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "{not_supported} foto katalog tidak diimpor: LightCraft tidak membaca jenis file-nya.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "{not_supported} foto katalog tidak diimpor: LightCraft tidak membaca jenis file-nya ({types}).",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "{path_refused} foto katalog tidak diimpor: file aslinya ada di komputer atau perangkat lain, atau jalurnya di katalog tidak valid. Peringatan mencantumkannya.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} catalog images were not imported because their records in the catalog are incomplete.", "Listing folders — folders: {folders}, files: {files}": "Listing folders — folders: {folders}, files: {files}", "Added {n} photo{}": "{n} foto ditambahkan{:.0}",@@ -77,9 +77,6 @@ "LightCraft could not open its window: {e}": "LightCraft non è riuscito ad aprire la sua finestra: {e}", "LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft non è riuscito ad aprire la sua finestra: {e}\n\nI dettagli sono nel file di log: {path}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Catalogo di Lightroom esaminato: foto {photos}, raccolte {collections}, mancanti {missing}",- "Lightroom import complete: {imported} photos added": "Importazione da Lightroom completata: foto aggiunte {imported}",- "Lightroom import complete: {imported} photos added, {failed} failed": "Importazione da Lightroom completata: foto aggiunte {imported}, con errori {failed}",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Importazione da Lightroom completata: foto aggiunte {imported}; avviso sull'indice: {warning}", "Lightroom import failed: {e}{warning}": "Importazione da Lightroom non riuscita: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Analisi di Lightroom non riuscita: {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Caratteri: {} (OFL). Icone: originali.",@@ -250,6 +247,22 @@ "{s} s of AV1 video": "{s} s di video AV1", "Delete {n} effect preset{}? This can't be undone.": "Eliminare i preset effetto ({n})?{:.0} L'operazione non si può annullare.", "{count} more warnings.": "Altri avvisi: {count}.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} foto del catalogo sono in Eliminate di recente di questa libreria e vi restano; ripristinale da Eliminate di recente per vederle.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Importazione da Lightroom completata. Foto nel catalogo: {in_catalog} — {parts}",+ "{added} added": "aggiunte {added}",+ "{already} already in the library": "già nella libreria {already}",+ "{in_trash} in Recently Deleted": "in Eliminate di recente {in_trash}",+ "{not_supported} not supported": "non supportate {not_supported}",+ "{path_refused} on refused paths": "con percorsi rifiutati {path_refused}",+ "{incomplete} incomplete": "incomplete {incomplete}",+ "{failed} failed": "con errori {failed}",+ "{message}; index warning: {warning}": "{message}; avviso sull'indice: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "Foto nel catalogo, copie virtuali incluse: {in_catalog}; raccolte: {collections}.",+ "{added} added to the library.": "Aggiunte alla libreria: {added}.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "Foto del catalogo già in questa libreria, aggiornate dal catalogo: {already}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "Foto del catalogo non importate perché LightCraft non legge i loro tipi di file: {not_supported}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "Foto del catalogo non importate perché LightCraft non legge i loro tipi di file ({types}): {not_supported}.",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "Foto del catalogo non importate perché i loro originali sono su un altro computer o su un dispositivo, o i loro percorsi nel catalogo non sono validi: {path_refused}. Gli avvisi le elencano.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} immagini del catalogo non sono state importate perché i loro record nel catalogo sono incompleti.", "· {in_source} identical copies": "· copie identiche: {in_source}", "{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "Unione {what} aggiunta a {mp}, la dimensione massima di un panorama (la dimensione piena è {full})",@@ -265,7 +278,6 @@ "new, inside {parent}": "nuova, dentro {parent}", "Delete these {n} effect presets?": "Eliminare questi preset effetto ({n})?", "Imported {e} mask effect{} (Masking ▸ Effect)": "Imported {e} mask effect{} (Masking → Effect)",- "{photos} catalog records processed; {collections} collections.": "Record del catalogo elaborati: {photos}; raccolte: {collections}.", "Skipped {problem}": "Saltato: {problem}", "Updated {name}": "{name} aggiornato", "The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "Non è stato possibile scrivere i file XMP di alcune foto ({n}): la loro cartella è scollegata? LightCraft continua a riprovare.",@@ -127,9 +127,6 @@ "Build Standard-Sized Previews ({scope})": "標準プレビューを作成({scope})", "Discard Smart Previews ({scope})": "スマートプレビューを破棄({scope})", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Lightroom カタログを確認しました:写真 {photos} 枚、コレクション {collections} 件、見つからないファイル {missing} 件",- "Lightroom import complete: {imported} photos added": "Lightroom の読み込みが完了しました:{imported} 枚の写真を追加",- "Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom の読み込みが完了しました:{imported} 枚を追加、{failed} 枚が失敗",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom の読み込みが完了しました:{imported} 枚を追加。インデックスの警告:{warning}", "Lightroom import failed: {e}{warning}": "Lightroom の読み込みに失敗しました:{e}{warning}", "Lightroom inspection failed: {e}{warning}": "Lightroom の確認に失敗しました:{e}{warning}", "Label Set: {name}": "ラベルセット:{name}",@@ -273,8 +270,23 @@ "{n} originals opened in the other editor": "{n}個の元ファイルを別のエディタで開きました", "· {in_source} identical copies": "・同一のコピー {in_source} 件", "{count} more warnings.": "ほかに{count}件の警告があります。",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "カタログの写真 {in_trash} 枚は、このライブラリの「最近削除した写真」にあり、そのまま残ります。表示するには「最近削除した写真」から復元してください。",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Lightroom の読み込みが完了しました。カタログの写真 {in_catalog} 枚:{parts}",+ "{added} added": "{added} 枚を追加",+ "{already} already in the library": "{already} 枚はライブラリに既存",+ "{in_trash} in Recently Deleted": "{in_trash} 枚は「最近削除した写真」内",+ "{not_supported} not supported": "{not_supported} 枚は非対応",+ "{path_refused} on refused paths": "{path_refused} 枚はパスを拒否",+ "{incomplete} incomplete": "{incomplete} 枚は不完全",+ "{failed} failed": "{failed} 枚が失敗",+ "{message}; index warning: {warning}": "{message}。インデックスの警告:{warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "カタログの写真は {in_catalog} 枚(仮想コピーを含む)、コレクションは {collections} 件です。",+ "{added} added to the library.": "{added} 枚をライブラリに追加しました。",+ "{already} catalog photos were already in this library and were updated from the catalog.": "カタログの写真 {already} 枚はすでにこのライブラリにあり、カタログの内容で更新しました。",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "カタログの写真 {not_supported} 枚は読み込まれませんでした。LightCraft はそのファイル形式を読み込めません。",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "カタログの写真 {not_supported} 枚は読み込まれませんでした。LightCraft はそのファイル形式({types})を読み込めません。",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "カタログの写真 {path_refused} 枚は読み込まれませんでした。元画像が別のコンピューターやデバイス上にあるか、カタログ内のパスが無効です。警告に一覧があります。", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "カタログ内のレコードが不完全なため、{skipped}件の画像を読み込めませんでした。",- "{photos} catalog records processed; {collections} collections.": "カタログの{photos}件のレコードを処理しました。コレクションは{collections}件です。", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing}件のオリジナルが見つかりません。カタログのレコードは利用できます。「見つからない写真を検索」で再リンクしてください。", "Applied {name}": "{name} を適用しました", "Delete “{name}”?": "「{name}」を削除しますか?",@@ -94,9 +94,6 @@ "Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "{path}-ისთვის Lightroom-ის გეომეტრია არ შენარჩუნდა; Upright-ის შენახული გარდაქმნა არ არის მხარდაჭერილი.", "Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "{path}-ისთვის Lightroom-ის გეომეტრია არ შენარჩუნდა; შენახული გარდაქმნა იგნორირებულია, რადგან Upright გამორთულია.", "Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "{path}-ისთვის Lightroom-ის გეომეტრია არ შენარჩუნდა; {mode} ხელახლა გამოითვლება.",- "Lightroom import complete: {imported} photos added": "Lightroom-იდან იმპორტი დასრულდა: დაემატა ფოტო: {imported}",- "Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom-იდან იმპორტი დასრულდა: დაემატა ფოტო: {imported}, ვერ მოხერხდა: {failed}",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom-იდან იმპორტი დასრულდა: დაემატა ფოტო: {imported}; ინდექსის გაფრთხილება: {warning}", "Lightroom import failed: {e}{warning}": "Lightroom-იდან იმპორტი ვერ მოხერხდა: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Lightroom-ის შემოწმება ვერ მოხერხდა: {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. შრიფტები: {} (OFL). ხატულები: ორიგინალური.",@@ -279,6 +276,22 @@ "Updated {name}": "Updated {name}", "new, inside {parent}": "new, inside {parent}", "{count} more warnings.": "{count} more warnings.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "კატალოგის ფოტოები ამ ბიბლიოთეკის ახლახან წაშლილებში: {in_trash}. ისინი იქ რჩება; სანახავად აღადგინეთ ისინი ახლახან წაშლილებიდან.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Lightroom-იდან იმპორტი დასრულდა. ფოტოები კატალოგში: {in_catalog} — {parts}",+ "{added} added": "დაემატა: {added}",+ "{already} already in the library": "უკვე ბიბლიოთეკაშია: {already}",+ "{in_trash} in Recently Deleted": "ახლახან წაშლილებში: {in_trash}",+ "{not_supported} not supported": "მხარდაუჭერელი: {not_supported}",+ "{path_refused} on refused paths": "უარყოფილი ბილიკით: {path_refused}",+ "{incomplete} incomplete": "არასრული: {incomplete}",+ "{failed} failed": "ვერ მოხერხდა: {failed}",+ "{message}; index warning: {warning}": "{message}; ინდექსის გაფრთხილება: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "ფოტოები კატალოგში, ვირტუალური ასლების ჩათვლით: {in_catalog}; კოლექციები: {collections}.",+ "{added} added to the library.": "დაემატა ბიბლიოთეკას: {added}.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "კატალოგის ფოტოები, რომლებიც უკვე იყო ამ ბიბლიოთეკაში და განახლდა კატალოგიდან: {already}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "არ დაიმპორტდა კატალოგის ფოტოები, რომელთა ფაილის ტიპებს LightCraft ვერ კითხულობს: {not_supported}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "არ დაიმპორტდა კატალოგის ფოტოები, რომელთა ფაილის ტიპებს LightCraft ვერ კითხულობს ({types}): {not_supported}.",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "არ დაიმპორტდა კატალოგის ფოტოები, რომელთა ორიგინალები სხვა კომპიუტერზე ან მოწყობილობაზეა, ან რომელთა ბილიკები კატალოგში არასწორია: {path_refused}. ისინი გაფრთხილებებშია ჩამოთვლილი.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "კატალოგის {skipped} სურათი არ დაიმპორტდა, რადგან მათი ჩანაწერები კატალოგში არასრულია.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.", "{n} JPEG files": "{n} JPEG files",@@ -286,7 +299,6 @@ "{n} photos in view · {slides} slides · {time}": "{n} photos in view · {slides} slides · {time}", "{n} preset{}": "{n} preset{}", "{n} raws can now be developed from their raw data": "{n} raws can now be developed from their raw data",- "{photos} catalog records processed; {collections} collections.": "{photos} catalog records processed; {collections} collections.", "{s} s of AV1 video": "{s} s of AV1 video", "{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "{what} merge added at {mp}, the largest a panorama can be (its full size is {full})", "{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has": "{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has",@@ -70,9 +70,6 @@ "LightCraft could not open its window: {e}": "LightCraft nie mógł otworzyć okna: {e}", "LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft nie mógł otworzyć okna: {e}\n\nSzczegóły znajdują się w pliku dziennika: {path}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Sprawdzono katalog Lightroom — zdjęcia: {photos}, kolekcje: {collections}, brakujące: {missing}",- "Lightroom import complete: {imported} photos added": "Import z Lightroom zakończony — dodane zdjęcia: {imported}",- "Lightroom import complete: {imported} photos added, {failed} failed": "Import z Lightroom zakończony — dodane zdjęcia: {imported}, błędy: {failed}",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Import z Lightroom zakończony — dodane zdjęcia: {imported}; ostrzeżenie indeksu: {warning}", "Lightroom import failed: {e}{warning}": "Import z Lightroom nieudany: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Sprawdzanie katalogu Lightroom nieudane: {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT lub Apache-2.0. Czcionki: {} (OFL). Ikony: oryginalne.",@@ -261,8 +258,23 @@ "Update “{name}” with Current Settings": "Zaktualizuj „{name}” bieżącymi ustawieniami", "{n} preset{}": "Ustawienia: {n}{:.0}", "{count} more warnings.": "Pozostałe ostrzeżenia: {count}.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Zdjęcia katalogu w Ostatnio usuniętych tej biblioteki: {in_trash}. Pozostają tam; przywróć je z Ostatnio usuniętych, aby je zobaczyć.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Import z Lightroom zakończony. Zdjęcia w katalogu: {in_catalog} — {parts}",+ "{added} added": "dodane: {added}",+ "{already} already in the library": "już w bibliotece: {already}",+ "{in_trash} in Recently Deleted": "w Ostatnio usuniętych: {in_trash}",+ "{not_supported} not supported": "nieobsługiwane: {not_supported}",+ "{path_refused} on refused paths": "z odrzuconą ścieżką: {path_refused}",+ "{incomplete} incomplete": "niekompletne: {incomplete}",+ "{failed} failed": "błędy: {failed}",+ "{message}; index warning: {warning}": "{message}; ostrzeżenie indeksu: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "Zdjęcia w katalogu, z wirtualnymi kopiami: {in_catalog}; kolekcje: {collections}.",+ "{added} added to the library.": "Dodane do biblioteki: {added}.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "Zdjęcia katalogu, które już były w tej bibliotece i zostały zaktualizowane z katalogu: {already}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "Niezaimportowane zdjęcia katalogu, których typów plików LightCraft nie odczytuje: {not_supported}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "Niezaimportowane zdjęcia katalogu, których typów plików LightCraft nie odczytuje ({types}): {not_supported}.",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "Niezaimportowane zdjęcia katalogu, których oryginały są na innym komputerze lub urządzeniu albo których ścieżki w katalogu są nieprawidłowe: {path_refused}. Ostrzeżenia je wymieniają.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "Nie zaimportowano obrazów z katalogu: {skipped}. Ich rekordy w katalogu są niekompletne.",- "{photos} catalog records processed; {collections} collections.": "Przetworzone rekordy katalogu: {photos}; kolekcje: {collections}.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Brakujące oryginały: {missing}. Ich rekordy w katalogu są dostępne; użyj polecenia Znajdź brakujące zdjęcia, aby je ponownie powiązać.", "Applied {name}": "Zastosowano {name}", "Delete “{name}”?": "Usunąć „{name}”?",@@ -48,9 +48,6 @@ "Imported {n} preset{}, {f} file{} not readable": "{n} predefinição(ões) importada(s){:.0}, {f} arquivo(s) não legível(is){:.0}", "Imported {} photo{}": "{0} foto{1} importada{1}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Catálogo do Lightroom inspecionado: {photos} fotos, {collections} coleções, {missing} ausentes",- "Lightroom import complete: {imported} photos added": "Importação do Lightroom concluída: {imported} fotos adicionadas",- "Lightroom import complete: {imported} photos added, {failed} failed": "Importação do Lightroom concluída: {imported} fotos adicionadas, {failed} com falha",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Importação do Lightroom concluída: {imported} fotos adicionadas; aviso do índice: {warning}", "Lightroom import failed: {e}{warning}": "Falha na importação do Lightroom: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Falha na inspeção do Lightroom: {e}{warning}", "Label Set: {name}": "Conjunto de rótulos: {name}",@@ -273,8 +270,23 @@ "{n} originals opened in the other editor": "{n} originais abertos no outro editor", "· {in_source} identical copies": "· {in_source} cópias idênticas", "{count} more warnings.": "Mais {count} avisos.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} fotos do catálogo estão em Excluídos recentemente desta biblioteca e continuam lá; restaure-as de Excluídos recentemente para vê-las.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Importação do Lightroom concluída. {in_catalog} fotos no catálogo: {parts}",+ "{added} added": "{added} adicionadas",+ "{already} already in the library": "{already} já na biblioteca",+ "{in_trash} in Recently Deleted": "{in_trash} em Excluídos recentemente",+ "{not_supported} not supported": "{not_supported} não compatíveis",+ "{path_refused} on refused paths": "{path_refused} com caminhos recusados",+ "{incomplete} incomplete": "{incomplete} incompletas",+ "{failed} failed": "{failed} com falha",+ "{message}; index warning: {warning}": "{message}; aviso do índice: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "{in_catalog} fotos no catálogo, cópias virtuais incluídas; {collections} coleções.",+ "{added} added to the library.": "{added} adicionadas à biblioteca.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "{already} fotos do catálogo já estavam nesta biblioteca e foram atualizadas a partir do catálogo.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "{not_supported} fotos do catálogo não foram importadas: o LightCraft não lê seus tipos de arquivo.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "{not_supported} fotos do catálogo não foram importadas: o LightCraft não lê seus tipos de arquivo ({types}).",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "{path_refused} fotos do catálogo não foram importadas: seus originais estão em outro computador ou em um dispositivo, ou seus caminhos no catálogo não são válidos. Os avisos as listam.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} imagens do catálogo não foram importadas porque seus registros no catálogo estão incompletos.",- "{photos} catalog records processed; {collections} collections.": "{photos} registros do catálogo processados; {collections} coleções.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} originais estão ausentes. Seus registros no catálogo estão disponíveis; use Encontrar fotos ausentes para vinculá-los novamente.", "Applied {name}": "{name} aplicado", "Delete “{name}”?": "Excluir “{name}”?",@@ -83,9 +83,6 @@ "LightCraft could not open its window: {e}": "LightCraft не удалось открыть окно: {e}", "LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft не удалось открыть окно: {e}\n\nПодробности в файле журнала: {path}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Каталог Lightroom проверен: фото — {photos}, коллекций — {collections}, отсутствует — {missing}",- "Lightroom import complete: {imported} photos added": "Импорт из Lightroom завершён: добавлено фото — {imported}",- "Lightroom import complete: {imported} photos added, {failed} failed": "Импорт из Lightroom завершён: добавлено фото — {imported}, ошибок — {failed}",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Импорт из Lightroom завершён: добавлено фото — {imported}; предупреждение индекса: {warning}", "Lightroom import failed: {e}{warning}": "Ошибка импорта из Lightroom: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Ошибка проверки Lightroom: {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Шрифты: {} (OFL). Значки: оригинальные.",@@ -269,8 +266,23 @@ "{n} originals opened in the other editor": "Открыто оригиналов в другом редакторе: {n}", "· {in_source} identical copies": "· идентичных копий: {in_source}", "{count} more warnings.": "Других предупреждений: {count}.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Фото каталога в «Недавно удалённых» этой библиотеки: {in_trash}. Они остаются там; чтобы увидеть их, восстановите их из «Недавно удалённых».",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Импорт из Lightroom завершён. Фото в каталоге: {in_catalog} — {parts}",+ "{added} added": "добавлено: {added}",+ "{already} already in the library": "уже в библиотеке: {already}",+ "{in_trash} in Recently Deleted": "в «Недавно удалённых»: {in_trash}",+ "{not_supported} not supported": "не поддерживается: {not_supported}",+ "{path_refused} on refused paths": "с отклонённым путём: {path_refused}",+ "{incomplete} incomplete": "неполных: {incomplete}",+ "{failed} failed": "ошибок: {failed}",+ "{message}; index warning: {warning}": "{message}; предупреждение индекса: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "Фото в каталоге, включая виртуальные копии: {in_catalog}; коллекций: {collections}.",+ "{added} added to the library.": "Добавлено в библиотеку: {added}.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "Фото каталога, которые уже были в этой библиотеке и обновлены из каталога: {already}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "Не импортированы фото каталога, типы файлов которых LightCraft не читает: {not_supported}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "Не импортированы фото каталога, типы файлов которых LightCraft не читает ({types}): {not_supported}.",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "Не импортированы фото каталога, оригиналы которых находятся на другом компьютере или устройстве или пути которых в каталоге недопустимы: {path_refused}. Они перечислены в предупреждениях.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "Не импортированы изображения каталога: {skipped}. Их записи в каталоге неполные.",- "{photos} catalog records processed; {collections} collections.": "Обработано записей каталога: {photos}; коллекций: {collections}.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Не найдены оригиналы: {missing}. Их записи каталога доступны; используйте поиск отсутствующих фотографий, чтобы восстановить ссылки.", "Applied {name}": "Применено: {name}", "Delete “{name}”?": "Удалить «{name}»?",@@ -113,9 +113,6 @@ "Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Lightroom-geometrin för {path} bevarades inte; den sparade transformeringen ignoreras eftersom Upright är av.", "Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Lightroom-geometrin för {path} bevarades inte; sparad Upright-transformering stöds inte.", "Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Lightroom-geometrin bevarades inte för {path}; {mode} beräknas om.",- "Lightroom import complete: {imported} photos added": "Lightroom-importen är klar: {imported} foton har lagts till",- "Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom-importen är klar: {imported} foton har lagts till, {failed} misslyckades",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom-importen är klar: {imported} foton har lagts till; indexvarning: {warning}", "Lightroom import failed: {e}{warning}": "Lightroom-importen misslyckades: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Granskningen av Lightroom misslyckades: {e}{warning}", "Making the denoised picture… {}%": "Skapar den brusreducerade bilden… {}%",@@ -245,7 +242,6 @@ "{n} tasks are still running: {names}. Quitting stops them.": "{n} uppgifter körs fortfarande: {names}. Om du avslutar stoppas de.", "{n} {noun}": "{n} {noun}", "{pct} %": "{pct} %",- "{photos} catalog records processed; {collections} collections.": "{photos} katalogposter bearbetade; {collections} samlingar.", "{pid} (missing)": "{pid} (saknas)", "{sel_n} selected · {counted}": "{sel_n} markerade · {counted}", "{size} GB": "{size} GB",@@ -296,6 +292,22 @@ "Move {n} photo{} to “{}”?": "Move {n} photo{} to “{}”?", "Moved {n} photo{} to “{}”. Edit → Undo moves them back.": "Moved {n} photo{} to “{}”. Edit → Undo moves them back.", "{n} selected photos · {pages} pages": "{n} selected photos · {pages} pages",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} katalogfoton finns i Nyligen raderade i det här biblioteket och ligger kvar där; återställ dem från Nyligen raderade för att se dem.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Lightroom-importen är klar. {in_catalog} foton i katalogen: {parts}",+ "{added} added": "{added} tillagda",+ "{already} already in the library": "{already} redan i biblioteket",+ "{in_trash} in Recently Deleted": "{in_trash} i Nyligen raderade",+ "{not_supported} not supported": "{not_supported} stöds inte",+ "{path_refused} on refused paths": "{path_refused} med nekade sökvägar",+ "{incomplete} incomplete": "{incomplete} ofullständiga",+ "{failed} failed": "{failed} misslyckades",+ "{message}; index warning: {warning}": "{message}; indexvarning: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "{in_catalog} foton i katalogen, virtuella kopior inräknade; {collections} samlingar.",+ "{added} added to the library.": "{added} har lagts till i biblioteket.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "{already} katalogfoton fanns redan i det här biblioteket och har uppdaterats från katalogen.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "{not_supported} katalogfoton importerades inte: LightCraft läser inte deras filtyper.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "{not_supported} katalogfoton importerades inte: LightCraft läser inte deras filtyper ({types}).",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "{path_refused} katalogfoton importerades inte: deras original finns på en annan dator eller en enhet, eller så är deras sökvägar i katalogen ogiltiga. Varningarna listar dem.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} catalog images were not imported because their records in the catalog are incomplete.", "Listing folders — folders: {folders}, files: {files}": "Listing folders — folders: {folders}, files: {files}", "Added {n} photo{}": "{n} foto tillagda{:.0}",@@ -79,9 +79,6 @@ "LightCraft could not open its window: {e}": "LightCraft не вдалося відкрити вікно: {e}", "LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft не вдалося відкрити вікно: {e}\n\nПодробиці у файлі журналу: {path}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Каталог Lightroom перевірено: фото — {photos}, колекцій — {collections}, відсутні — {missing}",- "Lightroom import complete: {imported} photos added": "Імпорт Lightroom завершено: додано фото — {imported}",- "Lightroom import complete: {imported} photos added, {failed} failed": "Імпорт Lightroom завершено: додано фото — {imported}, помилок — {failed}",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Імпорт Lightroom завершено: додано фото — {imported}; попередження індексу: {warning}", "Lightroom import failed: {e}{warning}": "Помилка імпорту Lightroom: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Помилка перевірки Lightroom: {e}{warning}", "MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Шрифти: {} (OFL). Значки: оригінальні.",@@ -269,8 +266,23 @@ "{n} originals opened in the other editor": "Відкрито оригіналів в іншому редакторі: {n}", "· {in_source} identical copies": "· ідентичних копій: {in_source}", "{count} more warnings.": "Інших попереджень: {count}.",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Фото каталогу в нещодавно видалених цієї бібліотеки: {in_trash}. Вони залишаються там; щоб побачити їх, відновіть їх із нещодавно видалених.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Імпорт Lightroom завершено. Фото в каталозі: {in_catalog} — {parts}",+ "{added} added": "додано: {added}",+ "{already} already in the library": "уже в бібліотеці: {already}",+ "{in_trash} in Recently Deleted": "у нещодавно видалених: {in_trash}",+ "{not_supported} not supported": "не підтримується: {not_supported}",+ "{path_refused} on refused paths": "з відхиленим шляхом: {path_refused}",+ "{incomplete} incomplete": "неповних: {incomplete}",+ "{failed} failed": "помилок: {failed}",+ "{message}; index warning: {warning}": "{message}; попередження індексу: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "Фото в каталозі, разом із віртуальними копіями: {in_catalog}; колекцій: {collections}.",+ "{added} added to the library.": "Додано до бібліотеки: {added}.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "Фото каталогу, які вже були в цій бібліотеці й оновлені з каталогу: {already}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "Не імпортовано фото каталогу, типи файлів яких LightCraft не читає: {not_supported}.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "Не імпортовано фото каталогу, типи файлів яких LightCraft не читає ({types}): {not_supported}.",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "Не імпортовано фото каталогу, оригінали яких на іншому комп’ютері чи пристрої або шляхи яких у каталозі недійсні: {path_refused}. Їх перелічено в попередженнях.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "Не імпортовано зображення каталогу: {skipped}. Їхні записи в каталозі неповні.",- "{photos} catalog records processed; {collections} collections.": "Оброблено записів каталогу: {photos}; колекцій: {collections}.", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Не знайдено оригінали: {missing}. Їхні записи каталогу доступні; скористайтеся пошуком відсутніх фотографій, щоб відновити посилання.", "Applied {name}": "Застосовано: {name}", "Delete “{name}”?": "Видалити «{name}»?",@@ -116,9 +116,6 @@ "Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Hình học Lightroom cho {path} không được giữ; biến đổi Upright đã lưu không được hỗ trợ.", "Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Hình học Lightroom cho {path} không được giữ; biến đổi đã lưu bị bỏ qua vì Upright đang tắt.", "Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Hình học Lightroom cho {path} không được giữ; {mode} sẽ được tính lại.",- "Lightroom import complete: {imported} photos added": "Nhập Lightroom xong: đã thêm {imported} ảnh",- "Lightroom import complete: {imported} photos added, {failed} failed": "Nhập Lightroom xong: đã thêm {imported} ảnh, {failed} thất bại",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Nhập Lightroom xong: đã thêm {imported} ảnh; cảnh báo chỉ mục: {warning}", "Lightroom import failed: {e}{warning}": "Nhập Lightroom thất bại: {e}{warning}", "Lightroom inspection failed: {e}{warning}": "Kiểm tra Lightroom thất bại: {e}{warning}", "Listing folders — folders: {folders}, files: {files}": "Listing folders — folders: {folders}, files: {files}",@@ -253,10 +250,25 @@ "{n} tasks are still running: {names}. Quitting stops them.": "Còn {n} tác vụ đang chạy: {names}. Thoát sẽ dừng chúng.", "{n} {noun}": "{noun}: {n}", "{pct} %": "{pct} %",- "{photos} catalog records processed; {collections} collections.": "{photos} catalog records processed; {collections} collections.", "{pid} (missing)": "{pid} (thiếu)", "{sel_n} selected · {counted}": "Đã chọn {sel_n} · {counted}", "{size} GB": "{size} GB",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} ảnh của danh mục nằm trong Đã xóa gần đây của thư viện này và vẫn ở đó; hãy khôi phục chúng từ Đã xóa gần đây để xem.",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Nhập Lightroom xong. {in_catalog} ảnh trong danh mục: {parts}",+ "{added} added": "đã thêm {added}",+ "{already} already in the library": "{already} đã có trong thư viện",+ "{in_trash} in Recently Deleted": "{in_trash} trong Đã xóa gần đây",+ "{not_supported} not supported": "{not_supported} không được hỗ trợ",+ "{path_refused} on refused paths": "{path_refused} ở đường dẫn bị từ chối",+ "{incomplete} incomplete": "{incomplete} không đầy đủ",+ "{failed} failed": "{failed} thất bại",+ "{message}; index warning: {warning}": "{message}; cảnh báo chỉ mục: {warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "{in_catalog} ảnh trong danh mục, kể cả bản sao ảo; {collections} bộ sưu tập.",+ "{added} added to the library.": "Đã thêm {added} vào thư viện.",+ "{already} catalog photos were already in this library and were updated from the catalog.": "{already} ảnh của danh mục đã có trong thư viện này và được cập nhật từ danh mục.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "{not_supported} ảnh của danh mục không được nhập: LightCraft không đọc được loại tệp của chúng.",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "{not_supported} ảnh của danh mục không được nhập: LightCraft không đọc được loại tệp của chúng ({types}).",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "{path_refused} ảnh của danh mục không được nhập: bản gốc của chúng nằm trên máy tính hoặc thiết bị khác, hoặc đường dẫn của chúng trong danh mục không hợp lệ. Các cảnh báo liệt kê chúng.", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} catalog images were not imported because their records in the catalog are incomplete.", "{stage}… {:.0}%": "{stage}… {:.0}%", "{s} s of AV1 video": "{s} s of AV1 video",@@ -127,9 +127,6 @@ "Build Standard-Sized Previews ({scope})": "生成标准尺寸预览({scope})", "Discard Smart Previews ({scope})": "丢弃智能预览({scope})", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "已检查 Lightroom 目录:{photos} 张照片,{collections} 个收藏夹,{missing} 个缺失",- "Lightroom import complete: {imported} photos added": "Lightroom 导入完成:已添加 {imported} 张照片",- "Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom 导入完成:已添加 {imported} 张照片,{failed} 张失败",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom 导入完成:已添加 {imported} 张照片;索引警告:{warning}", "Lightroom import failed: {e}{warning}": "Lightroom 导入失败:{e}{warning}", "Lightroom inspection failed: {e}{warning}": "Lightroom 检查失败:{e}{warning}", "Label Set: {name}": "标签集:{name}",@@ -273,8 +270,23 @@ "{n} originals opened in the other editor": "已在其他编辑器中打开 {n} 个原始文件", "· {in_source} identical copies": "· {in_source} 个相同副本", "{count} more warnings.": "另有 {count} 条警告。",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "目录中有 {in_trash} 张照片位于此图库的“最近删除”中,并将保留在那里;如需查看,请从“最近删除”中恢复。",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Lightroom 导入完成。目录中有 {in_catalog} 张照片:{parts}",+ "{added} added": "已添加 {added} 张",+ "{already} already in the library": "{already} 张已在图库中",+ "{in_trash} in Recently Deleted": "{in_trash} 张在“最近删除”中",+ "{not_supported} not supported": "{not_supported} 张不受支持",+ "{path_refused} on refused paths": "{path_refused} 张路径被拒绝",+ "{incomplete} incomplete": "{incomplete} 张不完整",+ "{failed} failed": "{failed} 张失败",+ "{message}; index warning: {warning}": "{message};索引警告:{warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "目录中有 {in_catalog} 张照片(含虚拟副本);{collections} 个收藏集。",+ "{added} added to the library.": "已将 {added} 张添加到图库。",+ "{already} catalog photos were already in this library and were updated from the catalog.": "目录中有 {already} 张照片已在此图库中,并已根据目录更新。",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "目录中有 {not_supported} 张照片未导入:LightCraft 无法读取其文件类型。",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "目录中有 {not_supported} 张照片未导入:LightCraft 无法读取其文件类型({types})。",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "目录中有 {path_refused} 张照片未导入:其原始文件位于其他计算机或设备上,或其在目录中的路径无效。警告中列出了它们。", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "有 {skipped} 张图像未导入,因为它们在目录中的记录不完整。",- "{photos} catalog records processed; {collections} collections.": "已处理 {photos} 条目录记录;{collections} 个收藏集。", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "有 {missing} 个原始文件缺失。其目录记录可用;请使用“查找缺失照片”重新链接文件。", "Applied {name}": "已应用 {name}", "Delete “{name}”?": "删除“{name}”?",@@ -45,9 +45,6 @@ "Imported {n} preset{}, {f} file{} not readable": "已匯入 {n} 個預設集{:.0},{f} 個檔案無法讀取{:.0}", "Imported {} photo{}": "已匯入 {} 張照片{:.0}", "Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "已檢查 Lightroom 目錄:{photos} 張相片,{collections} 個收藏集,{missing} 個遺失",- "Lightroom import complete: {imported} photos added": "Lightroom 匯入完成:已加入 {imported} 張相片",- "Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom 匯入完成:已加入 {imported} 張相片,{failed} 張失敗",- "Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom 匯入完成:已加入 {imported} 張相片;索引警告:{warning}", "Lightroom import failed: {e}{warning}": "Lightroom 匯入失敗:{e}{warning}", "Lightroom inspection failed: {e}{warning}": "Lightroom 檢查失敗:{e}{warning}", "Label Set: {name}": "標籤集:{name}",@@ -273,8 +270,23 @@ "{n} originals opened in the other editor": "已在其他編輯器中開啟 {n} 個原始檔案", "· {in_source} identical copies": "· {in_source} 個相同副本", "{count} more warnings.": "另有 {count} 則警告。",+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "目錄中有 {in_trash} 張照片位於此圖庫的「最近刪除」中,並會保留在那裡;如需檢視,請從「最近刪除」中復原。",+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}": "Lightroom 匯入完成。目錄中有 {in_catalog} 張相片:{parts}",+ "{added} added": "已加入 {added} 張",+ "{already} already in the library": "{already} 張已在圖庫中",+ "{in_trash} in Recently Deleted": "{in_trash} 張在「最近刪除」中",+ "{not_supported} not supported": "{not_supported} 張不受支援",+ "{path_refused} on refused paths": "{path_refused} 張路徑遭拒",+ "{incomplete} incomplete": "{incomplete} 張不完整",+ "{failed} failed": "{failed} 張失敗",+ "{message}; index warning: {warning}": "{message};索引警告:{warning}",+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.": "目錄中有 {in_catalog} 張相片(含虛擬副本);{collections} 個收藏集。",+ "{added} added to the library.": "已將 {added} 張加入圖庫。",+ "{already} catalog photos were already in this library and were updated from the catalog.": "目錄中有 {already} 張相片已在此圖庫中,並已依目錄更新。",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.": "目錄中有 {not_supported} 張相片未匯入:LightCraft 無法讀取其檔案類型。",+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).": "目錄中有 {not_supported} 張相片未匯入:LightCraft 無法讀取其檔案類型({types})。",+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.": "目錄中有 {path_refused} 張相片未匯入:其原始檔案位於其他電腦或裝置上,或其在目錄中的路徑無效。警告中列出了它們。", "{skipped} catalog images were not imported because their records in the catalog are incomplete.": "有 {skipped} 張影像未匯入,因為它們在目錄中的記錄不完整。",- "{photos} catalog records processed; {collections} collections.": "已處理 {photos} 筆目錄記錄;{collections} 個收藏集。", "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "有 {missing} 個原始檔案遺失。其目錄記錄可用;請使用「尋找遺失相片」重新連結檔案。", "Applied {name}": "已套用 {name}", "Delete “{name}”?": "刪除「{name}」?",@@ -286,24 +286,14 @@ fn terminal(app: &mut LightcraftApp, ctx: &egui::Context, task: &LightroomTask, missing = missing ) } else {- let report = value.get("report").unwrap_or(&value);- let imported = report["imported"].as_u64().unwrap_or(0);- let failed = report["failed"].as_array().map_or(0, Vec::len);- let warning = value.get("indexWarning").and_then(Value::as_str);- if let Some(warning) = warning {- crate::i18n::tr_format!(- "Lightroom import complete: {imported} photos added; index warning: {warning}",- imported = imported,- warning = warning- )- } else if failed == 0 {- crate::i18n::tr_format!("Lightroom import complete: {imported} photos added", imported = imported)- } else {- crate::i18n::tr_format!("Lightroom import complete: {imported} photos added, {failed} failed", imported = imported, failed = failed)+ let message = import_summary(value.get("report").unwrap_or(&value));+ match value.get("indexWarning").and_then(Value::as_str) {+ Some(warning) => crate::i18n::tr_format!("{message}; index warning: {warning}", message = message, warning = warning),+ None => message, } }; if matches!(kind, Kind::Import) && value.get("cancelled") != Some(&Value::Bool(true)) {- app.lightroom_result = Some(result_dialog(&value, &message));+ app.lightroom_result = Some(result_dialog(&value, &message, &app.session.catalog)); show_result(app); } app.lightroom_last = Some(value);@@ -311,33 +301,105 @@ fn terminal(app: &mut LightcraftApp, ctx: &egui::Context, task: &LightroomTask, app.toast_for(ctx, message, 6.0); } +/// The toast for a finished import: how many photos the catalog has and what became of them, the+/// photos added and then each other outcome there is (the report's breakdown, see+/// `lightroom_catalog::apply_prepared`).+fn import_summary(report: &Value) -> String {+ let count = |key: &str| report[key].as_u64().unwrap_or(0);+ let mut parts = vec![crate::i18n::tr_format!("{added} added", added = count("added"))];+ let already = count("alreadyInLibrary");+ if already > 0 {+ parts.push(crate::i18n::tr_format!("{already} already in the library", already = already));+ }+ let in_trash = count("inRecentlyDeleted");+ if in_trash > 0 {+ parts.push(crate::i18n::tr_format!("{in_trash} in Recently Deleted", in_trash = in_trash));+ }+ let not_supported = count("notSupported");+ if not_supported > 0 {+ parts.push(crate::i18n::tr_format!("{not_supported} not supported", not_supported = not_supported));+ }+ let path_refused = count("pathRefused");+ if path_refused > 0 {+ parts.push(crate::i18n::tr_format!("{path_refused} on refused paths", path_refused = path_refused));+ }+ let incomplete = count("incomplete");+ if incomplete > 0 {+ parts.push(crate::i18n::tr_format!("{incomplete} incomplete", incomplete = incomplete));+ }+ let failed = report["failed"].as_array().map_or(0, Vec::len);+ if failed > 0 {+ parts.push(crate::i18n::tr_format!("{failed} failed", failed = failed));+ }+ crate::i18n::tr_format!(+ "Lightroom import complete. {in_catalog} photos in the catalog: {parts}",+ in_catalog = count("inCatalog"),+ parts = parts.join(", ")+ )+}+ fn show_result(app: &mut LightcraftApp) { if app.ui.dialog.is_none() { app.ui.dialog = app.lightroom_result.take(); } } -/// The lasting result window for an inspect or import: counts, failures, missing originals, the-/// skipped images (one line, in the UI language) and at most eight warnings.-fn result_dialog(value: &Value, summary: &str) -> crate::state::Dialog {+/// The lasting result window for an import: how many photos the catalog has and where each went+/// (one line per outcome there is, adding up to the catalog's photos), failures, missing+/// originals and at most eight warnings. Show in Library goes to the lowest mapped photo that is+/// not in Recently Deleted, where All Photos can show it.+fn result_dialog(value: &Value, summary: &str, catalog: &lightcraft_catalog::Catalog) -> crate::state::Dialog { let report = value.get("report").unwrap_or(value);+ let count = |key: &str| report[key].as_u64().unwrap_or(0); let mut details = Vec::new();- if let Some(photos) = report["photos"].as_u64() {+ if let Some(in_catalog) = report["inCatalog"].as_u64() { details.push(crate::i18n::tr_format!(- "{photos} catalog records processed; {collections} collections.",- photos = photos,- collections = report["collections"].as_u64().unwrap_or(0)+ "{in_catalog} photos in the catalog, virtual copies included; {collections} collections.",+ in_catalog = in_catalog,+ collections = count("collections") )); }- if let Some(failures) = report["failed"].as_array() {- details- .extend(failures.iter().take(4).map(|failure| format!("{}: {}", failure[0].as_str().unwrap_or(""), failure[1].as_str().unwrap_or(""))));+ let added = count("added");+ if added > 0 {+ details.push(crate::i18n::tr_format!("{added} added to the library.", added = added)); }- let missing = report["missing"].as_array().map_or(0, Vec::len);- if missing > 0 {+ let already = count("alreadyInLibrary");+ if already > 0 { details.push(crate::i18n::tr_format!(- "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.",- missing = missing+ "{already} catalog photos were already in this library and were updated from the catalog.",+ already = already+ ));+ }+ let in_trash = count("inRecentlyDeleted");+ if in_trash > 0 {+ details.push(crate::i18n::tr_format!(+ "{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.",+ in_trash = in_trash+ ));+ }+ let not_supported = count("notSupported");+ if not_supported > 0 {+ // the file types, as the files name them (MP4, MOV); one without an extension isn't named+ let types: Vec<String> =+ report["notSupportedTypes"].as_object().into_iter().flatten().map(|(ext, _)| ext.to_uppercase()).filter(|ext| !ext.is_empty()).collect();+ details.push(if types.is_empty() {+ crate::i18n::tr_format!(+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types.",+ not_supported = not_supported+ )+ } else {+ crate::i18n::tr_format!(+ "{not_supported} catalog photos were not imported: LightCraft doesn't read their file types ({types}).",+ not_supported = not_supported,+ types = types.join(", ")+ )+ });+ }+ let path_refused = count("pathRefused");+ if path_refused > 0 {+ details.push(crate::i18n::tr_format!(+ "{path_refused} catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.",+ path_refused = path_refused )); } // images whose catalog records are incomplete come as data (`skipped`), said here in the UI@@ -349,6 +411,17 @@ fn result_dialog(value: &Value, summary: &str) -> crate::state::Dialog { skipped = skipped )); }+ let missing = report["missing"].as_array().map_or(0, Vec::len);+ if missing > 0 {+ details.push(crate::i18n::tr_format!(+ "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.",+ missing = missing+ ));+ }+ if let Some(failures) = report["failed"].as_array() {+ details+ .extend(failures.iter().take(4).map(|failure| format!("{}: {}", failure[0].as_str().unwrap_or(""), failure[1].as_str().unwrap_or(""))));+ } if let Some(warning) = value.get("indexWarning").and_then(Value::as_str) { details.push(warning.to_string()); }@@ -359,7 +432,10 @@ fn result_dialog(value: &Value, summary: &str) -> crate::state::Dialog { details.push(crate::i18n::tr_format!("{count} more warnings.", count = warnings.len() - 8)); } }- let first = value.get("error").is_none().then(|| report["mapping"].as_object()?.values().filter_map(Value::as_u64).min()).flatten();+ // a catalog photo can map to a library photo left in Recently Deleted (#2589), which All+ // Photos doesn't show+ let live = |id: &u64| catalog.photo(lightcraft_catalog::PhotoId(*id)).is_some_and(|p| !p.deleted);+ let first = value.get("error").is_none().then(|| report["mapping"].as_object()?.values().filter_map(Value::as_u64).filter(live).min()).flatten(); crate::state::Dialog::LightroomResult { summary: summary.to_string(), library: report["library"].as_str().or_else(|| value["library"].as_str()).map(str::to_string),@@ -650,9 +726,9 @@ mod tests { rules: vec![lightcraft_catalog::rules::Rule::Field { field: "rating".into(), op: "is".into(), value: json!(5) }], }); let (task, _tx, _, _) = fake_import(&h);- let value = json!({"photos": 1, "imported": 1, "collections": 0, "missing": ["D:/Photos/a.NEF"], "mapping": {"1": id}, "library": "C:/Pictures/LightCraft Library"});+ let value = json!({"photos": 1, "imported": 1, "inCatalog": 1, "added": 1, "collections": 0, "missing": ["D:/Photos/a.NEF"], "mapping": {"1": id}, "library": "C:/Pictures/LightCraft Library"}); terminal(&mut h.app, &h.view.ctx, &task, value);- assert!(h.app.ui.toast.as_ref().unwrap().0.contains("1 photos added"));+ assert!(h.app.ui.toast.as_ref().unwrap().0.contains("1 photos in the catalog: 1 added")); h.step(); h.step(); let text = h.painted_text().join(" ");@@ -673,6 +749,33 @@ mod tests { assert!(!h.app.session.visible().is_empty()); } + /// Show in Library goes to a photo All Photos shows: not a master left in Recently Deleted+ /// whose new virtual copy the import added (#2589), and nowhere when no mapped photo is shown.+ #[test]+ fn show_in_library_skips_photos_in_recently_deleted() {+ use lightcraft_catalog::{Op, Photo, Source};+ let mut s = lightcraft_engine::Session::new();+ let mut add = |deleted: bool| {+ let id = s.catalog.alloc_photo_id();+ let mut p = Photo::new(id, Source::File { path: format!("/p/{}.jpg", id.0) }, "a.jpg", "JPEG", 2, 2, "now");+ p.deleted = deleted;+ s.commit("Add", Op::AddPhoto { photo: Box::new(p) }).unwrap();+ id.0+ };+ let (trashed, live) = (add(true), add(false));+ assert!(trashed < live);+ let first = |mapping: Value| {+ let crate::state::Dialog::LightroomResult { first, .. } = result_dialog(&json!({"report": {"mapping": mapping}}), "done", &s.catalog)+ else {+ panic!("not a Lightroom result")+ };+ first+ };+ assert_eq!(first(json!({"1": trashed, "2": live})), Some(live));+ assert_eq!(first(json!({"1": trashed})), None, "only a photo in Recently Deleted: no Show in Library");+ assert_eq!(first(json!({"1": live + 100})), None, "no longer in the library");+ }+ #[test] fn lightroom_failure_stays_readable_without_offering_a_success_action() { let mut h = demo();@@ -707,10 +810,68 @@ mod tests { {"image": 9, "reason": "masterSkipped"}, ]}}); crate::i18n::set_language(crate::i18n::Locale::En);- let crate::state::Dialog::LightroomResult { details: en, .. } = result_dialog(&value, "done") else { panic!("not a Lightroom result") };- assert_eq!(en.len(), 4, "{en:?}");- assert_eq!(en[1], "3 catalog images were not imported because their records in the catalog are incomplete.");- assert_eq!(&en[2..], ["w1", "w2"]);+ let crate::state::Dialog::LightroomResult { details: en, .. } = result_dialog(&value, "done", &Default::default()) else {+ panic!("not a Lightroom result")+ };+ assert_eq!(en.len(), 3, "{en:?}");+ assert_eq!(en[0], "3 catalog images were not imported because their records in the catalog are incomplete.");+ assert_eq!(&en[1..], ["w1", "w2"]);+ }++ /// A report with one catalog photo per outcome (see `lightroom_catalog::apply_prepared`).+ fn every_outcome() -> Value {+ json!({"report": {"photos": 3, "collections": 2, "inCatalog": 8, "added": 2, "alreadyInLibrary": 1, "inRecentlyDeleted": 1,+ "notSupported": 2, "notSupportedTypes": {"mov": 1, "mp4": 1}, "pathRefused": 1, "incomplete": 1,+ "skipped": [{"image": 8, "reason": "missingFile"}], "missing": ["/a.ARW"], "failed": [], "warnings": []}})+ }++ /// The result window says where each of the catalog's photos went, one line per outcome there+ /// is, so an import that adds fewer photos than the catalog has explains the rest (#2589, #1584).+ #[test]+ fn the_result_lists_each_outcome_there_is() {+ crate::i18n::set_language(crate::i18n::Locale::En);+ let crate::state::Dialog::LightroomResult { details, .. } = result_dialog(&every_outcome(), "done", &Default::default()) else {+ panic!("not a Lightroom result")+ };+ assert_eq!(+ details,+ [+ "8 photos in the catalog, virtual copies included; 2 collections.",+ "2 added to the library.",+ "1 catalog photos were already in this library and were updated from the catalog.",+ "1 catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.",+ "2 catalog photos were not imported: LightCraft doesn't read their file types (MOV, MP4).",+ "1 catalog photos were not imported: their originals are on another computer or a device, or their paths in the catalog aren't valid. The warnings list them.",+ "1 catalog images were not imported because their records in the catalog are incomplete.",+ "1 originals are missing. Their catalog records are available; use Find Missing Photos to relink them.",+ ]+ );+ // an outcome no photo had gets no line+ let value =+ json!({"report": {"inCatalog": 3, "collections": 0, "added": 0, "alreadyInLibrary": 3, "inRecentlyDeleted": 0, "notSupported": 0}});+ let crate::state::Dialog::LightroomResult { details, .. } = result_dialog(&value, "done", &Default::default()) else {+ panic!("not a Lightroom result")+ };+ assert_eq!(details.len(), 2, "{details:?}");+ assert!(details[1].starts_with("3 catalog photos were already in this library"), "{details:?}");+ }++ /// The toast counts the catalog's photos and names each outcome besides the photos added only+ /// when some photo had it: "nothing happened" was "0 added" (#1584).+ #[test]+ fn the_toast_reconciles_the_catalog() {+ crate::i18n::set_language(crate::i18n::Locale::En);+ let report = json!({"inCatalog": 45091, "added": 16521, "alreadyInLibrary": 812, "inRecentlyDeleted": 0, "failed": []});+ assert_eq!(import_summary(&report), "Lightroom import complete. 45091 photos in the catalog: 16521 added, 812 already in the library");+ let report = json!({"inCatalog": 5, "added": 0, "alreadyInLibrary": 5});+ assert_eq!(import_summary(&report), "Lightroom import complete. 5 photos in the catalog: 0 added, 5 already in the library");+ let mut report = every_outcome()["report"].clone();+ report["failed"] = json!([["/b.ARW", "unreadable"]]);+ assert_eq!(+ import_summary(&report),+ "Lightroom import complete. 8 photos in the catalog: 2 added, 1 already in the library, 1 in Recently Deleted, 2 not supported, \+ 1 on refused paths, 1 incomplete, 1 failed"+ ); } #[test]@@ -718,7 +879,9 @@ mod tests { let warnings: Vec<_> = (0..10).map(|i| format!("w{i}")).collect(); let skipped: Vec<_> = (0..500).map(|i| json!({"image": i, "reason": "missingFile"})).collect(); let value = json!({"report": {"warnings": warnings, "skipped": skipped}});- let crate::state::Dialog::LightroomResult { details, .. } = result_dialog(&value, "done") else { panic!("not a Lightroom result") };+ let crate::state::Dialog::LightroomResult { details, .. } = result_dialog(&value, "done", &Default::default()) else {+ panic!("not a Lightroom result")+ }; // one line for the skipped images, then the usual eight warnings and the rest counted assert_eq!(details.len(), 10, "{details:?}"); assert!(details[0].starts_with("500 catalog images were not imported"));@@ -735,6 +898,8 @@ mod tests { collection_content: Vec::new(), warnings: vec!["warning".into()], skipped: Vec::new(),+ images: 0,+ not_supported: Default::default(), }); assert_eq!(report["photos"], 0); assert_eq!(report["collections"], 0);@@ -51,8 +51,26 @@ lightcraft-cli run --library "LightCraft Library" library.importLightroom path=" The import itself (`library.importLightroom`, `lightcraft-cli run`) adds records to the currently open LightCraft library; only the desktop window above can make a new library for it first. The desktop result stays open until dismissed and-shows the destination library, the number of records added, missing originals and warnings.-**Show in Library** opens All Photos, clears filters and selects an imported record. Errors stay+shows the destination library, where each of the catalog's photos went, missing originals and warnings.++**Where the photos went** (issues #2589, #1584). The report accounts for every image of the catalog, once:+`inCatalog` is the catalog's image count (its `Adobe_images` rows: masters and virtual copies, as Lightroom's All+Photographs counts them), and these add up to it:++| Report key | Catalog photos that… |+|---|---|+| `added` | became new library records (offline originals and virtual copies included), or whose record this import's file import made, or browsed Local photos that joined the library |+| `alreadyInLibrary` | matched a library photo (by path, or by the import index on reimport); the record is updated in place |+| `inRecentlyDeleted` | matched a library photo in Recently Deleted; it stays there |+| `notSupported` | aren't a file type LightCraft reads (videos, for one), with their virtual copies; `notSupportedTypes` counts them by extension |+| `pathRefused` | have an original the import doesn't look at (below), with their virtual copies; no record is made |+| `incomplete` | have incomplete catalog records (`skipped`) |++Should the counts not add up, a warning says by how much. `imported` (records added, as before) stays in the report+and normally equals `added`. The toast says, for example, "45091 photos in the catalog: 16521 added, 812 already in+the library" (only the outcomes some photo had, then failures); the result window has one line per outcome.+**Show in Library** opens All Photos, clears filters and selects an imported record (not one left in Recently+Deleted; the button is left out when every record is there). Errors stay readable in the same result window. If another dialog is open, the result waits until that dialog closes. The result is also available through `library.importLightroom {"status":true}` during that session. @@ -146,8 +164,10 @@ Original and root folder paths are resolved with the catalog as the document and only when they are on this computer or on the network share the catalog itself is on (`crates/engine/src/doc_path.rs`). An original on another computer (`\\server\share\…`, `//server/…`, `file://server/…`), a device path, or one whose folder or file name in the catalog is-an absolute path is not looked at: the import lists it as missing with a warning and leaves the-photo and its virtual copies out of the library. To import originals on a share, open the catalog+an absolute path is not looked at: the import leaves the photo and its virtual copies out of the library, with a+warning naming it, and counts them as `pathRefused`. They get no record because a record would have the app look at+that path later (previews, Find Missing Photos), which is what the refusal prevents; so they are not listed as missing+either. To import originals on a share, open the catalog from that share. A path that is not a path on this computer (a Windows drive path read on macOS or Linux) is not looked at either and stays catalogued as missing. @@ -1,5 +1,16 @@ # What's new in LightCraft +## 2026-10-27++### A Lightroom catalog import says where every photo went+- When a catalog import finishes, the message counts the catalog's photos (virtual copies included) and how many+ were added, and also how many were already in the library, are in Recently Deleted, aren't a file type LightCraft+ reads (videos, for one), have paths it doesn't open, or have incomplete catalog records, when there are any. The+ result window gives each of these its own line, and the lines add up to the catalog's photos. Before, it gave+ only the photos added, so an import of a large catalog seemed to lose photos, and re-importing one already in+ the library seemed to do nothing. Photos on another computer or a device are no longer reported as missing+ originals for Find Missing Photos, which had no record to relink them to.+ ## 2026-10-24 ### Relative-date filters and smart albums follow the clock