| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- import AppKit
- import Combine
- import Foundation
- @MainActor
- final class LanguageTranslatorViewModel: ObservableObject {
- static let maxWords = 10_000
- @Published var sourceText: String = ""
- @Published var translatedText: String = ""
- @Published var sourceLanguage: TranslationLanguage = .defaultSource
- @Published var targetLanguage: TranslationLanguage = .defaultTarget
- @Published private(set) var isTranslating = false
- @Published private(set) var errorMessage: String?
- private let translationService: any TranslationServicing
- private let historyStorage: HistoryStoring
- private let textExtractionService: any TextExtracting
- init(
- translationService: any TranslationServicing = TranslationService(),
- historyStorage: HistoryStoring = HistoryStorageService.shared,
- textExtractionService: any TextExtracting = TextExtractionService()
- ) {
- self.translationService = translationService
- self.historyStorage = historyStorage
- self.textExtractionService = textExtractionService
- }
- var wordCountLimitLabel: String {
- "\(sourceText.wordCount)/\(Self.maxWords)"
- }
- var canTranslate: Bool {
- !sourceText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isTranslating
- }
- var sourceLanguageOptions: [TranslationLanguage] {
- TranslationLanguage.all
- }
- var targetLanguageOptions: [TranslationLanguage] {
- TranslationLanguage.all.filter { $0 != sourceLanguage }
- }
- func pasteText() {
- guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
- sourceText = clipboard.truncated(toMaxWords: Self.maxWords)
- translatedText = ""
- errorMessage = nil
- }
- func uploadFile() {
- Task {
- do {
- guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
- sourceText = extracted.truncated(toMaxWords: Self.maxWords)
- translatedText = ""
- errorMessage = nil
- } catch {
- errorMessage = error.localizedDescription
- }
- }
- }
- func clearText() {
- sourceText = ""
- translatedText = ""
- errorMessage = nil
- }
- func swapLanguages() {
- let previousSource = sourceLanguage
- sourceLanguage = targetLanguage
- targetLanguage = previousSource
- let previousSourceText = sourceText
- sourceText = translatedText
- translatedText = previousSourceText
- }
- func translate() {
- let trimmed = sourceText.trimmingCharacters(in: .whitespacesAndNewlines)
- guard !trimmed.isEmpty else { return }
- isTranslating = true
- translatedText = ""
- errorMessage = nil
- Task {
- do {
- translatedText = try await translationService.translate(
- trimmed,
- from: sourceLanguage,
- to: targetLanguage
- )
- saveToHistory(sourceText: trimmed, translatedText: translatedText)
- } catch {
- errorMessage = error.localizedDescription
- }
- isTranslating = false
- }
- }
- func restore(from entry: HistoryEntry) {
- guard case .languageTranslator(let data) = entry.payload else { return }
- sourceText = data.sourceText
- translatedText = data.translatedText
- sourceLanguage = TranslationLanguage.all.first { $0.id == data.sourceLanguageID } ?? .defaultSource
- targetLanguage = TranslationLanguage.all.first { $0.id == data.targetLanguageID } ?? .defaultTarget
- errorMessage = nil
- }
- private func saveToHistory(sourceText: String, translatedText: String) {
- let payload = HistoryPayload.languageTranslator(
- TranslationHistoryData(
- sourceText: sourceText,
- translatedText: translatedText,
- sourceLanguageID: sourceLanguage.id,
- targetLanguageID: targetLanguage.id
- )
- )
- let entry = HistoryEntry(
- tool: .languageTranslator,
- title: HistoryHelpers.title(from: sourceText, fallback: "Translation"),
- preview: HistoryHelpers.preview(from: translatedText),
- payload: payload
- )
- historyStorage.save(entry)
- }
- func syncTargetLanguageIfNeeded() {
- guard targetLanguage == sourceLanguage else { return }
- targetLanguage = TranslationLanguage.all.first { $0 != sourceLanguage } ?? .defaultTarget
- }
- }
|