strom-research 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -259,6 +259,7 @@
259
259
  "story.why": "{count} údaje ze zápisů vyprávějí život (generace {generation} výzkumu {research}); rodina si ho přečte v rodinné knize aplikace Strom",
260
260
  "story.done": "strom story set {id} se všemi údaji, o které se opírá (--fact) – koncept, dokud ho uživatel neschválí",
261
261
  "review.research": "Revize: {name}",
262
+ "review.unproven.research": "Osoby bez vlastního záznamu",
262
263
  "review.more": " (a po nich dalších {count})",
263
264
  "review.mentions.what": "Co už rodokmen říká o osobě {name} mimo její údaje: {list}{more}",
264
265
  "review.mentions.why": "prameny, poznámky nebo deník osobu {name} jmenují, ale v jejích údajích to není – nejlevnější nálezy revize ({research})",
@@ -290,6 +291,18 @@
290
291
  "ui.review.pick": "Kterého?",
291
292
  "ui.review.family": "I s manželem či manželkou a dětmi?",
292
293
  "ui.review.run": "Nechat na tom agenta pracovat hned, samostatně? (úkolů: {n})",
294
+ "ui.menu.unproven": "Osoby bez vlastního záznamu ({n})",
295
+ "ui.unproven.title": "Tyto osoby zatím nedokládá žádný jejich vlastní záznam:",
296
+ "ui.unproven.named": "jmenuje je jiný záznam",
297
+ "ui.unproven.leads": "jen vodítka",
298
+ "ui.unproven.none": "bez záznamu",
299
+ "ui.unproven.tasks": "už se na nich pracuje",
300
+ "ui.unproven.living": "Nejspíš žijící (narození před méně než {years} lety) — to ví rodina, bez úkolů: {names}",
301
+ "ui.unproven.how": "Co má agent udělat?",
302
+ "ui.unproven.all": "Všechny, postupně (vždy {n} najednou)",
303
+ "ui.unproven.pick": "Jen ty, které vyberu",
304
+ "ui.unproven.numbers": "Jejich čísla ze seznamu (např. 1 3 5-7, nebo jejich ID; Enter nebo 0 zpět)",
305
+ "ui.unproven.bad": "V seznamu není: {x}",
293
306
  "ui.o.reread": "druhé čtení",
294
307
  "ui.o.reread.offer": "{n} údajů stojí jen na čtení jiným modelem ({models}); uživatel teď čte modelem {model} – jednou to nabídni, jednou větou: strom review <koho> --reread (úkol na osobu; rozhodne uživatel)",
295
308
  "ui.o.stories": "vyprávění",
@@ -327,6 +340,7 @@
327
340
  "ui.update.downloading": "Stahuji strom {version}…",
328
341
  "ui.update.failed": "Aktualizace se nepovedla: {detail}. strom zůstává, jak byl.",
329
342
  "ui.update.done": "✓ strom je aktualizovaný: {previous} → {version}.",
343
+ "ui.update.restart": "strom je aktualizovaný — tahle nabídka ale ještě běží ve staré verzi. Spusťte strom znovu (nebo zástupce „{shortcut}“) a poběží nová verze.",
330
344
  "ui.o.update": "aktualizace",
331
345
  "ui.o.update.new": "vyšla {version} (tato je {current}) – řekni to uživateli; strom update ji s jeho souhlasem nainstaluje",
332
346
  "ui.app.drag": "Aplikace Strom se otevřela. Přetáhněte do jejího okna soubor {file}{shown} – poprvé z něj bude nový rodokmen, příště se tentýž rodokmen aktualizuje. S Chrome nebo Edge na tomto počítači ho strom předá sám.",
@@ -447,6 +461,60 @@
447
461
  "ui.browse.plan": "Co agent udělá dál",
448
462
  "ui.review.new": "{name} — nových úkolů: {n}",
449
463
  "ui.review.nothing": "{name} — nic nového k revizi",
