Translate Toot using DeepL close #153
This commit is contained in:
parent
e375d792a6
commit
523cb48cd1
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -4,5 +4,7 @@
|
|||
<dict>
|
||||
<key>OPENAI_SECRET</key>
|
||||
<string>NICE_TRY</string>
|
||||
<key>DEEPL_SECRET</key>
|
||||
<string>NICE_TRY_AGAIN</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue