Add optional language filter on Universe feed
This commit is contained in:
@@ -80,24 +80,7 @@ struct TranslateView: View {
|
|||||||
currentLanguage = Locale.current.languageCode ?? "en"
|
currentLanguage = Locale.current.languageCode ?? "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in
|
noteLanguage = event.note_language(damus_state.keypair.privkey) ?? currentLanguage
|
||||||
// and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.
|
|
||||||
let originalBlocks = event.blocks(damus_state.keypair.privkey)
|
|
||||||
let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ")
|
|
||||||
|
|
||||||
// Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
|
|
||||||
let languageRecognizer = NLLanguageRecognizer()
|
|
||||||
languageRecognizer.processString(originalOnlyText)
|
|
||||||
noteLanguage = languageRecognizer.languageHypotheses(withMaximum: 1).first(where: { $0.value >= 0.5 })?.key.rawValue ?? currentLanguage
|
|
||||||
|
|
||||||
if let lang = noteLanguage, noteLanguage != currentLanguage {
|
|
||||||
// If the detected dominant language is a variant, remove the variant component and just take the language part as translation services typically only supports the variant-less language.
|
|
||||||
if #available(iOS 16, *) {
|
|
||||||
noteLanguage = Locale.LanguageCode(stringLiteral: lang).identifier(.alpha2)
|
|
||||||
} else {
|
|
||||||
noteLanguage = NSLocale(localeIdentifier: lang).languageCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let note_lang = noteLanguage else {
|
guard let note_lang = noteLanguage else {
|
||||||
noteLanguage = currentLanguage
|
noteLanguage = currentLanguage
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ class UserSettingsStore: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Published var show_only_preferred_languages: Bool {
|
||||||
|
didSet {
|
||||||
|
UserDefaults.standard.set(show_only_preferred_languages, forKey: "show_only_preferred_languages")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Published var translation_service: TranslationService {
|
@Published var translation_service: TranslationService {
|
||||||
didSet {
|
didSet {
|
||||||
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
|
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
|
||||||
@@ -210,6 +216,7 @@ class UserSettingsStore: ObservableObject {
|
|||||||
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
|
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
|
||||||
zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false
|
zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false
|
||||||
disable_animation = should_disable_image_animation()
|
disable_animation = should_disable_image_animation()
|
||||||
|
show_only_preferred_languages = UserDefaults.standard.object(forKey: "show_only_preferred_languages") as? Bool ?? false
|
||||||
|
|
||||||
// Note from @tyiu:
|
// Note from @tyiu:
|
||||||
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
|
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import CommonCrypto
|
|||||||
import secp256k1
|
import secp256k1
|
||||||
import secp256k1_implementation
|
import secp256k1_implementation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
import NaturalLanguage
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -259,6 +260,25 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
|||||||
return event_is_reply(self, privkey: privkey)
|
return event_is_reply(self, privkey: privkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func note_language(_ privkey: String?) -> String? {
|
||||||
|
// 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.
|
||||||
|
let originalBlocks = blocks(privkey)
|
||||||
|
let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ")
|
||||||
|
|
||||||
|
// Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
|
||||||
|
let languageRecognizer = NLLanguageRecognizer()
|
||||||
|
languageRecognizer.processString(originalOnlyText)
|
||||||
|
|
||||||
|
guard let locale = languageRecognizer.languageHypotheses(withMaximum: 1).first(where: { $0.value >= 0.5 })?.key.rawValue else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the variant component and just take the language part as translation services typically only supports the variant-less language.
|
||||||
|
// Moreover, speakers of one variant can generally understand other variants.
|
||||||
|
return localeToLanguage(locale)
|
||||||
|
}
|
||||||
|
|
||||||
public var referenced_ids: [ReferencedId] {
|
public var referenced_ids: [ReferencedId] {
|
||||||
return get_referenced_ids(key: "e")
|
return get_referenced_ids(key: "e")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,3 +21,14 @@ func localizedStringFormat(key: String, locale: Locale?) -> String {
|
|||||||
let fallback = bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: key, value: nil, table: nil)
|
let fallback = bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: key, value: nil, table: nil)
|
||||||
return bundle.localizedString(forKey: key, value: fallback, table: nil)
|
return bundle.localizedString(forKey: key, value: fallback, table: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes the variant part of a locale code so that it contains only the language code.
|
||||||
|
*/
|
||||||
|
func localeToLanguage(_ locale: String) -> String? {
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
return Locale.LanguageCode(stringLiteral: locale).identifier(.alpha2)
|
||||||
|
} else {
|
||||||
|
return NSLocale(localeIdentifier: locale).languageCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ struct ConfigView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section(NSLocalizedString("Translations", comment: "Section title for selecting the translation service.")) {
|
Section(NSLocalizedString("Translations", comment: "Section title for selecting the translation service.")) {
|
||||||
|
Toggle(NSLocalizedString("Show only preferred languages on Universe feed", comment: "Toggle to show notes that are only in the device's preferred languages on the Universe feed and hide notes that are in other languages."), isOn: $settings.show_only_preferred_languages)
|
||||||
|
.toggleStyle(.switch)
|
||||||
Picker(NSLocalizedString("Service", comment: "Prompt selection of translation service provider."), selection: $settings.translation_service) {
|
Picker(NSLocalizedString("Service", comment: "Prompt selection of translation service provider."), selection: $settings.translation_service) {
|
||||||
ForEach(TranslationService.allCases, id: \.self) { server in
|
ForEach(TranslationService.allCases, id: \.self) { server in
|
||||||
Text(server.model.displayName)
|
Text(server.model.displayName)
|
||||||
|
|||||||
@@ -7,12 +7,15 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
import NaturalLanguage
|
||||||
|
|
||||||
struct SearchHomeView: View {
|
struct SearchHomeView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
@StateObject var model: SearchHomeModel
|
@StateObject var model: SearchHomeModel
|
||||||
@State var search: String = ""
|
@State var search: String = ""
|
||||||
@FocusState private var isFocused: Bool
|
@FocusState private var isFocused: Bool
|
||||||
|
|
||||||
|
let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) })
|
||||||
|
|
||||||
var SearchInput: some View {
|
var SearchInput: some View {
|
||||||
HStack {
|
HStack {
|
||||||
@@ -41,12 +44,29 @@ struct SearchHomeView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var GlobalContent: some View {
|
var GlobalContent: some View {
|
||||||
return TimelineView(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: { _ in true })
|
return TimelineView(
|
||||||
.refreshable {
|
events: model.events,
|
||||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
loading: $model.loading,
|
||||||
model.unsubscribe()
|
damus: damus_state,
|
||||||
model.subscribe()
|
show_friend_icon: true,
|
||||||
|
filter: {
|
||||||
|
if damus_state.settings.show_only_preferred_languages == false {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't determine the note's language with 50%+ confidence, lean on the side of caution and show it anyway.
|
||||||
|
guard let noteLanguage = $0.note_language(damus_state.keypair.privkey) else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return preferredLanguages.contains(noteLanguage)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
.refreshable {
|
||||||
|
// Fetch new information by unsubscribing and resubscribing to the relay
|
||||||
|
model.unsubscribe()
|
||||||
|
model.subscribe()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var SearchContent: some View {
|
var SearchContent: some View {
|
||||||
|
|||||||
Reference in New Issue
Block a user