464
+ "ui.sync.title": "{file}: změn proti výzkumu: {n}",
465
+ "ui.sync.base": "Porovnáno s tím, co výzkum dal aplikaci Strom ({head}): za vaši úpravu se počítá jen to, co se od té doby změnilo.",
466
+ "ui.sync.nobase": "Soubor neříká, ze které verze výzkumu je: přidání se berou, rozdíly se jen ukážou (vyberete je přes --only).",
467
+ "ui.sync.headmissing": "Verzi výzkumu {head}, ze které soubor je, historie nemá: přidání se berou, rozdíly se jen ukážou (vyberete je přes --only).",
468
+ "ui.sync.partial": "Soubor má mnohem méně osob, než kolik dostal: nic se nebere jako smazané.",
469
+ "ui.sync.strangers": "Tato ID nepatří osobám toho jména ve výzkumu — berou se jako nové osoby: {ids}",
470
+ "ui.sync.nothing": "{file}: nic nového — výzkum má všechno, co v něm je.",
471
+ "ui.sync.person.new": "{n}. nová osoba: {name}",
472
+ "ui.sync.fact.new": "{n}. {who}: nové — {fact}",
473
+ "ui.sync.fact.changed": "{n}. {who}: {kind} změněno — {was} → {now}",
474
+ "ui.sync.fact.differs": "{n}. {who}: {kind} se liší — výzkum {was}, soubor {now}",
475
+ "ui.sync.fact.gone": "{n}. {who}: už v něm není — {fact}",
476
+ "ui.sync.name.new": "{n}. {who}: další jméno — {text}",
477
+ "ui.sync.sex.changed": "{n}. {who}: pohlaví {text}",
478
+ "ui.sync.note.new": "{n}. {who}: poznámka — „{text}“",
479
+ "ui.sync.family.new": "{n}. nová rodina: {who}{kids}",
480
+ "ui.sync.child.new": "{n}. {who}: dítě — {child}",
481
+ "ui.sync.child.gone": "{n}. {who}: už to není jejich dítě — {child}",
482
+ "ui.sync.person.gone": "{n}. už v něm není: {name}",
483
+ "ui.sync.do.add": "přidá se jako vodítko",
484
+ "ui.sync.do.correct": "opraví vodítko výzkumu",
485
+ "ui.sync.do.conflict": "rozpor k rozhodnutí (výzkum to má ze záznamu)",
486
+ "ui.sync.do.user": "platí vaše úprava (údaj ze záznamu se stáhne s důvodem)",
487
+ "ui.sync.do.pick": "jen když ho vyberete (--only)",
488
+ "ui.sync.do.report": "nic se nemění — řekněte agentovi, co jste tím mysleli",
489
+ "ui.sync.edits": "Vaše úpravy údajů doložených záznamem: {mode} (strom config set sync.edits conflict|user)",
490
+ "ui.sync.mode.conflict": "rozpor k rozhodnutí",
491
+ "ui.sync.mode.user": "platí vaše úprava",
492
+ "ui.sync.next": "Zapsat: strom sync {file} --apply · jen některé: --apply --only 1,3",
493
+ "ui.sync.done": "Zapsáno změn z {file}: {n} ({input}) — vrátit: strom sync undo {input}",
494
+ "ui.sync.undone": "Synchronizace {input} vrácena: kroků {n}.",
495
+ "ui.sync.wasundone": "Synchronizace {input} už vrácená je.",
496
+ "ui.sync.bridge.large": "strom je na poslání moc velký — vyexportujte ho jako GEDCOM a načtěte ho ve výzkumu (strom sync <soubor>)",
497
+ "ui.sync.bridge.other": "tento strom patří jinému výzkumu",
498
+ "ui.sync.bridge.foreign": "tohle nevypadá jako rodokmen tohoto výzkumu — vyexportujte ho a načtěte ve výzkumu jako jiný rodokmen",
499
+ "ui.sync.bridge.empty": "strom přišel prázdný nebo nečitelný",
500
+ "ui.sync.wait": "Otevírá se aplikace Strom s tímto výzkumem — vyberte v ní, který strom poslat, a potvrďte. Čekám na něj (nejvýš {min} min; Ctrl+C přeruší) …",
501
+ "ui.sync.waited": "Aplikace Strom strom do {min} min neposlala — zkuste to znovu: strom sync --app, nebo ho vyexportujte jako GEDCOM: strom sync <soubor>",
502
+ "ui.sync.got": "Aplikace Strom strom poslala.",
503
+ "ui.sync.nothing.unchanged": "Aplikace Strom nic neposlala: strom se od posledního načtení z výzkumu nezměnil.",
504
+ "ui.sync.nothing.cancelled": "Z aplikace Strom se nic neposlalo (zrušeno v ní).",
505
+ "ui.sync.nothing.no-tree": "Aplikace Strom strom tohoto výzkumu nemá — nejdřív ho v ní otevřete (v menu Aplikace Strom), nebo strom vyexportujte a načtěte soubor.",
506
+ "ui.sync.nosend": "Tato verze aplikace Strom strom ještě poslat neumí (přijde s novější verzí) — vyexportujte ho v aplikaci jako GEDCOM a načtěte: strom sync <soubor>",
507
+ "ui.sync.from": "Odkud?",
508
+ "ui.sync.from.app": "Přímo z aplikace Strom (pošle strom sama)",
509
+ "ui.sync.from.file": "Ze souboru (.ged, .json)",
510
+ "ui.sync.again": "Tento soubor výzkum už načetl ({input}) — znovu: --again",
511
+ "ui.sync.source.app": "Úpravy v aplikaci Strom ({file}, {day})",
512
+ "ui.sync.source.file": "Rodokmen {file} ({day})",
513
+ "ui.review.living": "{who}: nejspíš žijící (narození před méně než {years} lety, úmrtí nezapsané). Víc ví rodina než matriky a ty z posledních let bývají uzavřené. Přesto revidovat?",
514
+ "ui.review.living.no": "Nic se nepřidalo.",
515
+ "ui.review.unproven.none": "Každý ve stromu stojí na vlastním záznamu — nebo pro ty ostatní strom už nemá co navrhnout (strom person list --unproven).",
516
+ "ui.review.unproven.left": "dalších {n} bez vlastního záznamu: strom review --unproven znovu vezme další, až budou tihle hotoví",
517
+ "ui.review.unproven.left.human": "Dalších {n} osob čeká bez vlastního záznamu — přijdou na řadu, až budou tihle hotoví.",
450
518
  "ui.review.would": " přidal by: {level} · {what}",
451
519
  "ui.review.dry": "(zkouška nanečisto — nic se nezapsalo)",
452
520
  "ui.review.covered": " {name}: už to pokrývá {task} ({state}) — {what}",
@@ -506,10 +574,19 @@
506
574
  "ui.consent.hook.on": "Nechat hook „{name}“, aby se dozvídal všechno, co se uloží do vašich výzkumů? Je to program na tomto počítači a udělá s tím, k čemu byl napsán.",
507
575
  "ui.consent.hook.off": "Přestat hooku „{name}“ hlásit, co se ukládá?",
