mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 05:57:09 -06:00
Issue #2589: a Lightroom import never creates a photo already in Recently Deleted
A catalog photo whose file matched a photo in Recently Deleted by content was cloned with the trashed record's `deleted` flag (and its virtual-copy fields), so it landed in Recently Deleted while the report counted it as imported. A new virtual copy of a trashed master did the same. Both are now live records, as Create Virtual Copy already does. Catalog photos matched by path to a trashed photo stay there (the Lightroom import's OnDeleted::Skip, #706); the report counts them as `inRecentlyDeleted` and the result window says so in one line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b2d89895ad
commit
de9b0a8951
@@ -956,6 +956,9 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co
|
||||
let mut promoted = HashSet::new();
|
||||
let mut unmapped = BTreeMap::new();
|
||||
let mut preserved = 0;
|
||||
// Library photos in Recently Deleted that a catalog photo matched: they stay there (only an
|
||||
// import of the files themselves brings a photo back, see `OnDeleted`), and the report says so.
|
||||
let mut in_trash = HashSet::new();
|
||||
// Masters precede their virtual copies.
|
||||
let mut photos: Vec<_> = data.photos.iter().collect();
|
||||
photos.sort_by_key(|p| number(&p.image, "masterImage") != 0);
|
||||
@@ -980,6 +983,9 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co
|
||||
.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 p = if let Some(id) = existing_id.and_then(|id| s.catalog.photo(id)) {
|
||||
if id.deleted {
|
||||
in_trash.insert(id.id);
|
||||
}
|
||||
(**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)))?;
|
||||
@@ -991,12 +997,21 @@ 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)) {
|
||||
// Catalogs can contain distinct originals with identical bytes and independent edits.
|
||||
// The clone is a new record of its own: never in Recently Deleted because the photo with
|
||||
// the same bytes is (#2589), nor a virtual copy because that photo is one.
|
||||
let mut p = (**duplicate).clone();
|
||||
p.id = s.catalog.alloc_photo_id();
|
||||
p.source = Source::File { path: src.path.clone() };
|
||||
p.deleted = false;
|
||||
p.copy_of = None;
|
||||
p.copy_name = None;
|
||||
p.local_baseline = None;
|
||||
p.imported = now.clone();
|
||||
p.file_name = Path::new(&src.path).file_name().map(|n| n.to_string_lossy().into()).unwrap_or_else(|| src.path.clone());
|
||||
p
|
||||
} else {
|
||||
@@ -1105,7 +1120,7 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co
|
||||
}
|
||||
s.merge_undo(s.undo.len().saturating_sub(undo0), "Import Lightroom Catalog");
|
||||
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":album_count,"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":album_count,"preservedExistingEdits":preserved,"inRecentlyDeleted":in_trash.len(),"missing":missing,"failed":files.failed,"warnings":warnings,"skipped":data.skipped,"unmapped":unmapped,"archive":archive_path,"mapping":ids}),
|
||||
index: index.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -157,6 +157,82 @@ 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", edit);
|
||||
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",
|
||||
}],
|
||||
..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_eq!(s.catalog.photo(other).unwrap().deleted, case == "trashed", "{case}: the other photo is left as it was");
|
||||
}
|
||||
}
|
||||
/// 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}");
|
||||
}
|
||||
#[test]
|
||||
fn preserves_existing_edits_and_rejects_invalid_structure_before_mutation() {
|
||||
let mut s = crate::Session::new();
|
||||
|
||||
@@ -267,6 +267,7 @@
|
||||
"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.",
|
||||
"{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.",
|
||||
|
||||
@@ -269,6 +269,7 @@
|
||||
"{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.",
|
||||
"{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.",
|
||||
|
||||
@@ -269,6 +269,7 @@
|
||||
"{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.",
|
||||
"{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.",
|
||||
|
||||
@@ -257,6 +257,7 @@
|
||||
"{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.",
|
||||
"{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",
|
||||
|
||||
@@ -269,6 +269,7 @@
|
||||
"{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.",
|
||||
"{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.",
|
||||
|
||||
@@ -296,6 +296,7 @@
|
||||
"“{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.",
|
||||
"{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}",
|
||||
|
||||
@@ -250,6 +250,7 @@
|
||||
"{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.",
|
||||
"{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})",
|
||||
|
||||
@@ -273,6 +273,7 @@
|
||||
"{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} 枚は、このライブラリの「最近削除した写真」にあり、そのまま残ります。表示するには「最近削除した写真」から復元してください。",
|
||||
"{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}件のオリジナルが見つかりません。カタログのレコードは利用できます。「見つからない写真を検索」で再リンクしてください。",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"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}. ისინი იქ რჩება; სანახავად აღადგინეთ ისინი ახლახან წაშლილებიდან.",
|
||||
"{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",
|
||||
|
||||
@@ -261,6 +261,7 @@
|
||||
"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ć.",
|
||||
"{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ć.",
|
||||
|
||||
@@ -273,6 +273,7 @@
|
||||
"{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.",
|
||||
"{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.",
|
||||
|
||||
@@ -269,6 +269,7 @@
|
||||
"{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}. Они остаются там; чтобы увидеть их, восстановите их из «Недавно удалённых».",
|
||||
"{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}. Их записи каталога доступны; используйте поиск отсутствующих фотографий, чтобы восстановить ссылки.",
|
||||
|
||||
@@ -296,6 +296,7 @@
|
||||
"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.",
|
||||
"{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}",
|
||||
|
||||
@@ -269,6 +269,7 @@
|
||||
"{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}. Вони залишаються там; щоб побачити їх, відновіть їх із нещодавно видалених.",
|
||||
"{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}. Їхні записи каталогу доступні; скористайтеся пошуком відсутніх фотографій, щоб відновити посилання.",
|
||||
|
||||
@@ -257,6 +257,7 @@
|
||||
"{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.",
|
||||
"{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",
|
||||
|
||||
@@ -273,6 +273,7 @@
|
||||
"{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} 张照片位于此图库的“最近删除”中,并将保留在那里;如需查看,请从“最近删除”中恢复。",
|
||||
"{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} 个原始文件缺失。其目录记录可用;请使用“查找缺失照片”重新链接文件。",
|
||||
|
||||
@@ -273,6 +273,7 @@
|
||||
"{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} 張照片位於此圖庫的「最近刪除」中,並會保留在那裡;如需檢視,請從「最近刪除」中復原。",
|
||||
"{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} 個原始檔案遺失。其目錄記錄可用;請使用「尋找遺失相片」重新連結檔案。",
|
||||
|
||||
@@ -340,6 +340,13 @@ fn result_dialog(value: &Value, summary: &str) -> crate::state::Dialog {
|
||||
missing = missing
|
||||
));
|
||||
}
|
||||
let in_trash = report["inRecentlyDeleted"].as_u64().unwrap_or(0);
|
||||
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
|
||||
));
|
||||
}
|
||||
// images whose catalog records are incomplete come as data (`skipped`), said here in the UI
|
||||
// language; their ids are in the report for the CLI and agents
|
||||
let skipped = report["skipped"].as_array().map_or(0, Vec::len);
|
||||
@@ -713,6 +720,18 @@ mod tests {
|
||||
assert_eq!(&en[2..], ["w1", "w2"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn photos_left_in_recently_deleted_are_one_line() {
|
||||
let value = json!({"report": {"photos": 3, "collections": 0, "inRecentlyDeleted": 2}});
|
||||
crate::i18n::set_language(crate::i18n::Locale::En);
|
||||
let crate::state::Dialog::LightroomResult { details, .. } = result_dialog(&value, "done") else { panic!("not a Lightroom result") };
|
||||
assert_eq!(details.len(), 2, "{details:?}");
|
||||
assert!(details[1].starts_with("2 catalog photos are in this library's Recently Deleted"), "{details:?}");
|
||||
let value = json!({"report": {"photos": 3, "collections": 0, "inRecentlyDeleted": 0}});
|
||||
let crate::state::Dialog::LightroomResult { details, .. } = result_dialog(&value, "done") else { panic!("not a Lightroom result") };
|
||||
assert_eq!(details.len(), 1, "no line when none are: {details:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skipped_images_do_not_take_the_warning_slots() {
|
||||
let warnings: Vec<_> = (0..10).map(|i| format!("w{i}")).collect();
|
||||
|
||||
Reference in New Issue
Block a user