feat(translation): add translation button, works with offline downloaded languages
This commit is contained in:
34
WorterBuch/TranslationService.swift
Normal file
34
WorterBuch/TranslationService.swift
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// TranslationService.swift
|
||||
// WorterBuch
|
||||
//
|
||||
// Created by Claude on 07.12.2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Translation
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
class TranslationService {
|
||||
static func translate(text: String, from sourceLanguage: String = "de", to targetLanguage: String = "en") async -> String? {
|
||||
do {
|
||||
let sourceLocale = Locale.Language(identifier: sourceLanguage)
|
||||
let targetLocale = Locale.Language(identifier: targetLanguage)
|
||||
|
||||
print("Attempting translation from \(sourceLanguage) to \(targetLanguage)")
|
||||
|
||||
// Create session - requires language pack to be installed
|
||||
let session = TranslationSession(installedSource: sourceLocale, target: targetLocale)
|
||||
|
||||
let response = try await session.translate(text)
|
||||
return response.targetText
|
||||
} catch {
|
||||
print("Translation error: \(error)")
|
||||
print("To enable translation:")
|
||||
print("1. Open the Translate app on your iPad")
|
||||
print("2. Download German language pack")
|
||||
print("3. Try the translate button again")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ struct VocabularyFieldCell: View {
|
||||
let drawing: PKDrawing
|
||||
let text: String
|
||||
let onTap: () -> Void
|
||||
var showTranslateButton: Bool = false
|
||||
var onTranslate: (() -> Void)? = nil
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
@@ -41,19 +43,30 @@ struct VocabularyFieldCell: View {
|
||||
}
|
||||
|
||||
// Transcribed text - selectable but not editable
|
||||
if !text.isEmpty {
|
||||
SelectableTextView(
|
||||
text: text.lowercased(),
|
||||
font: .preferredFont(forTextStyle: .body),
|
||||
textColor: .label
|
||||
)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.frame(height: 40)
|
||||
} else {
|
||||
Text(" ")
|
||||
.font(.body)
|
||||
HStack(spacing: 8) {
|
||||
if !text.isEmpty {
|
||||
SelectableTextView(
|
||||
text: text.lowercased(),
|
||||
font: .preferredFont(forTextStyle: .body),
|
||||
textColor: .label
|
||||
)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.frame(height: 40)
|
||||
} else {
|
||||
Text(" ")
|
||||
.font(.body)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.frame(height: 40)
|
||||
}
|
||||
|
||||
if showTranslateButton, let onTranslate = onTranslate {
|
||||
Button(action: onTranslate) {
|
||||
Image(systemName: "arrow.right.arrow.left.circle.fill")
|
||||
.foregroundColor(.blue)
|
||||
.font(.system(size: 20))
|
||||
}
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(8)
|
||||
|
||||
@@ -86,7 +86,8 @@ struct VocabularyGridView: View {
|
||||
entry: entry,
|
||||
onSelectField: { fieldType in
|
||||
openFieldEditor(for: entry, fieldType: fieldType)
|
||||
}
|
||||
},
|
||||
onTranslate: translateEntry
|
||||
)
|
||||
.listRowInsets(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12))
|
||||
.listRowSeparator(.hidden)
|
||||
@@ -211,11 +212,35 @@ struct VocabularyGridView: View {
|
||||
print("Error saving context: \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
private func translateEntry(_ entry: VocabularyEntry) {
|
||||
guard let germanText = entry.germanWordText, !germanText.isEmpty else { return }
|
||||
guard entry.englishTranslationText?.isEmpty ?? true else { return }
|
||||
|
||||
Task {
|
||||
if #available(iOS 18.0, *) {
|
||||
if let translation = await TranslationService.translate(text: germanText) {
|
||||
await MainActor.run {
|
||||
entry.englishTranslationText = translation
|
||||
saveContext()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print("Translation requires iOS 18.0 or later")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VocabularyEntryRow: View {
|
||||
@ObservedObject var entry: VocabularyEntry
|
||||
let onSelectField: (FieldType) -> Void
|
||||
let onTranslate: (VocabularyEntry) -> Void
|
||||
|
||||
var shouldShowTranslateButton: Bool {
|
||||
!(entry.germanWordText?.isEmpty ?? true) &&
|
||||
(entry.englishTranslationText?.isEmpty ?? true)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
@@ -239,7 +264,9 @@ struct VocabularyEntryRow: View {
|
||||
VocabularyFieldCell(
|
||||
drawing: entry.englishTranslationPKDrawing,
|
||||
text: entry.englishTranslationText ?? "",
|
||||
onTap: { onSelectField(.englishTranslation) }
|
||||
onTap: { onSelectField(.englishTranslation) },
|
||||
showTranslateButton: shouldShowTranslateButton,
|
||||
onTranslate: { onTranslate(entry) }
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user