508
576
  "ui.run.others": "⚠ V tomto rodokmenu už pracuje jiný běh (od {since}): tento si vezme jiné úkoly – náklady se sčítají.",
509
- "ui.menu.more": "Rozšířit výzkum: podklady, nový směr, revize osoby",
577
+ "ui.menu.more": "Rozšířit výzkum: podklady, nový směr, revize osoby, jiný rodokmen",
578
+ "ui.menu.more.app": "Rozšířit výzkum: podklady, nový směr, revize osoby, úpravy z aplikace Strom",
510
579
  "ui.more.title": "Rozšířit výzkum",
511
580
  "ui.more.intake": "Přidat podklady: dokumenty, fotky, rodokmen z jiného programu, co víte",
512
581
  "ui.more.research": "Nový směr výzkumu: předkové nebo potomci někoho dalšího, nebo jedna otázka",
582
+ "ui.more.sync": "Načíst úpravy z aplikace Strom nebo z jiného rodokmenu (.ged, .json)",
583
+ "ui.more.sync.noapp": "Načíst změny z jiného rodokmenu (.ged, .json)",
584
+ "ui.sync.path.noapp": "Přetáhněte sem soubor s rodokmenem (.ged nebo .json), nebo napište cestu (Enter nebo 0 zpět)",
585
+ "ui.sync.path": "Přetáhněte sem soubor vyexportovaný z aplikace Strom (.ged nebo .json), nebo napište cestu (Enter nebo 0 zpět)",
586
+ "ui.sync.how": "Zapsat to do výzkumu?",
587
+ "ui.sync.all": "Ano, všechno, jak je vypsané",
588
+ "ui.sync.some": "Jen ty, které vyberu",
589
+ "ui.sync.app": "Otevřete výzkum znovu v aplikaci Strom (v menu Aplikace Strom): zeptá se, jestli strom aktualizovat — pak v něm budou vaše úpravy tak, jak je výzkum přijal.",
513
590
  "ui.intake.what": "Co přinášíte?",
514
591
  "ui.intake.files": "Soubory nebo složku z počítače",
515
592
  "ui.intake.text": "Co víte – jména, data, místa, rodinné vzpomínky",
@@ -259,6 +259,7 @@
259
259
  "story.why": "{count} Angaben aus Einträgen erzählen das Leben (Generation {generation} der Forschung {research}); die Familie liest es im Familienbuch der Strom-App",
260
260
  "story.done": "strom story set {id} mit jeder Angabe, auf der sie beruht (--fact) – ein Entwurf, bis der Benutzer sie freigibt",
261
261
  "review.research": "Durchsicht: {name}",
262
+ "review.unproven.research": "Personen ohne eigenen Eintrag",
262
263
  "review.more": " (und danach {count} weitere)",
263
264
  "review.mentions.what": "Was der Stammbaum außerhalb der Angaben über {name} schon sagt: {list}{more}",
264
265
  "review.mentions.why": "Quellen, Notizen oder das Tagebuch nennen {name}, die Angaben zeigen es aber nicht – die günstigsten Funde einer Durchsicht ({research})",
@@ -290,6 +291,18 @@
290
291
  "ui.review.pick": "Welche Person?",
291
292
  "ui.review.family": "Mit Ehepartner und Kindern?",
292
293
  "ui.review.run": "Den Agenten jetzt allein daran arbeiten lassen? ({n} Aufgaben)",
294
+ "ui.menu.unproven": "Personen ohne eigenen Eintrag ({n})",
295
+ "ui.unproven.title": "Diese Personen belegt noch kein eigener Eintrag:",
296
+ "ui.unproven.named": "in einem anderen Eintrag genannt",
297
+ "ui.unproven.leads": "nur Hinweise",
298
+ "ui.unproven.none": "kein Eintrag",
299
+ "ui.unproven.tasks": "daran wird schon gearbeitet",
300
+ "ui.unproven.living": "Wahrscheinlich lebend (vor weniger als {years} Jahren geboren) — das weiß die Familie, keine Aufgaben: {names}",
301
+ "ui.unproven.how": "Was soll der Agent tun?",
302
+ "ui.unproven.all": "Alle, nach und nach (jeweils {n})",
303
+ "ui.unproven.pick": "Nur die, die ich auswähle",
304
+ "ui.unproven.numbers": "Ihre Nummern aus der Liste (z. B. 1 3 5-7, oder ihre IDs; Enter oder 0 zurück)",
305
+ "ui.unproven.bad": "Nicht in der Liste: {x}",
293
306
  "ui.o.reread": "zweite Lesung",
294
307
  "ui.o.reread.offer": "{n} Angaben beruhen nur auf Lesungen eines anderen Modells ({models}); der Nutzer liest jetzt mit {model} – biete es einmal an, in einem Satz: strom review <wen> --reread (eine Aufgabe je Person; der Nutzer entscheidet)",
295
308
  "ui.o.stories": "Geschichten",
@@ -327,6 +340,7 @@
327
340
  "ui.update.downloading": "strom {version} wird heruntergeladen…",
328
341
  "ui.update.failed": "Die Aktualisierung hat nicht geklappt: {detail}. strom bleibt, wie er war.",
329
342
  "ui.update.done": "✓ strom ist aktualisiert: {previous} → {version}.",
343
+ "ui.update.restart": "strom ist aktualisiert — dieses Menü läuft aber noch in der alten Version. Starten Sie strom neu (oder die Verknüpfung „{shortcut}“), dann läuft die neue Version.",
330
344
  "ui.o.update": "Update",
