-
Notifications
You must be signed in to change notification settings - Fork 398
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: remove AppleTranslation swift package
- Loading branch information
Showing
7 changed files
with
282 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
Easydict/Swift/Service/Apple/TranslationService/TranslationManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// | ||
// TranslationManager.swift | ||
// AppleTranslation | ||
// | ||
// Created by tisfeng on 2024/10/10. | ||
// Copyright © 2024 izual. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import Translation | ||
|
||
// MARK: - TranslationManager | ||
|
||
@available(macOS 15.0, *) | ||
class TranslationManager: ObservableObject { | ||
// MARK: Internal | ||
|
||
@Published var sourceText: String = "" | ||
@Published var targetText: String = "" | ||
@Published var configuration: TranslationSession.Configuration? | ||
|
||
@MainActor | ||
func translate( | ||
text: String, | ||
sourceLanguage: Locale.Language?, | ||
targetLanguage: Locale.Language? | ||
) async throws | ||
-> TranslationSession.Response { | ||
sourceText = text | ||
|
||
return try await withCheckedThrowingContinuation { continuation in | ||
translationContinuation = continuation | ||
|
||
if configuration == nil { | ||
configuration = .init(source: sourceLanguage, target: targetLanguage) | ||
return | ||
} | ||
|
||
configuration?.source = sourceLanguage | ||
configuration?.target = targetLanguage | ||
configuration?.invalidate() | ||
} | ||
} | ||
|
||
func performTranslation(_ session: TranslationSession) { | ||
Task { | ||
do { | ||
let response = try await session.translate(sourceText) | ||
await MainActor.run { | ||
self.targetText = response.targetText | ||
} | ||
translationContinuation?.resume(returning: response) | ||
} catch { | ||
translationContinuation?.resume(throwing: error) | ||
} | ||
translationContinuation = nil | ||
} | ||
} | ||
|
||
// MARK: Private | ||
|
||
private var translationContinuation: CheckedContinuation<TranslationSession.Response, Error>? | ||
} |
169 changes: 169 additions & 0 deletions
169
Easydict/Swift/Service/Apple/TranslationService/TranslationService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// | ||
// TranslationService.swift | ||
// AppleTranslation | ||
// | ||
// Created by tisfeng on 2024/10/10. | ||
// Copyright © 2024 izual. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
import Translation | ||
|
||
// MARK: - TranslationService | ||
|
||
@objcMembers | ||
@available(macOS 15.0, *) | ||
public class TranslationService: NSObject { | ||
// MARK: Lifecycle | ||
|
||
@MainActor | ||
public init( | ||
attachedWindow: NSWindow? = nil, | ||
configuration: TranslationSession.Configuration? = nil | ||
) { | ||
self.attachedWindow = attachedWindow | ||
self.configuration = configuration | ||
super.init() | ||
setupTranslationView() | ||
} | ||
|
||
/// Initializer for objc, since objc cannot use Swift TranslationSession.Configuration. | ||
/// | ||
/// - Note: If attachedWindow is nil, the translation view will be attached to the translationWindow we created. | ||
@MainActor | ||
public convenience init(attachedWindow: NSWindow? = nil) { | ||
self.init(attachedWindow: attachedWindow, configuration: nil) | ||
} | ||
|
||
// MARK: Public | ||
|
||
public var enableTranslateSameLanguage = false | ||
public var configuration: TranslationSession.Configuration? | ||
|
||
/// The window that the translation view is attached to. | ||
/// | ||
/// - Note: If attachedWindow is nil, the translation view will be attached to the translationWindow we created. | ||
public var attachedWindow: NSWindow? | ||
|
||
public var translationView: NSView { | ||
translationController.view | ||
} | ||
|
||
/// Translate text with specified source and target languages. | ||
/// | ||
/// If no languages are provided, it uses the current configuration. | ||
public func translate( | ||
text: String, | ||
sourceLanguage: Locale.Language? = nil, | ||
targetLanguage: Locale.Language? = nil | ||
) async throws | ||
-> TranslationSession.Response { | ||
let source = sourceLanguage ?? configuration?.source | ||
let target = targetLanguage ?? configuration?.target | ||
|
||
do { | ||
// Check if the translation is ready for use. | ||
let isReady = try await translationIsReadyforUse(text: text, from: source, to: target) | ||
await MainActor.run { | ||
translationWindow?.alphaValue = isReady ? 0 : 1 | ||
} | ||
} catch { | ||
await MainActor.run { | ||
translationWindow?.level = .floating | ||
} | ||
} | ||
|
||
do { | ||
return try await manager.translate( | ||
text: text, | ||
sourceLanguage: source, | ||
targetLanguage: target | ||
) | ||
} catch { | ||
guard let translationError = error as? TranslationError else { throw error } | ||
|
||
switch translationError { | ||
case .unsupportedLanguagePairing: | ||
if source == target, enableTranslateSameLanguage { | ||
return TranslationSession.Response( | ||
sourceLanguage: source ?? .init(identifier: ""), | ||
targetLanguage: target ?? .init(identifier: ""), | ||
sourceText: text, | ||
targetText: text | ||
) | ||
} | ||
|
||
fallthrough | ||
|
||
default: | ||
throw error | ||
} | ||
} | ||
} | ||
|
||
/// Translate text with language codes, providing a more flexible api. | ||
/// | ||
/// - Parameters | ||
/// - sourceLanguageCode: ISO 639 code, such as zh, en, etc. | ||
/// | ||
/// - Note: Currently Apple Translate does not support language script, so zh-Hans and zh-Hant is the same as zh. | ||
public func translate( | ||
text: String, | ||
sourceLanguageCode: String, | ||
targetLanguageCode: String | ||
) async throws | ||
-> String { | ||
let response = try await translate( | ||
text: text, | ||
sourceLanguage: .init(identifier: sourceLanguageCode), | ||
targetLanguage: .init(identifier: targetLanguageCode) | ||
) | ||
return response.targetText | ||
} | ||
|
||
// MARK: Private | ||
|
||
private let manager = TranslationManager() | ||
private var translationWindow: NSWindow? | ||
|
||
private lazy var translationController = NSHostingController( | ||
rootView: TranslationView(manager: manager) | ||
) | ||
|
||
/// Check if the translation is ready for use. | ||
private func translationIsReadyforUse( | ||
text: String, from source: Locale.Language?, to target: Locale.Language? | ||
) async throws | ||
-> Bool { | ||
let status = try await LanguageAvailability().status(for: text, from: source, to: target) | ||
return status == .installed | ||
} | ||
|
||
@MainActor | ||
private func setupTranslationView() { | ||
// TranslationView must be added to a window, otherwise it will not work. | ||
if let attachedWindow { | ||
let translationView = translationController.view | ||
attachedWindow.contentView?.addSubview(translationView) | ||
translationView.isHidden = true | ||
} else { | ||
translationWindow = NSWindow(contentViewController: translationController) | ||
translationWindow?.title = "Translation" | ||
translationWindow?.setContentSize(CGSize(width: 200, height: 200)) | ||
translationWindow?.makeKeyAndOrderFront(nil) | ||
} | ||
} | ||
} | ||
|
||
@available(macOS 15.0, *) | ||
extension LanguageAvailability { | ||
public func status(for text: String, from source: Locale.Language?, to target: Locale.Language?) | ||
async throws | ||
-> LanguageAvailability.Status { | ||
if let source { | ||
return await status(from: source, to: target) | ||
} | ||
return try await status(for: text, to: target) | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
Easydict/Swift/Service/Apple/TranslationService/TranslationView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// | ||
// TranslationExample.swift | ||
// AppleTranslation | ||
// | ||
// Created by tisfeng on 2024/10/10. | ||
// Copyright © 2024 izual. All rights reserved. | ||
// | ||
|
||
import SwiftUI | ||
import Translation | ||
|
||
// MARK: - TranslationView | ||
|
||
@available(macOS 15.0, *) | ||
struct TranslationView: View { | ||
@ObservedObject var manager: TranslationManager | ||
|
||
var body: some View { | ||
VStack { | ||
TextField("", text: $manager.sourceText) | ||
Text(manager.targetText) | ||
} | ||
.padding() | ||
.translationTask(manager.configuration) { session in | ||
manager.performTranslation(session) | ||
} | ||
} | ||
} |
Oops, something went wrong.