Compare commits

...

1 Commits

Author SHA1 Message Date
e456ac864d Add Apple translation popovers for notes for iOS 17.4+ and macOS 14.4+
Changelog-Added: Add Apple translation popovers for notes for iOS 17.4+ and macOS 14.4+
2024-09-22 00:38:15 -04:00
4 changed files with 65 additions and 22 deletions

View File

@@ -27,19 +27,26 @@ struct TranslateView: View {
let damus_state: DamusState let damus_state: DamusState
let event: NostrEvent let event: NostrEvent
let size: EventViewKind let size: EventViewKind
@Binding var isAppleTranslationPopoverPresented: Bool
@ObservedObject var translations_model: TranslationModel @ObservedObject var translations_model: TranslationModel
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind) { init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, isAppleTranslationPopoverPresented: Binding<Bool>) {
self.damus_state = damus_state self.damus_state = damus_state
self.event = event self.event = event
self.size = size self.size = size
self._isAppleTranslationPopoverPresented = isAppleTranslationPopoverPresented
self._translations_model = ObservedObject(wrappedValue: damus_state.events.get_cache_data(event.id).translations_model) self._translations_model = ObservedObject(wrappedValue: damus_state.events.get_cache_data(event.id).translations_model)
} }
var TranslateButton: some View { var TranslateButton: some View {
Button(NSLocalizedString("Translate Note", comment: "Button to translate note from different language.")) { Button(NSLocalizedString("Translate Note", comment: "Button to translate note from different language.")) {
translate() if damus_state.settings.translation_service == .none {
isAppleTranslationPopoverPresented = true
} else {
translate()
}
} }
.translate_button_style() .translate_button_style()
} }
@@ -74,17 +81,25 @@ struct TranslateView: View {
} }
func should_transl(_ note_lang: String) -> Bool { func should_transl(_ note_lang: String) -> Bool {
should_translate(event: event, our_keypair: damus_state.keypair, settings: damus_state.settings, note_lang: note_lang) guard should_translate(event: event, our_keypair: damus_state.keypair, note_lang: note_lang) else {
return false
}
if TranslationService.isAppleTranslationPopoverSupported {
return damus_state.settings.translation_service == .none || damus_state.settings.can_translate
} else {
return damus_state.settings.can_translate
}
} }
var body: some View { var body: some View {
Group { Group {
switch self.translations_model.state { switch self.translations_model.state {
case .havent_tried: case .havent_tried:
if damus_state.settings.auto_translate { if damus_state.settings.auto_translate && damus_state.settings.translation_service != .none {
Text("") Text("")
} else if let note_lang = translations_model.note_language, should_transl(note_lang) { } else if let note_lang = translations_model.note_language, should_transl(note_lang) {
TranslateButton TranslateButton
} else { } else {
Text("") Text("")
} }
@@ -114,9 +129,11 @@ extension View {
} }
struct TranslateView_Previews: PreviewProvider { struct TranslateView_Previews: PreviewProvider {
@State static var isAppleTranslationPopoverPresented: Bool = false
static var previews: some View { static var previews: some View {
let ds = test_damus_state let ds = test_damus_state
TranslateView(damus_state: ds, event: test_note, size: .normal) TranslateView(damus_state: ds, event: test_note, size: .normal, isAppleTranslationPopoverPresented: $isAppleTranslationPopoverPresented)
} }
} }

View File

@@ -38,7 +38,13 @@ enum TranslationService: String, CaseIterable, Identifiable, StringCodable {
var model: Model { var model: Model {
switch self { switch self {
case .none: case .none:
return .init(tag: self.rawValue, displayName: NSLocalizedString("none_translation_service", value: "None", comment: "Dropdown option for selecting no translation service.")) let displayName: String
if TranslationService.isAppleTranslationPopoverSupported {
displayName = NSLocalizedString("apple_translation_service", value: "Apple", comment: "Dropdown option for selecting Apple as a translation service.")
} else {
displayName = NSLocalizedString("none_translation_service", value: "None", comment: "Dropdown option for selecting no translation service.")
}
return .init(tag: self.rawValue, displayName: displayName)
case .purple: case .purple:
return .init(tag: self.rawValue, displayName: NSLocalizedString("Damus Purple", comment: "Dropdown option for selecting Damus Purple as a translation service.")) return .init(tag: self.rawValue, displayName: NSLocalizedString("Damus Purple", comment: "Dropdown option for selecting Damus Purple as a translation service."))
case .libretranslate: case .libretranslate:
@@ -51,4 +57,12 @@ enum TranslationService: String, CaseIterable, Identifiable, StringCodable {
return .init(tag: self.rawValue, displayName: NSLocalizedString("translate.nostr.wine (DeepL, Pay with BTC)", comment: "Dropdown option for selecting translate.nostr.wine as the translation service.")) return .init(tag: self.rawValue, displayName: NSLocalizedString("translate.nostr.wine (DeepL, Pay with BTC)", comment: "Dropdown option for selecting translate.nostr.wine as the translation service."))
} }
} }
static var isAppleTranslationPopoverSupported: Bool {
if #available(iOS 17.4, macOS 14.4, *) {
return true
} else {
return false
}
}
} }

View File

@@ -244,16 +244,12 @@ class EventCache {
} }
} }
func should_translate(event: NostrEvent, our_keypair: Keypair, settings: UserSettingsStore, note_lang: String?) -> Bool { func should_translate(event: NostrEvent, our_keypair: Keypair, note_lang: String?) -> Bool {
guard settings.can_translate else {
return false
}
// don't translate reposts, longform, etc // don't translate reposts, longform, etc
if event.kind != 1 { if event.kind != 1 {
return false; return false;
} }
// Do not translate self-authored notes if logged in with a private key // Do not translate self-authored notes if logged in with a private key
// as we can assume the user can understand their own notes. // as we can assume the user can understand their own notes.
// The detected language prediction could be incorrect and not in the list of preferred languages. // The detected language prediction could be incorrect and not in the list of preferred languages.
@@ -261,25 +257,33 @@ func should_translate(event: NostrEvent, our_keypair: Keypair, settings: UserSet
if our_keypair.privkey != nil && our_keypair.pubkey == event.pubkey { if our_keypair.privkey != nil && our_keypair.pubkey == event.pubkey {
return false return false
} }
if let note_lang { if let note_lang {
let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) }) let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) })
// Don't translate if its in our preferred languages // Don't translate if its in our preferred languages
guard !preferredLanguages.contains(note_lang) else { guard !preferredLanguages.contains(note_lang) else {
// if its the same, give up and don't retry // if its the same, give up and don't retry
return false return false
} }
} }
// we should start translating if we have auto_translate on // we should start translating if we have auto_translate on
return true return true
} }
func can_and_should_translate(event: NostrEvent, our_keypair: Keypair, settings: UserSettingsStore, note_lang: String?) -> Bool {
guard settings.can_translate else {
return false
}
return should_translate(event: event, our_keypair: our_keypair, note_lang: note_lang)
}
func should_preload_translation(event: NostrEvent, our_keypair: Keypair, current_status: TranslateStatus, settings: UserSettingsStore, note_lang: String?) -> Bool { func should_preload_translation(event: NostrEvent, our_keypair: Keypair, current_status: TranslateStatus, settings: UserSettingsStore, note_lang: String?) -> Bool {
switch current_status { switch current_status {
case .havent_tried: case .havent_tried:
return should_translate(event: event, our_keypair: our_keypair, settings: settings, note_lang: note_lang) && settings.auto_translate return can_and_should_translate(event: event, our_keypair: our_keypair, settings: settings, note_lang: note_lang) && settings.auto_translate
case .translating: return false case .translating: return false
case .translated: return false case .translated: return false
case .not_needed: return false case .not_needed: return false
@@ -413,7 +417,7 @@ func preload_event(plan: PreloadPlan, state: DamusState) async {
var translations: TranslateStatus? = nil var translations: TranslateStatus? = nil
// We have to recheck should_translate here now that we have note_language // We have to recheck should_translate here now that we have note_language
if plan.load_translations && should_translate(event: plan.event, our_keypair: our_keypair, settings: settings, note_lang: note_language) && settings.auto_translate if plan.load_translations && 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) translations = await translate_note(profiles: profiles, keypair: our_keypair, event: plan.event, settings: settings, note_lang: note_language, purple: state.purple)
} }

View File

@@ -9,6 +9,7 @@ import SwiftUI
import LinkPresentation import LinkPresentation
import NaturalLanguage import NaturalLanguage
import MarkdownUI import MarkdownUI
import Translation
struct Blur: UIViewRepresentable { struct Blur: UIViewRepresentable {
var style: UIBlurEffect.Style = .systemUltraThinMaterial var style: UIBlurEffect.Style = .systemUltraThinMaterial
@@ -32,6 +33,8 @@ struct NoteContentView: View {
let preview_height: CGFloat? let preview_height: CGFloat?
let options: EventViewOptions let options: EventViewOptions
@State var isAppleTranslationPopoverPresented: Bool = false
@ObservedObject var artifacts_model: NoteArtifactsModel @ObservedObject var artifacts_model: NoteArtifactsModel
@ObservedObject var preview_model: PreviewModel @ObservedObject var preview_model: PreviewModel
@ObservedObject var settings: UserSettingsStore @ObservedObject var settings: UserSettingsStore
@@ -96,7 +99,7 @@ struct NoteContentView: View {
} }
var translateView: some View { var translateView: some View {
TranslateView(damus_state: damus_state, event: event, size: self.size) TranslateView(damus_state: damus_state, event: event, size: self.size, isAppleTranslationPopoverPresented: $isAppleTranslationPopoverPresented)
} }
func previewView(links: [URL]) -> some View { func previewView(links: [URL]) -> some View {
@@ -145,7 +148,7 @@ struct NoteContentView: View {
} }
} }
if !options.contains(.no_translate) && (size == .selected || damus_state.settings.auto_translate) { if !options.contains(.no_translate) && (size == .selected || TranslationService.isAppleTranslationPopoverSupported || damus_state.settings.auto_translate) {
if with_padding { if with_padding {
translateView translateView
.padding(.horizontal) .padding(.horizontal)
@@ -298,7 +301,12 @@ struct NoteContentView: View {
Markdown(md.markdown) Markdown(md.markdown)
.padding([.leading, .trailing, .top]) .padding([.leading, .trailing, .top])
case .separated(let separated): case .separated(let separated):
MainContent(artifacts: separated) if #available(iOS 17.4, macOS 14.4, *) {
MainContent(artifacts: separated)
.translationPresentation(isPresented: $isAppleTranslationPopoverPresented, text: event.get_content(damus_state.keypair))
} else {
MainContent(artifacts: separated)
}
} }
} }
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)