331
345
  "ui.o.update.new": "{version} ist da (diese ist {current}) – sag es dem Benutzer; strom update installiert sie mit seiner Zustimmung",
332
346
  "ui.app.drag": "Die Strom-App ist geöffnet. Ziehen Sie die Datei {file}{shown} in ihr Fenster – beim ersten Mal wird daraus ein neuer Stammbaum, danach wird derselbe Stammbaum aktualisiert. Mit Chrome oder Edge auf diesem Computer übergibt strom sie selbst.",
@@ -447,6 +461,60 @@
447
461
  "ui.browse.plan": "Was der Agent als Nächstes tut",
448
462
  "ui.review.new": "{name} — neue Aufgaben: {n}",
449
463
  "ui.review.nothing": "{name} — nichts Neues zu prüfen",
464
+ "ui.sync.title": "{file}: {n} Änderung(en) gegenüber der Forschung",
465
+ "ui.sync.base": "Verglichen mit dem, was die Forschung der Strom-App gab ({head}): nur was seitdem geändert wurde, gilt als Ihre Änderung.",
466
+ "ui.sync.nobase": "Die Datei sagt nicht, aus welchem Stand der Forschung sie stammt: Ergänzungen werden übernommen, Unterschiede nur gezeigt (auswählen mit --only).",
467
+ "ui.sync.headmissing": "Den Stand {head}, den die Datei nennt, hat die Geschichte der Forschung nicht: Ergänzungen werden übernommen, Unterschiede nur gezeigt (auswählen mit --only).",
468
+ "ui.sync.partial": "Die Datei hat viel weniger Personen, als sie bekam: nichts gilt als gelöscht.",
469
+ "ui.sync.strangers": "Diese IDs gehören nicht den Personen dieses Namens in der Forschung — als neue Personen übernommen: {ids}",
470
+ "ui.sync.nothing": "{file}: nichts Neues — die Forschung hat alles, was darin steht.",
471
+ "ui.sync.person.new": "{n}. neue Person: {name}",
472
+ "ui.sync.fact.new": "{n}. {who}: neu — {fact}",
473
+ "ui.sync.fact.changed": "{n}. {who}: {kind} geändert — {was} → {now}",
474
+ "ui.sync.fact.differs": "{n}. {who}: {kind} weicht ab — Forschung {was}, Datei {now}",
475
+ "ui.sync.fact.gone": "{n}. {who}: nicht mehr darin — {fact}",
476
+ "ui.sync.name.new": "{n}. {who}: ein weiterer Name — {text}",
477
+ "ui.sync.sex.changed": "{n}. {who}: Geschlecht {text}",
478
+ "ui.sync.note.new": "{n}. {who}: eine Notiz — „{text}“",
479
+ "ui.sync.family.new": "{n}. eine neue Familie: {who}{kids}",
480
+ "ui.sync.child.new": "{n}. {who}: ein Kind — {child}",
481
+ "ui.sync.child.gone": "{n}. {who}: nicht mehr ihr Kind — {child}",
482
+ "ui.sync.person.gone": "{n}. nicht mehr darin: {name}",
483
+ "ui.sync.do.add": "als Hinweis übernommen",
484
+ "ui.sync.do.correct": "berichtigt den Hinweis der Forschung",
485
+ "ui.sync.do.conflict": "ein Widerspruch zum Entscheiden (die Forschung hat es aus einem Eintrag)",
486
+ "ui.sync.do.user": "Ihre Änderung gilt (die Angabe aus dem Eintrag wird mit Grund zurückgezogen)",
487
+ "ui.sync.do.pick": "nur wenn Sie sie auswählen (--only)",
488
+ "ui.sync.do.report": "nichts ändert sich — sagen Sie dem Agenten, was Sie meinten",
489
+ "ui.sync.edits": "Ihre Änderungen an Angaben, die ein Eintrag belegt: {mode} (strom config set sync.edits conflict|user)",
490
+ "ui.sync.mode.conflict": "ein Widerspruch zum Entscheiden",
491
+ "ui.sync.mode.user": "Ihre Änderung gilt",
492
+ "ui.sync.next": "Schreiben: strom sync {file} --apply · nur einige: --apply --only 1,3",
493
+ "ui.sync.done": "{n} Änderung(en) aus {file} geschrieben ({input}) — zurücknehmen: strom sync undo {input}",
494
+ "ui.sync.undone": "Abgleich {input} zurückgenommen: {n} Schritt(e).",
495
+ "ui.sync.wasundone": "Abgleich {input} ist schon zurückgenommen.",
496
+ "ui.sync.bridge.large": "der Stammbaum ist zu groß zum Senden — exportieren Sie ihn als GEDCOM und laden Sie ihn in der Forschung (strom sync <Datei>)",
497
+ "ui.sync.bridge.other": "dieser Stammbaum gehört zu einer anderen Forschung",
498
+ "ui.sync.bridge.foreign": "das sieht nicht wie der Stammbaum dieser Forschung aus — exportieren Sie ihn und laden Sie ihn als anderen Stammbaum",
499
+ "ui.sync.bridge.empty": "der Stammbaum kam leer oder unlesbar an",
500
+ "ui.sync.wait": "Die Strom-App öffnet sich mit dieser Forschung — wählen Sie dort, welcher Stammbaum gesendet wird, und bestätigen Sie. Warte darauf (höchstens {min} Min.; Strg+C bricht ab) …",
501
+ "ui.sync.waited": "Die Strom-App hat den Stammbaum nicht innerhalb von {min} Min. gesendet — erneut: strom sync --app, oder als GEDCOM exportieren: strom sync <Datei>",
502
+ "ui.sync.got": "Die Strom-App hat den Stammbaum gesendet.",
503
+ "ui.sync.nothing.unchanged": "Die Strom-App sendet nichts: der Stammbaum hat sich seit dem letzten Laden aus der Forschung nicht geändert.",
504
+ "ui.sync.nothing.cancelled": "Aus der Strom-App wurde nichts gesendet (dort abgebrochen).",
505
+ "ui.sync.nothing.no-tree": "Die Strom-App hat keinen Stammbaum dieser Forschung — öffnen Sie ihn dort zuerst (Menüpunkt Strom-App) oder exportieren Sie ihn und laden die Datei.",
506
+ "ui.sync.nosend": "Diese Version der Strom-App kann den Stammbaum noch nicht senden (kommt mit einer neueren Version) — exportieren Sie ihn in der App als GEDCOM und laden Sie ihn: strom sync <Datei>",
507
+ "ui.sync.from": "Woher?",
508
+ "ui.sync.from.app": "Direkt aus der Strom-App (sie sendet den Stammbaum selbst)",
509
+ "ui.sync.from.file": "Aus einer Datei (.ged, .json)",
510
+ "ui.sync.again": "Diese Datei hat die Forschung schon übernommen ({input}) — noch einmal: --again",
511
+ "ui.sync.source.app": "Änderungen in der Strom-App ({file}, {day})",
512
+ "ui.sync.source.file": "Stammbaum {file} ({day})",
513
+ "ui.review.living": "{who}: wahrscheinlich lebend (vor weniger als {years} Jahren geboren, kein Tod eingetragen). Die Familie weiß mehr als die Register, und die der letzten Jahrzehnte sind meist gesperrt. Trotzdem durchsehen?",
514
+ "ui.review.living.no": "Nichts hinzugefügt.",
515
+ "ui.review.unproven.none": "Jede Person im Stammbaum stützt sich auf einen eigenen Eintrag — oder strom hat für die übrigen nichts mehr vorzuschlagen (strom person list --unproven).",
516
+ "ui.review.unproven.left": "{n} weitere ohne eigenen Eintrag: strom review --unproven nimmt erneut die nächsten, wenn diese erledigt sind",
517
+ "ui.review.unproven.left.human": "{n} weitere Personen warten ohne eigenen Eintrag — sie kommen dran, wenn diese erledigt sind.",
450
518
  "ui.review.would": " würde hinzufügen: {level} · {what}",
