Add the ability to translate using deepl even if the instance offers its own service (#1237)

* Allow forced translation with DeepL

Translation with DeepL can now be forced either per post or on the system level.

Signed-off-by: Paul Schuetz <pa.schuetz@web.de>

* Require the use of a private API key

A private API key of the user is now required to allow "always translate via
DeepL".

Signed-off-by: Paul Schuetz <pa.schuetz@web.de>

* Persist a stored API key

An API key is stored even if useOnlyDeepL is disabled. If the API key is empty,
the setting is still disabled.

Signed-off-by: Paul Schuetz <pa.schuetz@web.de>

* Localize the texts

Signed-off-by: Paul Schuetz <pa.schuetz@web.de>

* Save API key while writing

The API key is now saved, even if the app is closed before leaving the
translation settings view.

Signed-off-by: Paul Schuetz <pa.schuetz@web.de>

* Fix build

* Fix theme

* Transition to KeychainSwift, clean up

KeychainHelper is replaced with the already-used KeychainSwift package, the
functions are cleaned up so that the process is easier to understand. The
deactivateToggleIfNoKey function doesn't change the behavior of the buttons or
context menus in the timeline, only demonstrates the necessity of an API key to
the user. Consequently, it's only called when the settings view is shown.

Signed-off-by: Paul Schuetz <pa.schuetz@web.de>

* Swiftformat + fixes

---------

Signed-off-by: Paul Schuetz <pa.schuetz@web.de>
Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
This commit is contained in:
Paul Schuetz 2023-03-14 18:50:19 +01:00 committed by GitHub
parent f263c57858
commit baf853f46e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 306 additions and 25 deletions

View file

@ -65,6 +65,7 @@
9F7D939A29805DBD00EE6B7A /* AccountSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */; };
9F8CA5972979B61100481E8E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9B576C529743F4C00BCE646 /* Localizable.strings */; };
9F8CA5982979B63D00481E8E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9B576C529743F4C00BCE646 /* Localizable.strings */; };
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */; };
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; };
9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; };
9FAD858E29743F7400496AB1 /* (null) in Resources */ = {isa = PBXBuildFile; };
@ -225,6 +226,7 @@
9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingView.swift; sourceTree = "<group>"; };
9F7D939B2980F5C100EE6B7A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
9F7D939C2980F5C200EE6B7A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; };
9FAD85822971BF7200496AB1 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Secret.plist; sourceTree = "<group>"; };
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
9FAD858A29743F7400496AB1 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
@ -524,6 +526,7 @@
9FAE4ACA293783B000772766 /* SettingsTab.swift */,
9F2A540629699698009B2D7C /* SupportAppView.swift */,
D08A9C3429956CFA00204A4A /* SwipeActionsSettingsView.swift */,
9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */,
);
path = Settings;
sourceTree = "<group>";
@ -819,6 +822,7 @@
069709AA298C9AD7006E4CB5 /* AboutView.swift in Sources */,
9F2B92FC295DA94500DE16D0 /* InstanceInfoView.swift in Sources */,
C9B22677297F6C2E001F9EFE /* ContentSettingsView.swift in Sources */,
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */,
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */,
9FAD85CF2975B68900496AB1 /* SideBarView.swift in Sources */,
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */,

View file

