Skip to content

Commit

Permalink
Use Translation API on macOS 15
Browse files Browse the repository at this point in the history
  • Loading branch information
kishikawa katsumi authored and kishikawa katsumi committed Jun 20, 2024
1 parent d6aff47 commit 2b97bbd
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 10 deletions.
24 changes: 21 additions & 3 deletions TextChatTranslator.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
1416BAF22C23613D00E3E705 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1416BAF12C23613C00E3E705 /* App.swift */; };
1416BAF42C2380CC00E3E705 /* TranslationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1416BAF32C2380C700E3E705 /* TranslationContext.swift */; };
142C3AD82C2341A200A7D487 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142C3AD72C23419F00A7D487 /* SettingsView.swift */; };
14C3D6592C1D86B000A24281 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14C3D6582C1D86B000A24281 /* AppDelegate.swift */; };
14C3D65D2C1D86B200A24281 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14C3D65C2C1D86B200A24281 /* Assets.xcassets */; };
14C3D6602C1D86B200A24281 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 14C3D65F2C1D86B200A24281 /* Base */; };
Expand All @@ -18,6 +21,9 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
1416BAF12C23613C00E3E705 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
1416BAF32C2380C700E3E705 /* TranslationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationContext.swift; sourceTree = "<group>"; };
142C3AD72C23419F00A7D487 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
14C3D6552C1D86B000A24281 /* Text Chat Translator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Text Chat Translator.app"; sourceTree = BUILT_PRODUCTS_DIR; };
14C3D6582C1D86B000A24281 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
14C3D65C2C1D86B200A24281 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -61,6 +67,9 @@
isa = PBXGroup;
children = (
14C3D6582C1D86B000A24281 /* AppDelegate.swift */,
1416BAF12C23613C00E3E705 /* App.swift */,
142C3AD72C23419F00A7D487 /* SettingsView.swift */,
1416BAF32C2380C700E3E705 /* TranslationContext.swift */,
14C3D6672C1D872600A24281 /* OverlayWindow.swift */,
14C3D66A2C1D872600A24281 /* Translation.swift */,
14C3D6682C1D872600A24281 /* AXUIElement.swift */,
Expand Down Expand Up @@ -101,7 +110,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1530;
LastUpgradeCheck = 1530;
LastUpgradeCheck = 1600;
TargetAttributes = {
14C3D6542C1D86B000A24281 = {
CreatedOnToolsVersion = 15.3;
Expand Down Expand Up @@ -145,11 +154,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
142C3AD82C2341A200A7D487 /* SettingsView.swift in Sources */,
14C3D66C2C1D872600A24281 /* AXUIElement.swift in Sources */,
14C3D66B2C1D872600A24281 /* OverlayWindow.swift in Sources */,
14C3D66D2C1D872600A24281 /* Debounce.swift in Sources */,
14C3D6592C1D86B000A24281 /* AppDelegate.swift in Sources */,
1416BAF22C23613D00E3E705 /* App.swift in Sources */,
14C3D66E2C1D872600A24281 /* Translation.swift in Sources */,
1416BAF42C2380CC00E3E705 /* TranslationContext.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -172,6 +184,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
Expand Down Expand Up @@ -201,6 +214,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
Expand Down Expand Up @@ -235,6 +249,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
Expand Down Expand Up @@ -264,6 +279,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand Down Expand Up @@ -294,6 +310,7 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 27AEDK3C9F;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -307,7 +324,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 0.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.kishikawakatsumi.TextChatTranslator;
PRODUCT_MODULE_NAME = TextChatTranslator;
Expand All @@ -326,6 +343,7 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 27AEDK3C9F;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -339,7 +357,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 0.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.kishikawakatsumi.TextChatTranslator;
PRODUCT_MODULE_NAME = TextChatTranslator;
Expand Down
2 changes: 1 addition & 1 deletion TextChatTranslator/AXUIElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ extension AXUIElement {
}
}

extension AXError: Error {}
extension AXError: @retroactive Error {}

private func cocoaScreenPointFromCarbonScreenPoint(_ carbonPoint: CGPoint) -> CGPoint {
CGPoint(x: carbonPoint.x, y: NSScreen.screens[0].frame.size.height - carbonPoint.y)
Expand Down
30 changes: 30 additions & 0 deletions TextChatTranslator/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import SwiftUI
import Observation
import Translation

@main
struct TextChatTranslatorApp: App {
@AppStorage("targetLanguage") private var targetLanguage = "ja"

@State private var translationContext = TranslationContext()
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

var body: some Scene {
WindowGroup {
VStack {

}
.translationTask(translationContext.configuration) { (session) in
appDelegate.translationSession = session
}
}
Settings {
SettingsView()
.environment(\.translationContext, translationContext)
}
}
}

struct OpenSettings {
@Environment(\.openSettings) var openSettings
}
53 changes: 50 additions & 3 deletions TextChatTranslator/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
import Cocoa
import Translation

private let debounce = Debounce(delay: 0.2)

@main
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
private let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
var translationSession: TranslationSession? {
didSet {
guard let _ = translationSession else { return }
guard mainWindow == nil else { return }
for window in NSApp.windows {
if window.className == "SwiftUI.AppKitWindow" {
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true

window.hasShadow = false

window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
window.ignoresMouseEvents = true

window.isOpaque = false
window.backgroundColor = .clear

window.setContentSize(.zero)
window.setFrameOrigin(.zero)

mainWindow = window
}
}
}
}

private lazy var statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
private let translationMenuItem = NSMenuItem(
title: NSLocalizedString("Start Translation", comment: ""),
action: #selector(toggleTranslationEnabled), keyEquivalent: ""
)

private var mainWindow: NSWindow?
private var overlays = [NSWindow]()

private var application: AXUIElement?
Expand Down Expand Up @@ -45,6 +74,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
translationMenuItem.offStateImage = NSImage(named: NSImage.statusNoneName)
menu.addItem(translationMenuItem)

menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(
title: NSLocalizedString("Settings…", comment: ""),
action: #selector(openSettings), keyEquivalent: ",")
)

menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(
title: NSLocalizedString("Quit Text Chat Translator", comment: ""),
Expand Down Expand Up @@ -110,6 +145,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
isTranslationEnabled.toggle()
}

@objc
private func openSettings() {
let openSettings = OpenSettings()
openSettings.openSettings()
NSApp.activate()
}

private func translateMessages() {
guard isTranslationEnabled else {
return
Expand All @@ -131,7 +173,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {

let t = text
Task { @MainActor in
overlayWindow.text = try await translate(text: t)
if let translationSession {
overlayWindow.text = try await translate(
session: translationSession,
text: t
)
}
}

overlayWindow.setFrameOrigin(frame.origin)
Expand Down
18 changes: 18 additions & 0 deletions TextChatTranslator/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"sourceLanguage" : "en",
"strings" : {
"Background Color:" : {

},
"Install Translation…" : {

},
"Quit Text Chat Translator" : {
"localizations" : {
"ja" : {
Expand All @@ -10,6 +16,12 @@
}
}
}
},
"Settings…" : {

},
"Source Language:" : {

},
"Start Translation" : {
"localizations" : {
Expand All @@ -30,6 +42,12 @@
}
}
}
},
"Target Language:" : {

},
"Text Color:" : {

}
},
"version" : "1.0"
Expand Down
8 changes: 7 additions & 1 deletion TextChatTranslator/OverlayWindow.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AppKit
import SwiftUI

class OverlayWindow: NSPanel {
var text: String {
Expand Down Expand Up @@ -67,6 +68,10 @@ fileprivate class OverlayContentView: NSView {
adjustFontSizeToFit()
}
}

@AppStorage("backgroundColor") private var backgroundColor = Color.white
@AppStorage("textColor") private var textColor = Color.black

private let textLabel = NSTextField(wrappingLabelWithString: "")
private let defaultFont: NSFont = .systemFont(ofSize: 16)
private let leadingMargin: CGFloat = 72
Expand All @@ -76,7 +81,7 @@ fileprivate class OverlayContentView: NSView {

let containerView = NSView()
containerView.wantsLayer = true
containerView.layer?.backgroundColor = .white
containerView.layer?.backgroundColor = backgroundColor.cgColor

containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
Expand All @@ -91,6 +96,7 @@ fileprivate class OverlayContentView: NSView {
textLabel.translatesAutoresizingMaskIntoConstraints = true
containerView.addSubview(textLabel)

textLabel.textColor = NSColor(textColor)
textLabel.font = defaultFont
textLabel.backgroundColor = .clear
textLabel.drawsBackground = false
Expand Down
Loading

0 comments on commit 2b97bbd

Please sign in to comment.