451
519
  "ui.review.dry": "(Probelauf — nichts geschrieben)",
452
520
  "ui.review.covered": " {name}: schon abgedeckt durch {task} ({state}) — {what}",
@@ -506,10 +574,19 @@
506
574
  "ui.consent.hook.on": "Den Hook „{name}“ über alles informieren, was in Ihre Forschungen gespeichert wird? Er ist ein Programm auf diesem Computer und tut damit, wofür er geschrieben wurde.",
507
575
  "ui.consent.hook.off": "Dem Hook „{name}“ nicht mehr melden, was gespeichert wird?",
508
576
  "ui.run.others": "⚠ In diesem Stammbaum arbeitet schon ein anderer Lauf (seit {since}): dieser nimmt andere Aufgaben – die Kosten addieren sich.",
509
- "ui.menu.more": "Die Forschung erweitern: Unterlagen, eine neue Richtung, eine Person prüfen",
577
+ "ui.menu.more": "Die Forschung erweitern: Unterlagen, eine neue Richtung, eine Person prüfen, ein anderer Stammbaum",
578
+ "ui.menu.more.app": "Die Forschung erweitern: Unterlagen, eine neue Richtung, eine Person prüfen, Änderungen aus der Strom-App",
510
579
  "ui.more.title": "Die Forschung erweitern",
511
580
  "ui.more.intake": "Unterlagen der Familie: Dokumente, Fotos, ein Stammbaum aus einem anderen Programm, was Sie wissen",
512
581
  "ui.more.research": "Eine neue Richtung: die Vorfahren oder Nachkommen einer weiteren Person, oder eine Frage",
582
+ "ui.more.sync": "Änderungen aus der Strom-App oder einem anderen Stammbaum übernehmen (.ged, .json)",
583
+ "ui.more.sync.noapp": "Änderungen aus einem anderen Stammbaum übernehmen (.ged, .json)",
584
+ "ui.sync.path.noapp": "Ziehen Sie die Stammbaum-Datei (.ged oder .json) hierher oder geben Sie den Pfad ein (Enter oder 0 zurück)",
585
+ "ui.sync.path": "Ziehen Sie die aus der Strom-App exportierte Datei (.ged oder .json) hierher oder geben Sie den Pfad ein (Enter oder 0 zurück)",
586
+ "ui.sync.how": "In die Forschung schreiben?",
587
+ "ui.sync.all": "Ja, alles wie angezeigt",
588
+ "ui.sync.some": "Nur die, die ich auswähle",
589
+ "ui.sync.app": "Öffnen Sie die Forschung erneut in der Strom-App (Menüpunkt Strom-App): Sie fragt, ob der Stammbaum aktualisiert werden soll — dann enthält er Ihre Änderungen, wie die Forschung sie übernommen hat.",
513
590
  "ui.intake.what": "Was bringen Sie mit?",
514
591
  "ui.intake.files": "Dateien oder einen Ordner von diesem Computer",
515
592
  "ui.intake.text": "Was Sie wissen – Namen, Daten, Orte, Erinnerungen der Familie",
@@ -87,7 +87,14 @@ they ask.
87
87
  about her", after a new model): \`strom review <whom>\` — strom proposes
