From 5058fb33d7498f80a27e4635edfe8dbcafcc25aa Mon Sep 17 00:00:00 2001 From: alltheseas <64376233+alltheseas@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:56:31 -0600 Subject: [PATCH] Restore translated note rendering Changelog-Fixed: Fixed broken automatic translations Closes: https://github.com/damus-io/damus/issues/3406 Signed-off-by: alltheseas <64376233+alltheseas@users.noreply.github.com> --- damus/Features/Events/NoteContentView.swift | 2 +- .../Translations/Views/TranslateView.swift | 16 ++--- damus/Shared/Utilities/EventCache.swift | 59 +++++++++++++++---- nostrdb/NdbNote.swift | 2 +- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/damus/Features/Events/NoteContentView.swift b/damus/Features/Events/NoteContentView.swift index 50b7b45b..f236c492 100644 --- a/damus/Features/Events/NoteContentView.swift +++ b/damus/Features/Events/NoteContentView.swift @@ -307,7 +307,7 @@ struct NoteContentView: View { } // always reload artifacts on load - let plan = get_preload_plan(evcache: damus_state.events, ev: event, our_keypair: damus_state.keypair, settings: damus_state.settings) + let plan = get_preload_plan(ndb: damus_state.ndb, evcache: damus_state.events, ev: event, our_keypair: damus_state.keypair, settings: damus_state.settings) // TODO: make this cleaner Task { diff --git a/damus/Features/Translations/Views/TranslateView.swift b/damus/Features/Translations/Views/TranslateView.swift index dab5fc79..e457210c 100644 --- a/damus/Features/Translations/Views/TranslateView.swift +++ b/damus/Features/Translations/Views/TranslateView.swift @@ -158,15 +158,15 @@ func translate_note(profiles: Profiles, keypair: Keypair, event: NostrEvent, set return .not_needed } - // Render translated note - // TODO: fix translated blocks - //let translated_blocks = parse_note_content(content: .content(translated_note, event.tags)) - //let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles, can_hide_last_previewable_refs: true) + // Render translated note using the same NostrDB block parser so links/mentions/media stay consistent. + let artifacts: NoteArtifactsSeparated + if let translated_blocks = try? NdbBlockGroup.parse(content: translated_note) { + artifacts = render_blocks(blocks: translated_blocks, profiles: profiles, can_hide_last_previewable_refs: true) + } else { + artifacts = .just_content(translated_note) + } - return .not_needed - - // and cache it - //return .translated(Translated(artifacts: artifacts, language: note_lang)) + return .translated(Translated(artifacts: artifacts, language: note_lang)) } func current_language() -> String { diff --git a/damus/Shared/Utilities/EventCache.swift b/damus/Shared/Utilities/EventCache.swift index 465c9e1d..5ebb9799 100644 --- a/damus/Shared/Utilities/EventCache.swift +++ b/damus/Shared/Utilities/EventCache.swift @@ -280,6 +280,11 @@ func can_and_should_translate(event: NostrEvent, our_keypair: Keypair, settings: } func should_preload_translation(event: NostrEvent, our_keypair: Keypair, current_status: TranslateStatus, settings: UserSettingsStore, note_lang: String?) -> Bool { + guard let note_lang else { + // Without a detected language we can't safely auto-translate. + return false + } + switch current_status { case .havent_tried: return can_and_should_translate(event: event, our_keypair: our_keypair, settings: settings, note_lang: note_lang) && settings.auto_translate @@ -306,7 +311,13 @@ func load_preview(artifacts: NoteArtifactsSeparated) async -> Preview? { return Preview(meta: meta) } -func get_preload_plan(evcache: EventCache, ev: NostrEvent, our_keypair: Keypair, settings: UserSettingsStore) -> PreloadPlan? { +@MainActor +private func compute_note_language(ndb: Ndb, ev: NostrEvent, keypair: Keypair) -> String? { + return ev.note_language(ndb: ndb, keypair) +} + +@MainActor +func get_preload_plan(ndb: Ndb, evcache: EventCache, ev: NostrEvent, our_keypair: Keypair, settings: UserSettingsStore) -> PreloadPlan? { let cache = evcache.get_cache_data(ev.id) let load_artifacts = cache.artifacts.should_preload if load_artifacts { @@ -314,7 +325,10 @@ func get_preload_plan(evcache: EventCache, ev: NostrEvent, our_keypair: Keypair, } // Cached event might not have the note language determined yet, so determine the language here before figuring out if translations should be preloaded. - let note_lang = cache.translations_model.note_language ?? /*ev.note_language(our_keypair.privkey)*/ current_language() + let note_lang = cache.translations_model.note_language ?? compute_note_language(ndb: ndb, ev: ev, keypair: our_keypair) + if cache.translations_model.note_language == nil { + cache.translations_model.note_language = note_lang + } let load_translations = should_preload_translation(event: ev, our_keypair: our_keypair, current_status: cache.translations, settings: settings, note_lang: note_lang) if load_translations { @@ -418,11 +432,19 @@ func preload_event(plan: PreloadPlan, state: DamusState) async { } } - let note_language = plan.data.translations_model.note_language ?? plan.event.note_language(ndb: state.ndb, our_keypair) ?? current_language() + let note_language: String? + if let cached_language = plan.data.translations_model.note_language { + note_language = cached_language + } else { + note_language = await compute_note_language(ndb: state.ndb, ev: plan.event, keypair: our_keypair) + } var translations: TranslateStatus? = nil // We have to recheck should_translate here now that we have note_language - if plan.load_translations && can_and_should_translate(event: plan.event, our_keypair: our_keypair, settings: settings, note_lang: note_language) && settings.auto_translate + if plan.load_translations, + let note_language, + can_and_should_translate(event: plan.event, our_keypair: our_keypair, settings: settings, note_lang: note_language), + settings.auto_translate { translations = await translate_note(profiles: profiles, keypair: our_keypair, event: plan.event, settings: settings, note_lang: note_language, purple: state.purple) } @@ -446,18 +468,29 @@ func preload_events(state: DamusState, events: [NostrEvent]) { let our_keypair = state.keypair let settings = state.settings - let plans = events.compactMap { ev in - get_preload_plan(evcache: event_cache, ev: ev, our_keypair: our_keypair, settings: settings) - } - - if plans.count == 0 { - return - } - Task { + let plans = await withTaskGroup(of: PreloadPlan?.self) { group in + for ev in events { + group.addTask { + await get_preload_plan(ndb: state.ndb, evcache: event_cache, ev: ev, our_keypair: our_keypair, settings: settings) + } + } + + var results: [PreloadPlan] = [] + for await plan in group { + if let plan { + results.append(plan) + } + } + return results + } + + if plans.count == 0 { + return + } + for plan in plans { await preload_event(plan: plan, state: state) } } } - diff --git a/nostrdb/NdbNote.swift b/nostrdb/NdbNote.swift index fa4e5aef..2e22d9bd 100644 --- a/nostrdb/NdbNote.swift +++ b/nostrdb/NdbNote.swift @@ -556,8 +556,8 @@ extension NdbNote { return thread_reply() != nil } + @MainActor func note_language(ndb: Ndb, _ keypair: Keypair) -> String? { - assert(!Thread.isMainThread, "This function must not be run on the main thread.") // Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in // and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.