mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-22 00:11:00 +00:00
Translate Toot using DeepL close #153
This commit is contained in:
parent
e375d792a6
commit
523cb48cd1
9 changed files with 148 additions and 34 deletions
|
@ -243,6 +243,8 @@
|
||||||
"timeline.trending" = "Im Trend";
|
"timeline.trending" = "Im Trend";
|
||||||
|
|
||||||
// MARK: Package: Status
|
// MARK: Package: Status
|
||||||
|
"status.action.translate" = "Übersetzen";
|
||||||
|
"status.action.translated-label" = "Übersetzt mit DeepL.com";
|
||||||
"status.action.bookmark" = "Lesezeichen";
|
"status.action.bookmark" = "Lesezeichen";
|
||||||
"status.action.boost" = "Boosten";
|
"status.action.boost" = "Boosten";
|
||||||
"status.action.copy-text" = "Text kopieren";
|
"status.action.copy-text" = "Text kopieren";
|
||||||
|
|
|
@ -243,6 +243,8 @@
|
||||||
"timeline.trending" = "Trending";
|
"timeline.trending" = "Trending";
|
||||||
|
|
||||||
// MARK: Package: Status
|
// MARK: Package: Status
|
||||||
|
"status.action.translate" = "Translate";
|
||||||
|
"status.action.translated-label" = "Translated using DeepL.com";
|
||||||
"status.action.bookmark" = "Bookmark";
|
"status.action.bookmark" = "Bookmark";
|
||||||
"status.action.boost" = "Boost";
|
"status.action.boost" = "Boost";
|
||||||
"status.action.copy-text" = "Copy Text";
|
"status.action.copy-text" = "Copy Text";
|
||||||
|
|
|
@ -242,6 +242,8 @@
|
||||||
"timeline.trending" = "Tendencia";
|
"timeline.trending" = "Tendencia";
|
||||||
|
|
||||||
// MARK: Package: Status
|
// MARK: Package: Status
|
||||||
|
"status.action.translate" = "Traducir";
|
||||||
|
"status.action.translated-label" = "Traducido usando DeepL.com";
|
||||||
"status.action.bookmark" = "Añadir a marcadores";
|
"status.action.bookmark" = "Añadir a marcadores";
|
||||||
"status.action.boost" = "Boostear";
|
"status.action.boost" = "Boostear";
|
||||||
"status.action.copy-text" = "Copiar texto";
|
"status.action.copy-text" = "Copiar texto";
|
||||||
|
|
|
@ -243,6 +243,8 @@
|
||||||
"timeline.trending" = "Trending";
|
"timeline.trending" = "Trending";
|
||||||
|
|
||||||
// MARK: Package: Status
|
// MARK: Package: Status
|
||||||
|
"status.action.translate" = "Vertalen";
|
||||||
|
"status.action.translated-label" = "Vertaald met behulp van DeepL.com";
|
||||||
"status.action.bookmark" = "Bladwijzer";
|
"status.action.bookmark" = "Bladwijzer";
|
||||||
"status.action.boost" = "Boosten";
|
"status.action.boost" = "Boosten";
|
||||||
"status.action.copy-text" = "Tekst Kopiëren";
|
"status.action.copy-text" = "Tekst Kopiëren";
|
||||||
|
|
|
@ -4,5 +4,7 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>OPENAI_SECRET</key>
|
<key>OPENAI_SECRET</key>
|
||||||
<string>NICE_TRY</string>
|
<string>NICE_TRY</string>
|
||||||
|
<key>DEEPL_SECRET</key>
|
||||||
|
<string>NICE_TRY_AGAIN</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
52
Packages/Network/Sources/Network/DeepLClient.swift
Normal file
52
Packages/Network/Sources/Network/DeepLClient.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -199,45 +199,15 @@ public struct StatusRowView: View {
|
||||||
})
|
})
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reasons.contains(.placeholder) {
|
makeTranslateView(status: status)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let poll = status.poll {
|
if let poll = status.poll {
|
||||||
StatusPollView(poll: poll)
|
StatusPollView(poll: poll)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !status.mediaAttachments.isEmpty {
|
makeMediasView(status: status)
|
||||||
if theme.statusDisplayStyle == .compact {
|
makeCardView(status: status)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,4 +245,68 @@ public struct StatusRowView: View {
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.contentShape(Rectangle())
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
@Published var displaySpoiler: Bool = false
|
@Published var displaySpoiler: Bool = false
|
||||||
@Published var isEmbedLoading: Bool = true
|
@Published var isEmbedLoading: Bool = true
|
||||||
@Published var isFiltered: Bool = false
|
@Published var isFiltered: Bool = false
|
||||||
|
|
||||||
|
@Published var translation: String?
|
||||||
|
@Published var isLoadingTranslation: Bool = false
|
||||||
|
|
||||||
var filter: Filtered? {
|
var filter: Filtered? {
|
||||||
status.reblog?.filtered?.first ?? status.filtered?.first
|
status.reblog?.filtered?.first ?? status.filtered?.first
|
||||||
|
@ -220,4 +223,18 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
|
reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
|
||||||
repliesCount = status.reblog?.repliesCount ?? status.repliesCount
|
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 {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
|
|
||||||
cd ../IceCubesApp/
|
cd ../IceCubesApp/
|
||||||
plutil -replace OPENAI_SECRET -string $OPENAI_SECRET Secret.plist
|
plutil -replace OPENAI_SECRET -string $OPENAI_SECRET Secret.plist
|
||||||
|
plutil -replace DEEPL_SECRET -string $DEEPL_SECRET Secret.plist
|
||||||
plutil -p Secret.plist
|
plutil -p Secret.plist
|
||||||
exit 0
|
exit 0
|
||||||
|
|
Loading…
Reference in a new issue