88
88
  the work as tasks (what the tree already says of them elsewhere, entries to
89
89
  read whole, facts to check); tell them how many and what a session costs,
90
- and they choose: here with you, or the agent alone.
90
+ and they choose: here with you, or the agent alone. Several people:
91
+ \`strom review P… P…\`; those no record of their own proves ("who has no
92
+ source?"): \`strom person list --unproven\`, then \`strom review --unproven\`
93
+ (a batch at a time).
94
+ - **Their edits in the Strom app** ("I fixed grandma's birthday in the
95
+ app", a .ged or .json they exported): \`strom sync <file>\` shows what it
96
+ would take — tell them, write it on their yes (\`--apply\`); a sync is
97
+ undone with \`strom sync undo I…\`.
91
98
  - **What waits for them** (\`strom\` shows it): tell them plainly what to do
92
99
  and where, one thing at a time.
93
100
  - **Results and the Strom app**: \`output/tree-strom.ged\` is the family tree
@@ -32,7 +32,10 @@ export async function subMenu(ctx, lang, build) {
32
32
  const i = await ctx.choose(title ?? "", all.map((it) => ({ key: it.key === "0" ? "0" : String(++n), label: it.label })), all.length - 1);
33
33
  if (i === undefined)
34
34
  return;
35
- if ((await guarded(ctx, lang, all[i].act)) === true)
35
+ const done = await guarded(ctx, lang, all[i].act);
36
+ if (done === "quit")
37
+ return "quit";
38
+ if (done === true)
36
39
  return;
37
40
  }
38
41
  }
@@ -4,9 +4,13 @@
4
4
  // agent takes it up in the next conversation or run.
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
- import { agentReady, droppedPaths, pause, pickPerson, subMenu, translator } from "./menu-parts.js";
7
+ import { agentReady, droppedPaths, outOfAnswers, pause, pickPerson, subMenu, translator } from "./menu-parts.js";
8
8
  import { Tree } from "../core/tree.js";
9
- import { displayName } from "../core/people.js";
9
+ import { displayName, lifespan } from "../core/people.js";
10
+ import { DEATH_AFTER_YEARS, unprovenPeople } from "../core/review.js";
11
+ import { planSync, readTreeFile, receivedSince } from "../core/sync.js";
12
+ import { appSendsChanges, stromAppState } from "../core/stromapp.js";
13
+ import { UNPROVEN_BATCH } from "../commands/research.js";
10
14
  import { expandHome } from "../core/paths.js";
11
15
  import { collectFiles } from "../core/media.js";
12
16
  import { BOOK_OF_SCANS, IMAGE_EXT } from "../commands/intake.js";
@@ -17,8 +21,15 @@ export async function addToResearch(ctx, run, lang, root) {
17
21
  { key: "1", label: t("ui.more.intake"), act: async () => void (await addMaterial(ctx, run, lang, root)) },
18
22
  { key: "2", label: t("ui.more.research"), act: async () => void (await newResearch(ctx, run, lang, root)) },
19
23
  ];
20
- if (Tree.open(root, ctx.env).count("person") > 0)
24
+ const tree = Tree.open(root, ctx.env);
25
+ if (tree.count("person") > 0) {
21
26
  items.push({ key: "3", label: t("ui.menu.review"), act: async () => void (await reviewPerson(ctx, run, lang, root)) });
27
+ items.push({ key: "4", label: t(noApp(ctx) ? "ui.more.sync.noapp" : "ui.more.sync"), act: async () => void (await syncTree(ctx, run, lang, root)) });
28
+ }
29
+ // only when there are some: the people no record of their own proves — last, it shows only sometimes
30
+ const unproven = unprovenPeople(tree).filter((u) => !u.living).length;
31
+ if (unproven)
32
+ items.push({ key: "5", label: t("ui.menu.unproven", { n: unproven }), act: async () => void (await reviewUnproven(ctx, run, lang, root)) });
22
33
  return { title: t("ui.more.title"), items };
23
34
  });
24
35
  }
@@ -176,6 +187,154 @@ async function newResearch(ctx, run, lang, root) {
176
187
  ctx.io.stdout(t("ui.research.done", { name: made.name }) + "\n");
177
188
  await offerChat(ctx, run, lang, root, t("ui.research.chat"), t("ui.research.say", { name: made.name }));
178
189
  }
190
+ /** The person said no to the Strom app: it is not named. */
191
+ const noApp = (ctx) => stromAppState(ctx.settings) === "no";
192
+ /** A family tree coming back (the Strom app, another program): what changed in it, shown, then written on the person's word. */
193
+ async function syncTree(ctx, run, lang, root) {
194
+ const t = translator(lang);
195
+ let file;
196
+ // straight from the Strom app, where it can send the tree (and the person wants it)
197
+ if (!noApp(ctx) && appSendsChanges(ctx.settings)) {
198
+ const from = await ctx.choose(t("ui.sync.from"), [{ label: t("ui.sync.from.app") }, { label: t("ui.sync.from.file") }], 0, { back: t("ui.browse.back") });
199
+ if (from === undefined)
200
+ return;
201
+ if (from === 0) {
202
+ const since = Date.now();
203
+ if ((await run(["sync", "--app"])) !== 0)
204
+ return pause(ctx, lang);
205
+ file = receivedSince(root, since);
206
+ if (!file)
207
+ return pause(ctx, lang);
208
+ return confirmSync(ctx, run, lang, root, file);
209
+ }
210
+ }
211
+ for (;;) {
212
+ if (outOfAnswers(ctx))
213
+ return;
214
+ const answer = (await ctx.ask(t(noApp(ctx) ? "ui.sync.path.noapp" : "ui.sync.path"))).trim();
215
+ if (!answer || answer === "0")
216
+ return;
217
+ const resolve = (p) => path.resolve(ctx.cwd, expandHome(p, ctx.env));
218
+ const dropped = droppedPaths(answer).map(resolve);
219
+ file = [dropped[0], resolve(answer)].find((p) => !!p && fs.existsSync(p) && fs.statSync(p).isFile());
220
+ if (file)
221
+ break;
222
+ ctx.io.stdout(t("ui.intake.missing", { path: answer }) + "\n");
223
+ }
224
+ // what it would take, asked first: nothing to write — nothing asked
225
+ let count;
226
+ try {
227
+ const tree = Tree.open(root, ctx.env);
228
+ count = planSync(tree, readTreeFile(file), ctx.settings.syncEdits(tree.config)).changes.length;
229
+ }
230
+ catch {
231
+ count = 0;
232
+ }
233
+ if ((await run(["sync", file])) !== 0 || !count)
234
+ return pause(ctx, lang);
235
+ return confirmSync(ctx, run, lang, root, file, count);
236
+ }
237
+ /** What the tree brings is shown: written on the person's word — all, or the ones they pick. */
238
+ async function confirmSync(ctx, run, lang, root, file, known) {
239
+ const t = translator(lang);
240
+ let count = known ?? 0;
241
+ if (known === undefined)
242
+ try {
243
+ const tree = Tree.open(root, ctx.env);
244
+ count = planSync(tree, readTreeFile(file), ctx.settings.syncEdits(tree.config)).changes.length;
245
+ }
246
+ catch {
247
+ count = 0;
248
+ }
249
+ if (!count)
250
+ return pause(ctx, lang);
251
+ const how = await ctx.choose(t("ui.sync.how"), [{ label: t("ui.sync.all") }, { label: t("ui.sync.some") }], 0, { back: t("ui.browse.back") });
252
+ if (how === undefined)
253
+ return;
254
+ let only = [];
255
+ if (how === 1) {
256
+ const picked = await pickNumbers(ctx, lang, Array.from({ length: count }, (_, i) => String(i + 1)));
257
+ if (!picked?.length)
258
+ return;
259
+ only = ["--only", picked.map((i) => i + 1).join(",")];
260
+ }
261
+ if ((await run(["sync", file, "--apply", ...only])) === 0 && !noApp(ctx))
262
+ ctx.io.stdout(t("ui.sync.app") + "\n");
263
+ await pause(ctx, lang);
264
+ }
265
+ /** The people no record of their own proves: listed, then all of them a batch at a time, or the ones picked by number. */
266
+ async function reviewUnproven(ctx, run, lang, root) {
267
+ const t = translator(lang);
268
+ const out = (line) => ctx.io.stdout(line + "\n");
269
+ const tree = Tree.open(root, ctx.env);
270
+ const found = unprovenPeople(tree);
271
+ const all = found.filter((u) => !u.living);
272
+ const living = found.filter((u) => u.living);
273
+ const open = tree.list("task").filter((x) => ["open", "doing", "parked", "waiting"].includes(x.state));
274
+ out(t("ui.unproven.title"));
275
+ all.forEach((u, i) => {
276
+ const busy = open.some((x) => x.subject.includes(u.person.id));
277
+ const life = lifespan(u.person);
278
+ out(` ${String(i + 1).padStart(2)}. ${u.person.id} ${displayName(u.person)}${life ? ` (${life})` : ""} — ${t(`ui.unproven.${u.kind}`)}${busy ? ` · ${t("ui.unproven.tasks")}` : ""}`);
279
+ });
280
+ if (living.length)
281
+ out(t("ui.unproven.living", { years: DEATH_AFTER_YEARS, names: living.map((u) => `${u.person.id} ${displayName(u.person)}`).join(", ") }));
282
+ const how = await ctx.choose(t("ui.unproven.how"), [{ label: t("ui.unproven.all", { n: UNPROVEN_BATCH }) }, { label: t("ui.unproven.pick") }], 0, { back: t("ui.browse.back") });
283
+ if (how === undefined)
284
+ return;
285
+ let argv;
286
+ if (how === 0)
287
+ argv = ["review", "--unproven"];
288
+ else {
289
+ const picked = await pickNumbers(ctx, lang, all.map((u) => u.person.id));
290
+ if (!picked?.length)
291
+ return;
292
+ argv = ["review", ...picked.map((i) => all[i].person.id)];
293
+ }
294
+ if ((await run(argv)) !== 0)
295
+ return pause(ctx, lang);
296
+ const after = Tree.open(root, ctx.env);
297
+ const ids = argv.slice(1).filter((a) => a.startsWith("P"));
298
+ const research = after
299
+ .list("research")
300
+ .find((r) => r.direction === "person" && r.state === "active" && (ids.length ? r.review?.people?.length === ids.length && ids.every((id) => r.review.people.includes(id)) : r.review?.unproven));
301
+ const waiting = research ? after.list("task").filter((x) => x.research === research.id && x.state === "open").length : 0;
302
+ if (research && waiting && (await ctx.confirm(t("ui.review.run", { n: waiting }), false)))
303
+ await run(["run", "--research", research.id, "--max", String(waiting)]);
304
+ await pause(ctx, lang);
305
+ }
306
+ /** Numbers of a list as a person types them: "1 3 5-7", "2,4", or the IDs shown ("P0012"); each once, in their order. Undefined: back. */
307
+ async function pickNumbers(ctx, lang, ids) {
308
+ const count = ids.length;
309
+ const t = translator(lang);
310
+ for (;;) {
311
+ const answer = (await ctx.ask(t("ui.unproven.numbers"))).trim();
312
+ if (!answer || answer === "0")
313
+ return undefined;
314
+ const out = [];
315
+ const bad = [];
316
+ for (const part of answer.split(/[\s,;]+/u).filter(Boolean)) {
317
+ const id = ids.indexOf(part.toUpperCase());
318
+ if (id >= 0) {
319
+ if (!out.includes(id))
320
+ out.push(id);
321
+ continue;
322
+ }
323
+ const m = /^(\d+)(?:\s*[-–]\s*(\d+))?$/u.exec(part);
324
+ const from = m ? Number(m[1]) : NaN;
325
+ const to = m?.[2] ? Number(m[2]) : from;
326
+ if (!m || from < 1 || to > count || to < from)
327
+ bad.push(part);
328
+ else
329
+ for (let i = from; i <= to; i++)
330
+ if (!out.includes(i - 1))
331
+ out.push(i - 1);
332
+ }
333
+ if (!bad.length)
334
+ return out;
335
+ ctx.io.stdout(t("ui.unproven.bad", { x: bad.join(", ") }) + "\n");
336
+ }
337
+ }
179
338
  /** One person, looked at again: what the tree says of them elsewhere, what to read whole, what to check. */
180
339
  async function reviewPerson(ctx, run, lang, root) {
181
340
  const t = translator(lang);
@@ -186,7 +345,7 @@ async function reviewPerson(ctx, run, lang, root) {
186
345
  if ((await run(["review", person.id, ...(family ? ["--scope", "family"] : [])])) !== 0)
187
346
  return pause(ctx, lang);
188
347
  const tree = Tree.open(root, ctx.env);
189
- const research = tree.list("research").find((r) => r.direction === "person" && r.focus === person.id && r.review);
348
+ const research = tree.list("research").find((r) => r.direction === "person" && r.focus === person.id && r.review && !r.review.people);
190
349
  const open = research ? tree.list("task").filter((x) => x.research === research.id && x.state === "open").length : 0;
191
350
  if (research && open && (await ctx.confirm(t("ui.review.run", { n: open }), false)))
192
351
  await run(["run", "--research", research.id, "--max", String(open)]);
@@ -5,7 +5,9 @@
5
5
  // the person at the terminal does, through the ordinary commands.
6
6
  import path from "node:path";
7
7
  import { claudeHere, pause, subMenu, translator } from "./menu-parts.js";
8
- import { Tree } from "../core/tree.js";
8
+ import { Tree, VERSION } from "../core/tree.js";
9
+ import { replacedOnDisk } from "../core/self.js";
10
+ import { shortcutName } from "./wizard.js";
9
11
  import { DEFAULT_RUN_MINUTES } from "../core/config.js";
10
12
  import { askGate, ensureGatesDir, listGates, loadGate } from "../core/gate.js";
11
13
  import { ensureHooksDir, hooksDir, listHooks } from "../core/hooks.js";
@@ -16,7 +18,7 @@ import { loadLogins } from "../core/logins.js";
16
18
  import { claudeRemoteAtStartup } from "../agents/global.js";
17
19
  export async function settingsMenu(ctx, run, lang, root, newer) {
18
20
  const t = translator(lang);
19
- await subMenu(ctx, lang, () => {
21
+ return subMenu(ctx, lang, () => {
20
22
  ctx.settings.reload();
21
23
  const shared = ctx.settings.shared()?.value;
22
24
  const tree = root ? Tree.open(root, ctx.env).config : undefined;
@@ -62,7 +64,19 @@ export async function settingsMenu(ctx, run, lang, root, newer) {
62
64
  });
63
65
  }
64
66
  if (newer)
65
- items.push({ key: "7", label: t("ui.menu.update", { version: newer }), act: async () => void (await run(["update"])) });
67
+ items.push({
68
+ key: "7",
69
+ label: t("ui.menu.update", { version: newer }),
70
+ act: async () => {
71
+ if ((await run(["update"])) !== 0)
72
+ return;
73
+ // this menu is still the old strom: the new one starts with the next strom
74
+ if (!replacedOnDisk(VERSION))
75
+ return;
76
+ ctx.io.stdout(t("ui.update.restart", { shortcut: shortcutName(lang) }) + "\n");
77
+ return "quit";
78
+ },
79
+ });
66
80
  return { title: t("ui.settings.title"), items };
67
81
  });
68
82
  }
package/dist/cli/menu.js CHANGED
@@ -219,7 +219,8 @@ export async function runMenu(ctx, run) {
219
219
  },
220
220
  }, {
221
221
  key: "4",
222
- label: t("ui.menu.more"),
222
+ // the Strom app named only for someone who has not said no to it
223
+ label: t(stromAppState(ctx.settings) === "no" ? "ui.menu.more" : "ui.menu.more.app"),
223
224
  act: async () => addToResearch(ctx, run, lang, root),
224
225
  }, {
225
226
  key: "4",
@@ -283,7 +284,7 @@ export async function runMenu(ctx, run) {
283
284
  if (i === undefined)
284
285
  return;
285
286
  const quit = await guarded(ctx, lang, items[i].act);
286
- if (quit === true) {
287
+ if (quit === true || quit === "quit") {
287
288
  out(t("ui.menu.bye"));
288
289
  return;
289
290
  }