@ -145,6 +145,9 @@ struct SettingsTabs: View {
NavigationLink(destination: SwipeActionsSettingsView()) {
Label("settings.general.swipeactions", systemImage: "hand.draw")
}
NavigationLink(destination: TranslationSettingsView()) {
Label("settings.general.translate", systemImage: "captions.bubble")
}
Link(destination: URL(string: UIApplication.openSettingsURLString)!) {
Label("settings.system", systemImage: "gear")
}

View file

@ -0,0 +1,74 @@
import DesignSystem
import Env
import SwiftUI
struct TranslationSettingsView: View {
@EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var theme: Theme
@State private var apiKey: String = ""
var body: some View {
Form {
Toggle(isOn: preferences.$alwaysUseDeepl) {
Label("settings.translation.always-deepl", systemImage: "captions.bubble")
}
.listRowBackground(theme.primaryBackgroundColor)
if preferences.alwaysUseDeepl {
Section("settings.translation.user-api-key") {
Picker("settings.translation.api-key-type", selection: $preferences.userDeeplAPIFree) {
Text("DeepL API Free").tag(true)
Text("DeepL API Pro").tag(false)
}
SecureField("settings.translation.user-api-key", text: $apiKey)
.textContentType(.password)
}
.onAppear(perform: readValue)
.listRowBackground(theme.primaryBackgroundColor)
if apiKey.isEmpty {
Section {
Link(destination: URL(string: "https://www.deepl.com/pro-api")!) {
Text("settings.translation.needed-message")
.foregroundColor(.red)
}
}
.listRowBackground(theme.primaryBackgroundColor)
}
}
}
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.onChange(of: apiKey, perform: writeNewValue)
.onAppear(perform: updatePrefs)
}
private func writeNewValue() {
writeNewValue(value: apiKey)
}
private func writeNewValue(value: String) {
DeepLUserAPIHandler.write(value: value)
}
private func readValue() {
if let apiKey = DeepLUserAPIHandler.readIfAllowed() {
self.apiKey = apiKey
} else {
apiKey = ""
}
}
private func updatePrefs() {
DeepLUserAPIHandler.deactivateToggleIfNoKey()
}
}
struct TranslationSettingsView_Previews: PreviewProvider {
static var previews: some View {
TranslationSettingsView()
.environmentObject(UserPreferences.shared)
}
}

View file

@ -165,6 +165,11 @@
"settings.other.hide-openai" = "Уключыць 🤖 памочніка";
"settings.other.social-keyboard" = "Уключыць сацыяльную клавіятуру";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Выпраўляльнік дублікатаў апавяшчэнняў";
"settings.push.duplicate.footer" = "Атрымліваеш падвоеныя апавяшчэнні? Паспрабуй гэтую чароўную кнопку каб выправіць";
"settings.push.duplicate.button.fix" = "🪄 Выправіць";
@ -390,6 +395,7 @@
// MARK: Package: Status
"status.action.translate" = "Перакласці";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Пераклад з дапамогай %@";
"status.action.translated-label-from-%@-%@" = "Translated from %@ using %@";
"status.action.bookmark" = "Закладка";

View file

@ -160,6 +160,11 @@
"settings.other.hide-openai" = "Activa l'ajudant 🤖";
"settings.other.social-keyboard" = "Activa el teclat social";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Solucionador de notificacions duplicades";
"settings.push.duplicate.footer" = "Rebeu les notificacions duplicades? Proveu aquest botó màgic per a solucionar-ho";
"settings.push.duplicate.button.fix" = "🪄 Soluciona-ho";
@ -384,6 +389,7 @@
// MARK: Package: Status
"status.action.translate" = "Tradueix";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Traduït amb %@";
"status.action.translated-label-from-%@-%@" = "Translated from %@ using %@";
"status.action.bookmark" = "Afegeix als marcadors";

View file

@ -141,6 +141,11 @@
"settings.other.hide-openai" = "Aktiviere 🤖-Helfer";
"settings.other.social-keyboard" = "Soziale Tastatur aktivieren";
"settings.other.sound-effect" = "Klänge aktivieren";
"settings.general.translate" = "Übersetzungseinstellungen";
"settings.translation.always-deepl" = "Immer mit DeepL übersetzen";
"settings.translation.user-api-key" = "DeepL API Schlüssel";
"settings.translation.api-key-type" = "Typ des Schlüssels";
"settings.translation.needed-message" = "Für diese Funktion ist ein DeepL API-Schlüssel erforderlich.";
"settings.general.content" = "Inhaltseinstellungen";
"settings.system" = "Systemeinstellungen";
"settings.content.navigation-title" = "Inhaltseinstellungen";
@ -381,6 +386,7 @@
// MARK: Package: Status
"status.action.translate" = "Übersetzen";
"status.action.translate-with-deepl" = "Mit DeepL übersetzen";
"status.action.translated-label-%@" = "Übersetzt mit %@";
"status.action.translated-label-from-%@-%@" = "Aus %@ mit %@ übersetzt";
"status.action.bookmark" = "Lesezeichen setzen";

View file

@ -166,6 +166,11 @@
"settings.other.hide-openai" = "Enable 🤖 Helper";
"settings.other.social-keyboard" = "Enable Social Keyboard";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Duplicate Notifications Fixer";
"settings.push.duplicate.footer" = "Receiving duplicate notifications? Try this magic button in order to fix it";
"settings.push.duplicate.button.fix" = "🪄 Fix It";
@ -387,6 +392,7 @@
// MARK: Package: Status
"status.action.translate" = "Translate";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Translated using %@";
"status.action.translated-label-from-%@-%@" = "Translated from %@ using %@";
"status.action.bookmark" = "Bookmark";

View file

@ -165,6 +165,11 @@
"settings.other.hide-openai" = "Enable 🤖 Helper";
"settings.other.social-keyboard" = "Enable Social Keyboard";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Duplicate Notifications Fixer";
"settings.push.duplicate.footer" = "Receiving duplicate notifications? Try this magic button in order to fix it";
"settings.push.duplicate.button.fix" = "🪄 Fix It";
@ -386,6 +391,7 @@
// MARK: Package: Status
"status.action.translate" = "Translate";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Translated using %@";
"status.action.translated-label-from-%@-%@" = "Translated from %@ using %@";
"status.action.bookmark" = "Bookmark";

View file

@ -141,6 +141,11 @@
"settings.other.hide-openai" = "Activar ayudante 🤖";
"settings.other.social-keyboard" = "Activar teclado social";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.general.content" = "Ajustes de contenido";
"settings.system" = "Ajustes del sistema";
"settings.content.navigation-title" = "Ajustes de contenido";
@ -386,6 +391,7 @@
// MARK: Package: Status
"status.action.translate" = "Traducir";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Traducido usando %@";
"status.action.translated-label-from-%@-%@" = "Traducido desde %@ usando %@";
"status.action.bookmark" = "Añadir a marcadores";

View file

@ -141,6 +141,11 @@
"settings.other.hide-openai" = "Gaitu 🤖 laguntzailea";
"settings.other.social-keyboard" = "Gaitu teklatu soziala";
"settings.other.sound-effect" = "Gaitu soinu efektuak";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.general.content" = "Edukiaren ezarpenak";
"settings.system" = "Sistemaren ezarpenak";
"settings.content.navigation-title" = "Edukiaren ezarpenak";
@ -379,6 +384,7 @@
// MARK: Package: Status
"status.action.translate" = "Itzuli";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "%@ erabiliz itzulia";
"status.action.translated-label-from-%@-%@" = "%@(e)tik %@ erabiliz itzulia";
"status.action.bookmark" = "Jarri laster-marka";

View file

@ -161,6 +161,11 @@
"settings.other.hide-openai" = "Activer 🤖 aide";
"settings.other.social-keyboard" = "Activer le clavier social";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Correcteur de notifications en double";
"settings.push.duplicate.footer" = "Recevez-vous des notifications en double ? Essayez ce bouton magique pour résoudre le problème";
"settings.push.duplicate.button.fix" = "🪄 Résoudre";
@ -381,6 +386,7 @@
// MARK: Package: Status
"status.action.translate" = "Traduire";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Traduit avec %@";
"status.action.translated-label-from-%@-%@" = "Translated from %@ using %@";
"status.action.bookmark" = "Marquer";

View file

@ -39,7 +39,6 @@
"enum.status-actions-display.only-buttons" = "Solo bottoni";
"enum.status-display-style.compact" = "Compatto";
"enum.status-display-style.large" = "Completo";
"enum.status-display-style.medium" = "Medio";
"enum.swipeactions.icon-with-text" = "Icon e testo";
"enum.swipeactions.icon-only" = "Solo icone";
@ -141,6 +140,11 @@
"settings.other.hide-openai" = "Abilita l'aiuto del 🤖";
"settings.other.social-keyboard" = "Abilita social keyboard";
"settings.other.sound-effect" = "Attiva gli effetti sonori";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.general.content" = "Impostazioni dei contenuti";
"settings.system" = "Vai alle impostazioni di sistema";
"settings.content.navigation-title" = "Impostazioni dei contenuti";
@ -386,6 +390,7 @@
// MARK: Package: Status
"status.action.translate" = "Traduci";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Tradotto usando %@";
"status.action.translated-label-from-%@-%@" = "Tradotto da %@ usando %@";
"status.action.bookmark" = "Salva nei segnalibri";

View file

@ -165,6 +165,11 @@
"settings.other.hide-openai" = "AI支援機能の有効化";
"settings.other.social-keyboard" = "ソーシャルメディア向けキーボードの有効化";
"settings.other.sound-effect" = "サウンドエフェクトを有効化";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "重複通知修正ツール";
"settings.push.duplicate.footer" = "重複して通知を受け取っていませんか?修正するためにこの魔法のボタンを試してみて";
"settings.push.duplicate.button.fix" = "🪄 修正する";
@ -385,6 +390,7 @@
// MARK: Package: Status
"status.action.translate" = "翻訳";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "%@ を使用して翻訳";
"status.action.translated-label-from-%@-%@" = "%@ から %@ を使用して翻訳";
"status.action.bookmark" = "ブックマーク";

View file

@ -161,6 +161,11 @@
"settings.other.hide-openai" = "글 작성 도우미 🤖";
"settings.other.social-keyboard" = "SNS 키보드";
"settings.other.sound-effect" = "효과음";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "중복 알림 해결사";
"settings.push.duplicate.footer" = "같은 알림이 여러 번 오나요? 위에 있는 버튼을 누르면 마법처럼 해결될 거에요.";
"settings.push.duplicate.button.fix" = "🪄 고치기";
@ -387,6 +392,7 @@
// MARK: Package: Status
"status.action.translate" = "번역";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "%@ 서비스를 통해 번역됨";
"status.action.translated-label-from-%@-%@" = "%2$@ 서비스를 통해 %1$@에서 번역됨";
"status.action.bookmark" = "보관함에 추가";

View file

@ -165,6 +165,11 @@
"settings.other.hide-openai" = "Aktiver 🤖-hjelper";
"settings.other.social-keyboard" = "Aktiver sosialt tastatur";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Reparasjon av dupliserte varslinger";
"settings.push.duplicate.footer" = "Får du dupliserte varsler? Prøv denne magiske knappen for å fikse det.";
"settings.push.duplicate.button.fix" = "🪄 Fiks det";
@ -385,6 +390,7 @@
// MARK: Package: Status
"status.action.translate" = "Oversett";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Oversatt ved hjelp av %@";
"status.action.translated-label-from-%@-%@" = "Translated from %@ using %@";
"status.action.bookmark" = "Bokmerk";

View file

@ -141,6 +141,11 @@
"settings.other.hide-openai" = "Gebruik 🤖-hulp";
"settings.other.social-keyboard" = "Gebruik socialmedia-toetsenbord";
"settings.other.sound-effect" = "Geluidseffecten";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.general.content" = "Inhoud";
"settings.system" = "Systeeminstellingen";
"settings.content.navigation-title" = "Inhoud";
@ -379,6 +384,7 @@
// MARK: Package: Status
"status.action.translate" = "Vertaal";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Vertaald met behulp van %@";
"status.action.translated-label-from-%@-%@" = "Vertaald uit het %@ met behulp van %@";
"status.action.bookmark" = "Voeg bladwijzer toe";

View file

@ -162,6 +162,11 @@
"settings.other.social-keyboard" = "Włącz klawiaturę społecznościową";
"settings.other.sound-effect" = "Włącz efekty dźwiękowe";
"settings.push.duplicate.title" = "Korektor duplikatów powiadomień";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.footer" = "Otrzymujesz zduplikowane powiadomienia? Spróbuj tego magicznego przycisku, aby to naprawić";
"settings.push.duplicate.button.fix" = "🪄 Napraw to";
"settings.other.autoplay-video" = "Odtwarzaj filmy automatycznie";
@ -381,6 +386,7 @@
// MARK: Package: Status
"status.action.translate" = "Przetłumacz";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Przetłumaczono za pomocą %@";
"status.action.translated-label-from-%@-%@" = "Tekst %@ przetłumaczono za pomocą %@";
"status.action.bookmark" = "Dodaj zakładkę";

View file

@ -161,6 +161,11 @@
"settings.other.hide-openai" = "Habilitar 🤖 ajudante";
"settings.other.social-keyboard" = "Habilitar Teclado Social";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Corretor de notificações duplicadas";
"settings.push.duplicate.footer" = "Recebendo notificações duplicadas? Tente este botão mágico para tentar corrigir";
"settings.push.duplicate.button.fix" = "🪄 Corrigir";
@ -385,6 +390,7 @@
// MARK: Package: Status
"status.action.translate" = "Traduzir";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Traduzir usando %@";
"status.action.translated-label-from-%@-%@" = "Traduzido de %@ usando %@";
"status.action.bookmark" = "Salvar";

View file

@ -161,6 +161,11 @@
"settings.other.hide-openai" = "Yardımcıyı 🤖 Aktive Et";
"settings.other.social-keyboard" = "Sosyal Klavyeyi Aktive Et";
"settings.other.sound-effect" = "Enable Sound Effects";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Duplicate notifications fixer";
"settings.push.duplicate.footer" = "Receiving duplicate notifications? Try this magic button in order to fix it";
"settings.push.duplicate.button.fix" = "🪄 Fix it";
@ -381,6 +386,7 @@
// MARK: Package: Status
"status.action.translate" = "Tercüme et";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "%@ tarafından tercüme edildi";
"status.action.translated-label-from-%@-%@" = "Translated from %@ using %@";
"status.action.bookmark" = "Yer İmi Ekle";

View file

@ -165,6 +165,11 @@
"settings.other.hide-openai" = "Увімкнути 🤖 помічника";
"settings.other.social-keyboard" = "Увімкнути Social Keyboard";
"settings.other.sound-effect" = "Увімкнути звукові ефекти";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.push.duplicate.title" = "Виправити задвоєння сповіщень";
"settings.push.duplicate.footer" = "Отримуєте сповіщення двічі? Спробуйте цю чарівну кнопку, щоб виправити це!";
"settings.push.duplicate.button.fix" = "🪄 Виправити!";
@ -386,6 +391,7 @@
// MARK: Package: Status
"status.action.translate" = "Перекласти";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "Переклад з допомогою %@";
"status.action.translated-label-from-%@-%@" = "Переклад з %@ з допомогою %@";
"status.action.bookmark" = "У закладки";

View file

@ -141,6 +141,11 @@
"settings.other.hide-openai" = "启用写作助手 🤖";
"settings.other.social-keyboard" = "启用社交键盘";
"settings.other.sound-effect" = "启用声音效果";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
"settings.general.content" = "内容设置";
"settings.system" = "系统设置";
@ -384,6 +389,7 @@
// MARK: Package: Status
"status.action.translate" = "翻译";
"status.action.translate-with-deepl" = "Translate with DeepL";
"status.action.translated-label-%@" = "由 %@ 翻译";
"status.action.translated-label-from-%@-%@" = "翻译自 %@,使用 %@";
"status.action.bookmark" = "书签";

View file

@ -209,6 +209,12 @@
"settings.section.cache" = "快取";
"settings.cache-media.clear" = "清除媒體快取";
"settings.general.translate" = "Translation Settings";
"settings.translation.always-deepl" = "Always Translate using DeepL";
"settings.translation.user-api-key" = "DeepL API Key";
"settings.translation.api-key-type" = "Type of the Key";
"settings.translation.needed-message" = "This feature requires a DeepL API key";
// MARK: Tabs
"tab.explore" = "探索";
"tab.federated" = "聯邦";

View file

@ -0,0 +1,43 @@
import Foundation
import KeychainSwift
import SwiftUI
@MainActor
public enum DeepLUserAPIHandler {
private static let key = "DeepL"
private static var keychain: KeychainSwift {
let keychain = KeychainSwift()
#if !DEBUG && !targetEnvironment(simulator)
keychain.accessGroup = AppInfo.keychainGroup
#endif
return keychain
}
public static func write(value: String) {
keychain.synchronizable = true
if !value.isEmpty {
keychain.set(value, forKey: key)
} else {
keychain.delete(key)
}
}
public static func readIfAllowed() -> String? {
guard UserPreferences.shared.alwaysUseDeepl else { return nil }
return readValue()
}
private static func readValue() -> String? {
keychain.synchronizable = true
return keychain.get(key)
}
public static func deactivateToggleIfNoKey() {
UserPreferences.shared.alwaysUseDeepl = shouldAlwaysUseDeepl
}
public static var shouldAlwaysUseDeepl: Bool {
readIfAllowed() != nil
}
}

View file

@ -26,6 +26,8 @@ public class UserPreferences: ObservableObject {
@AppStorage("app_default_post_visibility") public var appDefaultPostVisibility: Models.Visibility = .pub
@AppStorage("app_default_posts_sensitive") public var appDefaultPostsSensitive = false
@AppStorage("autoplay_video") public var autoPlayVideo = true
@AppStorage("always_use_deepl") public var alwaysUseDeepl = false
@AppStorage("user_deepl_api_free") public var userDeeplAPIFree = true
@AppStorage("suppress_dupe_reblogs") public var suppressDupeReblogs: Bool = false

View file

@ -6,9 +6,17 @@ public struct DeepLClient {
case notFound
}
private let endpoint = "https://api.deepl.com/v2/translate"
private var deeplUserAPIKey: String?
private var deeplUserAPIFree: Bool
private var endpoint: String {
"https://api\(deeplUserAPIFree && (deeplUserAPIKey != nil) ? "-free" : "").deepl.com/v2/translate"
}
private var APIKey: String {
if let deeplUserAPIKey {
return deeplUserAPIKey
}
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
let secret = NSDictionary(contentsOfFile: path)
return secret?["DEEPL_SECRET"] as? String ?? ""
@ -35,9 +43,12 @@ public struct DeepLClient {
return decoder
}
public init() {}
public init(userAPIKey: String?, userAPIFree: Bool) {
deeplUserAPIKey = userAPIKey
deeplUserAPIFree = userAPIFree
}
public func request(target: String, source _: String?, text: String) async throws -> StatusTranslation {
public func request(target: String, text: String) async throws -> StatusTranslation {
do {
var components = URLComponents(string: endpoint)!
var queryItems: [URLQueryItem] = []

View file

@ -288,28 +288,49 @@ public class StatusRowViewModel: ObservableObject {
}
func translate(userLang: String) async {
do {
withAnimation {
isLoadingTranslation = true
}
// We first use instance translation API if available.
let translation: StatusTranslation = try await client.post(endpoint: Statuses.translate(id: finalStatus.id,
lang: userLang))
withAnimation {
self.translation = translation
isLoadingTranslation = false
}
} catch {
// If not or fail we use Ice Cubes own DeepL client.
let deepLClient = DeepLClient()
let translation = try? await deepLClient.request(target: userLang,
source: finalStatus.language,
text: finalStatus.content.asRawText)
withAnimation {
self.translation = translation
isLoadingTranslation = false
}
if !alwaysTranslateWithDeepl {
do {
withAnimation {
isLoadingTranslation = true
}
// We first use instance translation API if available.
let translation: StatusTranslation = try await client.post(endpoint: Statuses.translate(id: finalStatus.id,
lang: userLang))
withAnimation {
self.translation = translation
isLoadingTranslation = false
}
return
} catch {}
}
// If not or fail we use Ice Cubes own DeepL client.
await translateWithDeepL(userLang: userLang)
}
func translateWithDeepL(userLang: String) async {
let deepLClient = getDeepLClient()
let translation = try? await deepLClient.request(target: userLang,
text: finalStatus.content.asRawText)
withAnimation {
self.translation = translation
isLoadingTranslation = false
}
}
private func getDeepLClient() -> DeepLClient {
let userAPIfree = UserPreferences.shared.userDeeplAPIFree
return DeepLClient(userAPIKey: userAPIKey, userAPIFree: userAPIfree)
}
private var userAPIKey: String? {
DeepLUserAPIHandler.readIfAllowed()
}
var alwaysTranslateWithDeepl: Bool {
DeepLUserAPIHandler.shouldAlwaysUseDeepl
}
func fetchRemoteStatus() async -> Bool {

View file

@ -137,6 +137,16 @@ struct StatusRowContextMenu: View {
} label: {
Label("status.action.translate", systemImage: "captions.bubble")
}
if viewModel.alwaysTranslateWithDeepl {
Button {
Task {
await viewModel.translateWithDeepL(userLang: lang)
}
} label: {
Label("status.action.translate-with-deepl", systemImage: "captions.bubble")
}
}
}
if account.account?.id == viewModel.status.reblog?.account.id ?? viewModel.status.account.id {