feat(translation): add translation button, works with offline downloaded languages

This commit is contained in:
2025-12-07 21:42:55 +01:00
parent 61e5a13388
commit d0c8f222ae
3 changed files with 87 additions and 13 deletions

View 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
}
}
}

View File

@@ -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)

View File

@@ -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)
}