mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-02-16 17:55:13 +00:00
Reworked translate: Use Mastodon API when available + always show the action in the post context menu
This commit is contained in:
parent
0449120684
commit
41058919bd
22 changed files with 70 additions and 30 deletions
|
@ -338,7 +338,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Tradueix";
|
||||
"status.action.translate-from-%@" = "Tradueix del %@";
|
||||
"status.action.translated-label" = "Traduït amb DeepL.com";
|
||||
"status.action.translated-label-%@" = "Traduït amb %@";
|
||||
"status.action.bookmark" = "Afegeix als marcadors";
|
||||
"status.action.boost" = "Impulsa";
|
||||
"status.action.copy-text" = "Copia el text";
|
||||
|
|
|
@ -339,7 +339,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Übersetzen";
|
||||
"status.action.translate-from-%@" = "Aus %@ übersetzen";
|
||||
"status.action.translated-label" = "Übersetzt mit DeepL.com";
|
||||
"status.action.translated-label-%@" = "Übersetzt mit %@";
|
||||
"status.action.bookmark" = "Lesezeichen";
|
||||
"status.action.boost" = "Boosten";
|
||||
"status.action.copy-text" = "Text kopieren";
|
||||
|
|
|
@ -341,7 +341,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Translate";
|
||||
"status.action.translate-from-%@" = "Translate from %@";
|
||||
"status.action.translated-label" = "Translated using DeepL.com";
|
||||
"status.action.translated-label-%@" = "Translated using %@";
|
||||
"status.action.bookmark" = "Bookmark";
|
||||
"status.action.boost" = "Boost";
|
||||
"status.action.copy-text" = "Copy Text";
|
||||
|
|
|
@ -340,7 +340,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Translate";
|
||||
"status.action.translate-from-%@" = "Translate from %@";
|
||||
"status.action.translated-label" = "Translated using DeepL.com";
|
||||
"status.action.translated-label-%@" = "Translated using %@";
|
||||
"status.action.bookmark" = "Bookmark";
|
||||
"status.action.boost" = "Boost";
|
||||
"status.action.copy-text" = "Copy Text";
|
||||
|
|
|
@ -340,7 +340,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Traducir";
|
||||
"status.action.translate-from-%@" = "Traducir desde %@";
|
||||
"status.action.translated-label" = "Traducido usando DeepL.com";
|
||||
"status.action.translated-label-%@" = "Traducido usando %@";
|
||||
"status.action.bookmark" = "Añadir a marcadores";
|
||||
"status.action.boost" = "Retootear";
|
||||
"status.action.copy-text" = "Copiar texto";
|
||||
|
|
|
@ -339,7 +339,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Itzuli";
|
||||
"status.action.translate-from-%@" = "Egin %@(a)ren itzulpena";
|
||||
"status.action.translated-label" = "DeepL.com erabiliz itzulia";
|
||||
"status.action.translated-label-%@" = "%@ erabiliz itzulia";
|
||||
"status.action.bookmark" = "Jarri laster-marka";
|
||||
"status.action.boost" = "Bultzatu";
|
||||
"status.action.copy-text" = "Kopiatu testua";
|
||||
|
|
|
@ -335,7 +335,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Traduire";
|
||||
"status.action.translate-from-%@" = "Traduire de %@";
|
||||
"status.action.translated-label" = "Traduit avec DeepL.com";
|
||||
"status.action.translated-label-%@" = "Traduit avec %@";
|
||||
"status.action.bookmark" = "Marquer";
|
||||
"status.action.boost" = "Promouvoir";
|
||||
"status.action.copy-text" = "Copier le texte";
|
||||
|
|
|
@ -340,7 +340,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Traduci";
|
||||
"status.action.translate-from-%@" = "Traduci da %@";
|
||||
"status.action.translated-label" = "Tradotto usando DeepL.com";
|
||||
"status.action.translated-label-%@" = "Tradotto usando %@";
|
||||
"status.action.bookmark" = "Salva nei segnalibri";
|
||||
"status.action.boost" = "Condividi";
|
||||
"status.action.copy-text" = "Copia il testo";
|
||||
|
|
|
@ -339,7 +339,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "翻訳";
|
||||
"status.action.translate-from-%@" = "%@ から翻訳";
|
||||
"status.action.translated-label" = "DeepL.comを使用して翻訳";
|
||||
"status.action.translated-label-%@" = "%@ を使用して翻訳";
|
||||
"status.action.bookmark" = "ブックマーク";
|
||||
"status.action.boost" = "ブースト";
|
||||
"status.action.copy-text" = "テキストをコピー";
|
||||
|
|
|
@ -341,7 +341,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "번역";
|
||||
"status.action.translate-from-%@" = "%@에서 번역";
|
||||
"status.action.translated-label" = "DeepL.com을 통해 번역됨";
|
||||
"status.action.translated-label-%@" = "%@을 통해 번역됨";
|
||||
"status.action.bookmark" = "보관함에 추가";
|
||||
"status.action.boost" = "부스트";
|
||||
"status.action.copy-text" = "복사";
|
||||
|
|
|
@ -339,7 +339,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Oversett";
|
||||
"status.action.translate-from-%@" = "Oversett fra %@";
|
||||
"status.action.translated-label" = "Oversatt ved hjelp av DeepL.com";
|
||||
"status.action.translated-label-%@" = "Oversatt ved hjelp av %@";
|
||||
"status.action.bookmark" = "Bokmerk";
|
||||
"status.action.boost" = "Forsterk";
|
||||
"status.action.copy-text" = "Kopier tekst";
|
||||
|
|
|
@ -333,7 +333,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Vertaal";
|
||||
"status.action.translate-from-%@" = "Vertaal uit het %@";
|
||||
"status.action.translated-label" = "Vertaald met behulp van DeepL.com";
|
||||
"status.action.translated-label-%@" = "Vertaald met behulp van %@";
|
||||
"status.action.bookmark" = "Voeg bladwijzer toe";
|
||||
"status.action.boost" = "Boosten";
|
||||
"status.action.copy-text" = "Kopieer tekst";
|
||||
|
|
|
@ -335,7 +335,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Przetłumacz";
|
||||
"status.action.translate-from-%@" = "Przetłumacz tekst %@";
|
||||
"status.action.translated-label" = "Przetłumaczono za pomocą DeepL.com";
|
||||
"status.action.translated-label-%@" = "Przetłumaczono za pomocą %@";
|
||||
"status.action.bookmark" = "Dodaj zakładkę";
|
||||
"status.action.boost" = "Podbij";
|
||||
"status.action.copy-text" = "Kopiuj tekst";
|
||||
|
|
|
@ -339,7 +339,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Traduzir";
|
||||
"status.action.translate-from-%@" = "Traduzir do %@";
|
||||
"status.action.translated-label" = "Traduzir usando DeepL.com";
|
||||
"status.action.translated-label-%@" = "Traduzir usando %@";
|
||||
"status.action.bookmark" = "Salvar";
|
||||
"status.action.boost" = "Boost";
|
||||
"status.action.copy-text" = "Copiar Texto";
|
||||
|
|
|
@ -335,7 +335,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "Tercüme et";
|
||||
"status.action.translate-from-%@" = "Tercüme et %@";
|
||||
"status.action.translated-label" = "DeepL.com tarafından tercüme edildi";
|
||||
"status.action.translated-label-%@" = "%@ tarafından tercüme edildi";
|
||||
"status.action.bookmark" = "Yer İmi Ekle";
|
||||
"status.action.boost" = "Yükselt";
|
||||
"status.action.copy-text" = "Yazıyı Kopyala";
|
||||
|
|
|
@ -340,7 +340,7 @@
|
|||
// MARK: Package: Status
|
||||
"status.action.translate" = "翻译";
|
||||
"status.action.translate-from-%@" = "翻译 %@";
|
||||
"status.action.translated-label" = "由 DeepL.com 翻译";
|
||||
"status.action.translated-label-%@" = "由 %@ 翻译";
|
||||
"status.action.bookmark" = "书签";
|
||||
"status.action.boost" = "转发";
|
||||
"status.action.copy-text" = "拷贝文本";
|
||||
|
|
13
Packages/Models/Sources/Models/StatusTranslation.swift
Normal file
13
Packages/Models/Sources/Models/StatusTranslation.swift
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
|
||||
public struct StatusTranslation: Decodable {
|
||||
public let content: HTMLString
|
||||
public let detectedSourceLanguage: String
|
||||
public let provider: String
|
||||
|
||||
public init(content: String, detectedSourceLanguage: String, provider: String) {
|
||||
self.content = .init(stringValue: content)
|
||||
self.detectedSourceLanguage = detectedSourceLanguage
|
||||
self.provider = provider
|
||||
}
|
||||
}
|
|
@ -1,6 +1,11 @@
|
|||
import Foundation
|
||||
import Models
|
||||
|
||||
public struct DeepLClient {
|
||||
public enum DeepLError: Error {
|
||||
case notFound
|
||||
}
|
||||
|
||||
private let endpoint = "https://api.deepl.com/v2/translate"
|
||||
|
||||
private var APIKey: String {
|
||||
|
@ -32,7 +37,7 @@ public struct DeepLClient {
|
|||
|
||||
public init() {}
|
||||
|
||||
public func request(target: String, source _: String?, text: String) async throws -> String {
|
||||
public func request(target: String, source _: String?, text: String) async throws -> StatusTranslation {
|
||||
do {
|
||||
var components = URLComponents(string: endpoint)!
|
||||
var queryItems: [URLQueryItem] = []
|
||||
|
@ -45,7 +50,12 @@ public struct DeepLClient {
|
|||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
let (result, _) = try await URLSession.shared.data(for: request)
|
||||
let response = try decoder.decode(Response.self, from: result)
|
||||
return response.translations.first?.text.removingPercentEncoding ?? ""
|
||||
if let translation = response.translations.first {
|
||||
return .init(content: translation.text.removingPercentEncoding ?? "",
|
||||
detectedSourceLanguage: translation.detectedSourceLanguage,
|
||||
provider: "DeepL.com")
|
||||
}
|
||||
throw DeepLError.notFound
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ public enum Statuses: Endpoint {
|
|||
case bookmark(id: String)
|
||||
case unbookmark(id: String)
|
||||
case history(id: String)
|
||||
case translate(id: String, lang: String?)
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
|
@ -50,6 +51,8 @@ public enum Statuses: Endpoint {
|
|||
return "statuses/\(id)/unbookmark"
|
||||
case let .history(id):
|
||||
return "statuses/\(id)/history"
|
||||
case let .translate(id, _):
|
||||
return "statuses/\(id)/translate"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +62,11 @@ public enum Statuses: Endpoint {
|
|||
return makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil)
|
||||
case let .favoritedBy(_, maxId):
|
||||
return makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil)
|
||||
case let .translate(_, lang):
|
||||
if let lang {
|
||||
return [.init(name: "lang", value: lang)]
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -75,16 +75,15 @@ struct StatusRowContextMenu: View {
|
|||
Label("status.action.copy-text", systemImage: "doc.on.doc")
|
||||
}
|
||||
|
||||
if let lang = preferences.serverPreferences?.postLanguage ?? Locale.current.language.languageCode?.identifier,
|
||||
let statusLanguage = viewModel.status.reblog?.language ?? viewModel.status.language,
|
||||
statusLanguage != lang
|
||||
if let lang = preferences.serverPreferences?.postLanguage ?? Locale.current.language.languageCode?.identifier
|
||||
{
|
||||
Button {
|
||||
Task {
|
||||
await viewModel.translate(userLang: lang)
|
||||
}
|
||||
} label: {
|
||||
if let languageName = Locale.current.localizedString(forLanguageCode: statusLanguage) {
|
||||
if let statusLang = viewModel.status.language,
|
||||
let languageName = Locale.current.localizedString(forLanguageCode: statusLang) {
|
||||
Label("status.action.translate-from-\(languageName)", systemImage: "captions.bubble")
|
||||
} else {
|
||||
Label("status.action.translate", systemImage: "captions.bubble")
|
||||
|
|
|
@ -375,9 +375,9 @@ public struct StatusRowView: View {
|
|||
if let translation = viewModel.translation, !viewModel.isLoadingTranslation {
|
||||
GroupBox {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(translation)
|
||||
Text(translation.content.asSafeMarkdownAttributedString)
|
||||
.font(.scaledBody)
|
||||
Text("status.action.translated-label")
|
||||
Text("status.action.translated-label-\(translation.provider)")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ public class StatusRowViewModel: ObservableObject {
|
|||
@Published var isFiltered: Bool = false
|
||||
@Published var isLoadingRemoteContent: Bool = false
|
||||
|
||||
@Published var translation: String?
|
||||
@Published var translation: StatusTranslation?
|
||||
@Published var isLoadingTranslation: Bool = false
|
||||
@Published var showDeleteAlert: Bool = false
|
||||
|
||||
|
@ -292,18 +292,28 @@ public class StatusRowViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func translate(userLang: String) async {
|
||||
let client = DeepLClient()
|
||||
guard let client else { return }
|
||||
do {
|
||||
withAnimation {
|
||||
isLoadingTranslation = true
|
||||
}
|
||||
let translation = try await client.request(target: userLang,
|
||||
source: status.language,
|
||||
text: status.reblog?.content.asRawText ?? status.content.asRawText)
|
||||
// We first use instance translation API if available.
|
||||
let translation: StatusTranslation = try await client.post(endpoint: Statuses.translate(id: status.reblog?.id ?? status.id,
|
||||
lang: userLang))
|
||||
withAnimation {
|
||||
self.translation = translation
|
||||
isLoadingTranslation = false
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
// If not or fail we use Ice Cubes own DeepL client.
|
||||
let deepLClient = DeepLClient()
|
||||
let translation = try? await deepLClient.request(target: userLang,
|
||||
source: status.language,
|
||||
text: status.reblog?.content.asRawText ?? status.content.asRawText)
|
||||
withAnimation {
|
||||
self.translation = translation
|
||||
isLoadingTranslation = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue