Translate Toot using DeepL close #153

This commit is contained in:
Thomas Ricouard 2023-01-21 09:58:38 +01:00
parent e375d792a6
commit 523cb48cd1
9 changed files with 148 additions and 34 deletions

View file

@ -243,6 +243,8 @@
"timeline.trending" = "Im Trend";
// MARK: Package: Status
"status.action.translate" = "Übersetzen";
"status.action.translated-label" = "Übersetzt mit DeepL.com";
"status.action.bookmark" = "Lesezeichen";
"status.action.boost" = "Boosten";
"status.action.copy-text" = "Text kopieren";

View file

@ -243,6 +243,8 @@
"timeline.trending" = "Trending";
// MARK: Package: Status
"status.action.translate" = "Translate";
"status.action.translated-label" = "Translated using DeepL.com";
"status.action.bookmark" = "Bookmark";
"status.action.boost" = "Boost";
"status.action.copy-text" = "Copy Text";

View file

@ -242,6 +242,8 @@
"timeline.trending" = "Tendencia";
// MARK: Package: Status
"status.action.translate" = "Traducir";
"status.action.translated-label" = "Traducido usando DeepL.com";
"status.action.bookmark" = "Añadir a marcadores";
"status.action.boost" = "Boostear";
"status.action.copy-text" = "Copiar texto";

View file

@ -243,6 +243,8 @@
"timeline.trending" = "Trending";
// MARK: Package: Status
"status.action.translate" = "Vertalen";
"status.action.translated-label" = "Vertaald met behulp van DeepL.com";
"status.action.bookmark" = "Bladwijzer";
"status.action.boost" = "Boosten";
"status.action.copy-text" = "Tekst Kopiëren";

View file

@ -4,5 +4,7 @@
<dict>
<key>OPENAI_SECRET</key>
<string>NICE_TRY</string>
<key>DEEPL_SECRET</key>
<string>NICE_TRY_AGAIN</string>
</dict>
</plist>

View file

@ -0,0 +1,52 @@
import Foundation
public struct DeepLClient {
private let endpoint = "https://api-free.deepl.com/v2/translate"
private var APIKey: String {
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
let secret = NSDictionary(contentsOfFile: path)
return secret?["DEEPL_SECRET"] as? String ?? ""
}
return ""
}
private var authorizationHeaderValue: String {
"DeepL-Auth-Key \(APIKey)"
}
public struct Response: Decodable {
public struct Translation: Decodable {
public let detectedSourceLanguage: String
public let text: String
}
public let translations: [Translation]
}
private var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}
public init() {}
public func request(target: String, source: String?, text: String) async throws -> String {
do {
var components = URLComponents(string: endpoint)!
var queryItems: [URLQueryItem] = []
queryItems.append(.init(name: "text", value: text))
queryItems.append(.init(name: "target_lang", value: target.uppercased()))
components.queryItems = queryItems
var request = URLRequest(url: components.url!)
request.httpMethod = "POST"
request.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization")
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 ?? ""
} catch {
throw error
}
}
}

View file

@ -199,45 +199,15 @@ public struct StatusRowView: View {
})
Spacer()
}
if !reasons.contains(.placeholder) {
if !viewModel.isCompact, !viewModel.isEmbedLoading, let embed = viewModel.embeddedStatus {
StatusEmbeddedView(status: embed)
} else if viewModel.isEmbedLoading, !viewModel.isCompact {
StatusEmbeddedView(status: .placeholder())
.redacted(reason: .placeholder)
.shimmering()
}
}
makeTranslateView(status: status)
if let poll = status.poll {
StatusPollView(poll: poll)
}
if !status.mediaAttachments.isEmpty {
if theme.statusDisplayStyle == .compact {
HStack {
StatusMediaPreviewView(attachments: status.mediaAttachments,
sensitive: status.sensitive,
isNotifications: viewModel.isCompact)
Spacer()
}
.padding(.vertical, 4)
} else {
StatusMediaPreviewView(attachments: status.mediaAttachments,
sensitive: status.sensitive,
isNotifications: viewModel.isCompact)
.padding(.vertical, 4)
}
}
if let card = status.card,
viewModel.embeddedStatus?.url != status.card?.url.absoluteString,
status.mediaAttachments.isEmpty,
!viewModel.isEmbedLoading,
theme.statusDisplayStyle == .large
{
StatusCardView(card: card)
}
makeMediasView(status: status)
makeCardView(status: status)
}
}
}
@ -275,4 +245,68 @@ public struct StatusRowView: View {
.foregroundColor(.gray)
.contentShape(Rectangle())
}
@ViewBuilder
private func makeTranslateView(status: AnyStatus) -> some View {
if let userLang = preferences.serverPreferences?.postLanguage,
status.language != nil,
userLang != status.language,
!status.content.asRawText.isEmpty,
viewModel.translation == nil {
Button {
Task {
await viewModel.translate(userLang: userLang)
}
} label: {
if viewModel.isLoadingTranslation {
ProgressView()
} else {
Text("status.action.translate")
}
}
} else if let translation = viewModel.translation {
GroupBox {
VStack(alignment: .leading, spacing: 4) {
Text(translation)
.font(.scaledBody)
Text("status.action.translated-label")
.font(.footnote)
.foregroundColor(.gray)
}
}
.fixedSize(horizontal: false, vertical: true)
}
}
@ViewBuilder
private func makeMediasView(status: AnyStatus) -> some View {
if !status.mediaAttachments.isEmpty {
if theme.statusDisplayStyle == .compact {
HStack {
StatusMediaPreviewView(attachments: status.mediaAttachments,
sensitive: status.sensitive,
isNotifications: viewModel.isCompact)
Spacer()
}
.padding(.vertical, 4)
} else {
StatusMediaPreviewView(attachments: status.mediaAttachments,
sensitive: status.sensitive,
isNotifications: viewModel.isCompact)
.padding(.vertical, 4)
}
}
}
@ViewBuilder
private func makeCardView(status: AnyStatus) -> some View {
if let card = status.card,
viewModel.embeddedStatus?.url != status.card?.url.absoluteString,
status.mediaAttachments.isEmpty,
!viewModel.isEmbedLoading,
theme.statusDisplayStyle == .large
{
StatusCardView(card: card)
}
}
}

View file

@ -22,6 +22,9 @@ public class StatusRowViewModel: ObservableObject {
@Published var displaySpoiler: Bool = false
@Published var isEmbedLoading: Bool = true
@Published var isFiltered: Bool = false
@Published var translation: String?
@Published var isLoadingTranslation: Bool = false
var filter: Filtered? {
status.reblog?.filtered?.first ?? status.filtered?.first
@ -220,4 +223,18 @@ public class StatusRowViewModel: ObservableObject {
reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
repliesCount = status.reblog?.repliesCount ?? status.repliesCount
}
func translate(userLang: String) async {
let client = DeepLClient()
do {
withAnimation {
isLoadingTranslation = true
}
let translation = try await client.request(target: userLang, source: status.language, text: status.content.asRawText)
withAnimation {
isLoadingTranslation = false
self.translation = translation
}
} catch {}
}
}

View file

@ -2,5 +2,6 @@
cd ../IceCubesApp/
plutil -replace OPENAI_SECRET -string $OPENAI_SECRET Secret.plist
plutil -replace DEEPL_SECRET -string $DEEPL_SECRET Secret.plist
plutil -p Secret.plist
exit 0