| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- import AppKit
- import Combine
- import Foundation
- import UniformTypeIdentifiers
- @MainActor
- final class LanguageTranslatorViewModel: ObservableObject {
- static let maxCharacters = 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
- init(
- translationService: any TranslationServicing = TranslationService(),
- historyStorage: HistoryStoring = HistoryStorageService.shared
- ) {
- self.translationService = translationService
- self.historyStorage = historyStorage
- }
- var characterCount: Int { sourceText.count }
- var characterCountLabel: String {
- "\(characterCount)/\(Self.maxCharacters)"
- }
- 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 = String(clipboard.prefix(Self.maxCharacters))
- translatedText = ""
- errorMessage = nil
- }
- func uploadFile() {
- let panel = NSOpenPanel()
- panel.title = "Choose a text file"
- panel.allowedContentTypes = [.plainText, .text, .utf8PlainText]
- panel.allowsMultipleSelection = false
- panel.canChooseDirectories = false
- guard panel.runModal() == .OK, let url = panel.url else { return }
- do {
- let contents = try String(contentsOf: url, encoding: .utf8)
- sourceText = String(contents.prefix(Self.maxCharacters))
- translatedText = ""
- errorMessage = nil
- } catch {
- errorMessage = "Could not read the selected file."
- }
- }
- 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
- }
- }
|