Compare commits
2 Commits
event-rela
...
apple-auto
| Author | SHA1 | Date | |
|---|---|---|---|
|
f28b15e84a
|
|||
|
e2cf6ffab2
|
@@ -33,6 +33,12 @@
|
||||
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
|
||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
|
||||
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
||||
3AEEB1FD2CAA3F7B004653F8 /* OfflineTranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEEB1FC2CAA3F7B004653F8 /* OfflineTranslateView.swift */; };
|
||||
3AEEB1FE2CAA3F7B004653F8 /* OfflineTranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEEB1FC2CAA3F7B004653F8 /* OfflineTranslateView.swift */; };
|
||||
3AF9B5A82CA0B5CF0021A08E /* LanguageSortComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF9B5A72CA0B5CF0021A08E /* LanguageSortComparator.swift */; };
|
||||
3AF9B5A92CA0B5CF0021A08E /* LanguageSortComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF9B5A72CA0B5CF0021A08E /* LanguageSortComparator.swift */; };
|
||||
3AF9B5AB2CA0D2D90021A08E /* AppleTranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF9B5AA2CA0D2D90021A08E /* AppleTranslationSettingsView.swift */; };
|
||||
3AF9B5AC2CA0D2D90021A08E /* AppleTranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF9B5AA2CA0D2D90021A08E /* AppleTranslationSettingsView.swift */; };
|
||||
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */; };
|
||||
4C011B5E2BD0A56A002F2F9B /* ChatEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */; };
|
||||
4C011B5F2BD0A56A002F2F9B /* ChatroomThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5D2BD0A56A002F2F9B /* ChatroomThreadView.swift */; };
|
||||
@@ -1329,9 +1335,12 @@
|
||||
3AEB8003297CCEA800713A25 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "tr-TR"; path = "tr-TR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3AEB8004297CCEA800713A25 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "tr-TR"; path = "tr-TR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3AEB8005297CCEA900713A25 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "tr-TR"; path = "tr-TR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
3AEEB1FC2CAA3F7B004653F8 /* OfflineTranslateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineTranslateView.swift; sourceTree = "<group>"; };
|
||||
3AF6336829884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3AF6336929884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3AF6336A29884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-PT"; path = "pt-PT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
3AF9B5A72CA0B5CF0021A08E /* LanguageSortComparator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSortComparator.swift; sourceTree = "<group>"; };
|
||||
3AF9B5AA2CA0D2D90021A08E /* AppleTranslationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleTranslationSettingsView.swift; sourceTree = "<group>"; };
|
||||
3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nip98HTTPAuth.swift; sourceTree = "<group>"; };
|
||||
4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEventView.swift; sourceTree = "<group>"; };
|
||||
4C011B5D2BD0A56A002F2F9B /* ChatroomThreadView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatroomThreadView.swift; sourceTree = "<group>"; };
|
||||
@@ -2276,6 +2285,7 @@
|
||||
4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */,
|
||||
4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */,
|
||||
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */,
|
||||
3AF9B5AA2CA0D2D90021A08E /* AppleTranslationSettingsView.swift */,
|
||||
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */,
|
||||
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */,
|
||||
D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */,
|
||||
@@ -2743,6 +2753,7 @@
|
||||
D74AAFCE2B155D8C006CF0F4 /* ZapDataModel.swift */,
|
||||
D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */,
|
||||
D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */,
|
||||
3AF9B5A72CA0B5CF0021A08E /* LanguageSortComparator.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
@@ -3007,6 +3018,7 @@
|
||||
4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */,
|
||||
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */,
|
||||
5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */,
|
||||
3AEEB1FC2CAA3F7B004653F8 /* OfflineTranslateView.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@@ -3774,6 +3786,7 @@
|
||||
5C8711DE2C460C06007879C2 /* PostingTimelineView.swift in Sources */,
|
||||
4CA9275D2A28FF630098A105 /* LongformView.swift in Sources */,
|
||||
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
|
||||
3AF9B5AC2CA0D2D90021A08E /* AppleTranslationSettingsView.swift in Sources */,
|
||||
D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */,
|
||||
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
|
||||
5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */,
|
||||
@@ -3849,6 +3862,7 @@
|
||||
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */,
|
||||
B5C60C202B530D5100C5ECA7 /* MuteItem.swift in Sources */,
|
||||
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
|
||||
3AEEB1FE2CAA3F7B004653F8 /* OfflineTranslateView.swift in Sources */,
|
||||
4C32B9582A9AD44700DC3548 /* VeriferOptions.swift in Sources */,
|
||||
D74AAFC22B153395006CF0F4 /* HeadlessDamusState.swift in Sources */,
|
||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
||||
@@ -3971,6 +3985,7 @@
|
||||
4C7D09622A098D0E00943473 /* WalletConnect.swift in Sources */,
|
||||
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
|
||||
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
||||
3AF9B5A92CA0B5CF0021A08E /* LanguageSortComparator.swift in Sources */,
|
||||
4C12535E2A76CA870004F4B8 /* SwitchedTimelineNotify.swift in Sources */,
|
||||
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */,
|
||||
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
|
||||
@@ -4355,6 +4370,7 @@
|
||||
D73E5E7E2C6A97F4007EB227 /* Hashtags.swift in Sources */,
|
||||
D73E5E7F2C6A97F4007EB227 /* LocalNotification.swift in Sources */,
|
||||
D73E5E802C6A97F4007EB227 /* CredentialHandler.swift in Sources */,
|
||||
3AF9B5AB2CA0D2D90021A08E /* AppleTranslationSettingsView.swift in Sources */,
|
||||
D73E5E812C6A97F4007EB227 /* KeyboardVisible.swift in Sources */,
|
||||
D73E5E832C6A97F4007EB227 /* AVPlayer+Additions.swift in Sources */,
|
||||
D73E5E842C6A97F4007EB227 /* Zaps+.swift in Sources */,
|
||||
@@ -4449,6 +4465,7 @@
|
||||
D73E5EDE2C6A97F4007EB227 /* AppearanceSettingsView.swift in Sources */,
|
||||
D73E5EDF2C6A97F4007EB227 /* KeySettingsView.swift in Sources */,
|
||||
D73E5EE02C6A97F4007EB227 /* ZapSettingsView.swift in Sources */,
|
||||
3AF9B5A82CA0B5CF0021A08E /* LanguageSortComparator.swift in Sources */,
|
||||
D73E5F792C6A9C4C007EB227 /* HomeModel.swift in Sources */,
|
||||
D73E5EE12C6A97F4007EB227 /* TranslationSettingsView.swift in Sources */,
|
||||
D73E5EE22C6A97F4007EB227 /* SearchSettingsView.swift in Sources */,
|
||||
@@ -4529,6 +4546,7 @@
|
||||
D73E5F2B2C6A97F4007EB227 /* ReplyPart.swift in Sources */,
|
||||
D73E5F2C2C6A97F4007EB227 /* ProxyView.swift in Sources */,
|
||||
D73E5F2D2C6A97F4007EB227 /* SelectedEventView.swift in Sources */,
|
||||
3AEEB1FD2CAA3F7B004653F8 /* OfflineTranslateView.swift in Sources */,
|
||||
D73E5F2E2C6A97F4007EB227 /* EventBody.swift in Sources */,
|
||||
D73E5F302C6A97F4007EB227 /* EventProfile.swift in Sources */,
|
||||
D73E5F312C6A97F4007EB227 /* EventMenu.swift in Sources */,
|
||||
|
||||
168
damus/Components/OfflineTranslateView.swift
Normal file
168
damus/Components/OfflineTranslateView.swift
Normal file
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// OfflineTranslateView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Terry Yiu on 9/29/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
import SwiftUI
|
||||
import NaturalLanguage
|
||||
import Translation
|
||||
|
||||
fileprivate let MIN_UNIQUE_CHARS = 2
|
||||
|
||||
@available(iOS 18.0, macOS 15.0, *)
|
||||
@available(macCatalyst, unavailable)
|
||||
struct OfflineTranslateView: View {
|
||||
let damus_state: DamusState
|
||||
let event: NostrEvent
|
||||
let size: EventViewKind
|
||||
|
||||
@ObservedObject var translations_model: TranslationModel
|
||||
|
||||
@State private var translationConfiguration: TranslationSession.Configuration?
|
||||
|
||||
// @State private var languageStatus: LanguageAvailability.Status?
|
||||
|
||||
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind) {
|
||||
self.damus_state = damus_state
|
||||
self.event = event
|
||||
self.size = size
|
||||
self._translations_model = ObservedObject(wrappedValue: damus_state.events.get_cache_data(event.id).translations_model)
|
||||
}
|
||||
|
||||
var TranslateButton: some View {
|
||||
Button(NSLocalizedString("Translate Note", comment: "Button to translate note from different language.")) {
|
||||
translate()
|
||||
}
|
||||
.translate_button_style()
|
||||
}
|
||||
|
||||
func TranslatedView(lang: String?, artifacts: NoteArtifactsSeparated, font_size: Double) -> some View {
|
||||
return VStack(alignment: .leading) {
|
||||
let translatedFromLanguageString = String(format: NSLocalizedString("Translated from %@", comment: "Button to indicate that the note has been translated from a different language."), lang ?? "ja")
|
||||
Text(translatedFromLanguageString)
|
||||
.foregroundColor(.gray)
|
||||
.font(.footnote)
|
||||
.padding([.top, .bottom], 10)
|
||||
|
||||
if self.size == .selected {
|
||||
SelectableText(damus_state: damus_state, event: event, attributedString: artifacts.content.attributed, size: self.size)
|
||||
} else {
|
||||
artifacts.content.text
|
||||
.font(eventviewsize_to_font(self.size, font_size: font_size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func translate() {
|
||||
guard /*let languageStatus, */translations_model.state == .havent_tried && damus_state.settings.translation_service == .none && damus_state.settings.translate_offline/* && languageStatus != .unsupported*/, let note_language = translations_model.note_language else {
|
||||
return
|
||||
}
|
||||
|
||||
guard translationConfiguration == nil else {
|
||||
translationConfiguration?.invalidate()
|
||||
return
|
||||
}
|
||||
|
||||
translationConfiguration = TranslationSession.Configuration(
|
||||
source: Locale.Language(identifier: note_language))
|
||||
}
|
||||
|
||||
// func setLanguageStatus() async {
|
||||
// guard languageStatus == nil else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// guard let note_language = translations_model.note_language else {
|
||||
// languageStatus = .unsupported
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let languageAvailability = LanguageAvailability()
|
||||
// let language = Locale.Language(identifier: note_language)
|
||||
// languageStatus = await languageAvailability.status(from: language, to: nil)
|
||||
// }
|
||||
|
||||
var body: some View {
|
||||
if let note_lang = translations_model.note_language, damus_state.settings.translation_service == .none && damus_state.settings.translate_offline && should_translate(event: event, our_keypair: damus_state.keypair, note_lang: note_lang) {
|
||||
Group {
|
||||
switch self.translations_model.state {
|
||||
case .havent_tried:
|
||||
if damus_state.settings.auto_translate/* && languageStatus == .installed*/ {
|
||||
Text("")
|
||||
} else {
|
||||
TranslateButton
|
||||
}
|
||||
case .translating:
|
||||
Text("")
|
||||
case .translated(let translated):
|
||||
let languageName = Locale.current.localizedString(forLanguageCode: translated.language)
|
||||
TranslatedView(lang: languageName, artifacts: translated.artifacts, font_size: damus_state.settings.font_size)
|
||||
case .not_needed:
|
||||
Text("")
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
// Task { @MainActor in
|
||||
// await setLanguageStatus()
|
||||
// }
|
||||
translate()
|
||||
}
|
||||
.translationTask(translationConfiguration) { translationSession in
|
||||
Task { @MainActor in
|
||||
do {
|
||||
guard let note_language = translations_model.note_language, translations_model.state == .havent_tried/*, languageStatus != .unsupported*/ else {
|
||||
return
|
||||
}
|
||||
|
||||
translations_model.state = .translating
|
||||
|
||||
let originalContent = event.get_content(damus_state.keypair)
|
||||
let response = try await translationSession.translate(originalContent)
|
||||
let translated_note = response.targetText
|
||||
|
||||
guard originalContent != translated_note else {
|
||||
// if its the same, give up and don't retry
|
||||
translations_model.state = .not_needed
|
||||
return
|
||||
}
|
||||
|
||||
guard translationMeetsStringDistanceRequirements(original: originalContent, translated: translated_note) else {
|
||||
translations_model.state = .not_needed
|
||||
return
|
||||
}
|
||||
|
||||
// Render translated note
|
||||
let translated_blocks = parse_note_content(content: .content(translated_note, event.tags))
|
||||
let artifacts = render_blocks(blocks: translated_blocks, profiles: damus_state.profiles)
|
||||
|
||||
// and cache it
|
||||
translations_model.state = .translated(Translated(artifacts: artifacts, language: note_language))
|
||||
} catch {
|
||||
// code to handle error
|
||||
print("Error translating note: \(error.localizedDescription)")
|
||||
translations_model.state = .not_needed
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("")
|
||||
}
|
||||
}
|
||||
|
||||
func translationMeetsStringDistanceRequirements(original: String, translated: String) -> Bool {
|
||||
return levenshteinDistanceIsGreaterThanOrEqualTo(from: original, to: translated, threshold: MIN_UNIQUE_CHARS)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 18.0, macOS 15.0, *)
|
||||
@available(macCatalyst, unavailable)
|
||||
struct OfflineTranslateView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let ds = test_damus_state
|
||||
OfflineTranslateView(damus_state: ds, event: test_note, size: .normal)
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ struct TranslateView: View {
|
||||
return false
|
||||
}
|
||||
|
||||
if TranslationService.isAppleTranslationPopoverSupported {
|
||||
if TranslationService.isAppleTranslationSupported {
|
||||
return damus_state.settings.translation_service == .none || damus_state.settings.can_translate
|
||||
} else {
|
||||
return damus_state.settings.can_translate
|
||||
|
||||
@@ -39,7 +39,7 @@ enum TranslationService: String, CaseIterable, Identifiable, StringCodable {
|
||||
switch self {
|
||||
case .none:
|
||||
let displayName: String
|
||||
if TranslationService.isAppleTranslationPopoverSupported {
|
||||
if TranslationService.isAppleTranslationSupported {
|
||||
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.")
|
||||
@@ -58,11 +58,15 @@ enum TranslationService: String, CaseIterable, Identifiable, StringCodable {
|
||||
}
|
||||
}
|
||||
|
||||
static var isAppleTranslationPopoverSupported: Bool {
|
||||
static var isAppleTranslationSupported: Bool {
|
||||
#if targetEnvironment(macCatalyst)
|
||||
return false
|
||||
#else
|
||||
if #available(iOS 17.4, macOS 14.4, *) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +180,9 @@ class UserSettingsStore: ObservableObject {
|
||||
@Setting(key: "auto_translate", default_value: true)
|
||||
var auto_translate: Bool
|
||||
|
||||
@Setting(key: "translate_offline", default_value: true)
|
||||
var translate_offline: Bool
|
||||
|
||||
@Setting(key: "show_general_statuses", default_value: true)
|
||||
var show_general_statuses: Bool
|
||||
|
||||
|
||||
51
damus/Util/LanguageSortComparator.swift
Normal file
51
damus/Util/LanguageSortComparator.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// LanguageSortComparator.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Terry Yiu on 9/22/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct LanguageSortComparator: SortComparator {
|
||||
var order: SortOrder
|
||||
|
||||
func compare(_ lhs: Locale.Language, _ rhs: Locale.Language) -> ComparisonResult {
|
||||
let comparisonResult = compareForward(lhs, rhs)
|
||||
switch order {
|
||||
case .forward:
|
||||
return comparisonResult
|
||||
case .reverse:
|
||||
switch comparisonResult {
|
||||
case .orderedAscending:
|
||||
return .orderedDescending
|
||||
case .orderedDescending:
|
||||
return .orderedAscending
|
||||
case .orderedSame:
|
||||
return .orderedSame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func compareForward(_ lhs: Locale.Language, _ rhs: Locale.Language) -> ComparisonResult {
|
||||
let currentLocale = Locale.current
|
||||
let localizedLhs = currentLocale.localizedString(forLanguage: lhs)
|
||||
let localizedRhs = currentLocale.localizedString(forLanguage: rhs)
|
||||
|
||||
return localizedLhs.localizedCompare(localizedRhs)
|
||||
}
|
||||
}
|
||||
|
||||
extension Locale {
|
||||
func localizedString(forLanguage language: Locale.Language) -> String {
|
||||
guard let languageCode = language.languageCode, let localizedLanguageCode = localizedString(forLanguageCode: languageCode.identifier) else {
|
||||
return language.languageCode?.identifier ?? language.minimalIdentifier
|
||||
}
|
||||
|
||||
if let region = language.region, let localizedRegion = localizedString(forRegionCode: region.identifier) {
|
||||
return "\(localizedLanguageCode) (\(localizedRegion))"
|
||||
} else {
|
||||
return localizedLanguageCode
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,17 @@ struct NoteContentView: View {
|
||||
}
|
||||
|
||||
var translateView: some View {
|
||||
#if targetEnvironment(macCatalyst)
|
||||
TranslateView(damus_state: damus_state, event: event, size: self.size, isAppleTranslationPopoverPresented: $isAppleTranslationPopoverPresented)
|
||||
#else
|
||||
if #available(iOS 18.0, macOS 15.0, *), damus_state.settings.translate_offline {
|
||||
AnyView(OfflineTranslateView(damus_state: damus_state, event: event, size: self.size))
|
||||
} else {
|
||||
AnyView(
|
||||
TranslateView(damus_state: damus_state, event: event, size: self.size, isAppleTranslationPopoverPresented: $isAppleTranslationPopoverPresented)
|
||||
)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func previewView(links: [URL]) -> some View {
|
||||
@@ -148,7 +158,7 @@ struct NoteContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if !options.contains(.no_translate) && (size == .selected || TranslationService.isAppleTranslationPopoverSupported || damus_state.settings.auto_translate) {
|
||||
if !options.contains(.no_translate) && (size == .selected || TranslationService.isAppleTranslationSupported || damus_state.settings.auto_translate) {
|
||||
if with_padding {
|
||||
translateView
|
||||
.padding(.horizontal)
|
||||
@@ -301,9 +311,13 @@ struct NoteContentView: View {
|
||||
Markdown(md.markdown)
|
||||
.padding([.leading, .trailing, .top])
|
||||
case .separated(let separated):
|
||||
if #available(iOS 17.4, macOS 14.4, *) {
|
||||
if #available(iOS 18.0, macOS 15.0, *), damus_state.settings.auto_translate {
|
||||
MainContent(artifacts: separated)
|
||||
} else if #available(iOS 17.4, macOS 14.4, *) {
|
||||
MainContent(artifacts: separated)
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
.translationPresentation(isPresented: $isAppleTranslationPopoverPresented, text: event.get_content(damus_state.keypair))
|
||||
#endif
|
||||
} else {
|
||||
MainContent(artifacts: separated)
|
||||
}
|
||||
|
||||
91
damus/Views/Settings/AppleTranslationSettingsView.swift
Normal file
91
damus/Views/Settings/AppleTranslationSettingsView.swift
Normal file
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// AppleTranslationSettingsView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Terry Yiu on 9/22/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Translation
|
||||
|
||||
@available(iOS 18.0, macOS 15.0, *)
|
||||
@available(macCatalyst, unavailable)
|
||||
struct AppleTranslationSettingsView: View {
|
||||
@ObservedObject var settings: UserSettingsStore
|
||||
|
||||
@State private var supportedLanguages: [Locale.Language] = []
|
||||
@State private var installedLanguages = Set<Locale.Language>()
|
||||
@State private var installingLanguages = Set<Locale.Language>()
|
||||
@State private var languageTranslationConfigurations = [Locale.Language: TranslationSession.Configuration]()
|
||||
|
||||
var body: some View {
|
||||
if settings.translation_service == .none {
|
||||
if !installedLanguages.isEmpty {
|
||||
Section(NSLocalizedString("Available Offline", comment: "Section for downloaded languages for Apple offline translations.")) {
|
||||
ForEach(installedLanguages.sorted(using: LanguageSortComparator(order: .forward)), id: \.self) { language in
|
||||
Text(Locale.current.localizedString(forLanguage: language))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(NSLocalizedString("Languages Available for Download", comment: "Section for downloadable languages for Apple offline translations.")) {
|
||||
ForEach(supportedLanguages.filter { !installedLanguages.contains($0) }, id: \.self) { language in
|
||||
HStack {
|
||||
Text(Locale.current.localizedString(forLanguage: language))
|
||||
Button(
|
||||
action: {
|
||||
installingLanguages.insert(language)
|
||||
languageTranslationConfigurations[language]?.invalidate()
|
||||
},
|
||||
label: {
|
||||
Image(systemName: "arrow.down.circle")
|
||||
}
|
||||
)
|
||||
}
|
||||
.translationTask(languageTranslationConfigurations[language]) { session in
|
||||
if installingLanguages.contains(language) {
|
||||
do {
|
||||
// Display a sheet asking the user's permission
|
||||
// to start downloading the language pairing by
|
||||
// translating a dummy string.
|
||||
//
|
||||
// We do not use `session.prepareTranslation()` because
|
||||
// it does not throw errors as loudly as `session.translate` does,
|
||||
// which helps us indicate when language download is complete.
|
||||
_ = try await session.translate("A")
|
||||
installedLanguages.insert(language)
|
||||
installingLanguages.remove(language)
|
||||
} catch {
|
||||
// Handle any errors.
|
||||
print("Error downloading language \(language): \(error)")
|
||||
installingLanguages.remove(language)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
Task {
|
||||
let languageAvailability = LanguageAvailability()
|
||||
supportedLanguages = await languageAvailability.supportedLanguages
|
||||
supportedLanguages.sort(using: LanguageSortComparator(order: .forward))
|
||||
installedLanguages.removeAll()
|
||||
|
||||
for supportedLanguage in supportedLanguages {
|
||||
let status = await languageAvailability.status(from: supportedLanguage, to: nil)
|
||||
switch status {
|
||||
case .installed:
|
||||
installedLanguages.insert(supportedLanguage)
|
||||
case .supported:
|
||||
languageTranslationConfigurations[supportedLanguage] = TranslationSession.Configuration(
|
||||
source: supportedLanguage
|
||||
)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,14 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Translation
|
||||
|
||||
struct TranslationSettingsView: View {
|
||||
@ObservedObject var settings: UserSettingsStore
|
||||
var damus_state: DamusState
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(NSLocalizedString("Translations", comment: "Section title for selecting the translation service.")) {
|
||||
@@ -102,11 +103,48 @@ struct TranslationSettingsView: View {
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
if #available(iOS 18.0, macOS 15.0, *), settings.translation_service == .none {
|
||||
Toggle(NSLocalizedString("Automatically translate notes", comment: "Toggle to automatically translate notes."), isOn: $settings.auto_translate)
|
||||
.toggleStyle(.switch)
|
||||
|
||||
Section (
|
||||
content: {
|
||||
Toggle(NSLocalizedString("On-Device Mode", comment: "Toggle to always translate offline using downloaded languages."), isOn: $settings.translate_offline)
|
||||
}, footer: {
|
||||
Text("Always translate offline using downloaded languages. Offline translations may not be as accurate as online translations. Apple may collect usage metrics, but this data does not include the original or translated content.", comment: "Section footer explaining the implications of enabling offline translations.")
|
||||
}
|
||||
)
|
||||
|
||||
AppleTranslationSettingsView(settings: settings)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("Translation", comment: "Navigation title for translation settings."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
.onChange(of: settings.auto_translate) { newValue in
|
||||
// Apple automatic translation can occur only if offline translations are enabled.
|
||||
if settings.translation_service == .none && newValue && !settings.translate_offline {
|
||||
settings.translate_offline = true
|
||||
}
|
||||
}
|
||||
.onChange(of: settings.translate_offline) { newValue in
|
||||
// Apple automatic translation can occur only if offline translations are enabled.
|
||||
if settings.translation_service == .none && !newValue && settings.auto_translate {
|
||||
settings.auto_translate = false
|
||||
}
|
||||
}
|
||||
.onChange(of: settings.translation_service) { newValue in
|
||||
// Apple automatic translation can occur only if offline translations are enabled.
|
||||
// If automatic translations are enabled for a non-Apple translation service,
|
||||
// and then the translation service is switched to Apple, offline translations need to be enabled.
|
||||
if newValue == .none && settings.auto_translate && !settings.translate_offline {
|
||||
settings.translate_offline = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user