Ariver
2026-05-19 7c8c804dacd1747101c0949a3ca26c94c690af79
Release TagLauncher 7.5.1
36 files modified
1 files added
3515 ■■■■■ changed files
Apptag/AppDefaults.swift 24 ●●●●● patch | view | raw | blame | history
Apptag/ApptagApp.swift 258 ●●●● patch | view | raw | blame | history
Apptag/ContentView.swift 317 ●●●●● patch | view | raw | blame | history
Apptag/DataLayer.swift 22 ●●●●● patch | view | raw | blame | history
Apptag/Info.plist 4 ●●●● patch | view | raw | blame | history
Apptag/Localization/ar-Najdi.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ar.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/cs.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/da.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/de.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/en.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/es.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/fr.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/id.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/it.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ja.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ko.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ms.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/nb.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/nl.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/nn.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/no.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/pl.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/pt-BR.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ro.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ru.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/sr-Cyrl.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/sv.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/th.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/tr.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/uk.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/vi.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/zh-Hans.json 41 ●●●●● patch | view | raw | blame | history
Apptag/Localization/zh-Hant.json 41 ●●●●● patch | view | raw | blame | history
Apptag/PreferencesView.swift 602 ●●●● patch | view | raw | blame | history
Apptag/QuickSearch.swift 1080 ●●●●● patch | view | raw | blame | history
CHANGELOG.md 19 ●●●●● patch | view | raw | blame | history
Apptag/AppDefaults.swift
@@ -2,7 +2,7 @@
enum AppDefaults {
    static let schemaVersionKey = "initialDefaultsSchemaVersion"
    static let currentSchemaVersion = 1
    static let currentSchemaVersion = 2
    static let tagFontSize: Double = 22
    static let iconSize: Double = 80
@@ -24,10 +24,32 @@
            "launchAtLogin": launchAtLogin,
            "showUncommonAppBubbles": showUncommonAppBubbles
        ])
        migrateShortcutDefaultsIfNeeded()
    }
    static func hasStoredValue(for key: String) -> Bool {
        let domain = Bundle.main.bundleIdentifier ?? "com.apptag.launcher"
        return UserDefaults.standard.persistentDomain(forName: domain)?[key] != nil
    }
    private static func migrateShortcutDefaultsIfNeeded() {
        let defaults = UserDefaults.standard
        guard defaults.integer(forKey: schemaVersionKey) < currentSchemaVersion else { return }
        defaults.set(LauncherHotkey.defaultMain.serialized, forKey: LauncherHotkeyKind.main.storageKey)
        defaults.removeObject(forKey: LauncherHotkeyKind.main.pendingStorageKey)
        defaults.removeObject(forKey: LauncherHotkeyKind.main.statusKey)
        defaults.removeObject(forKey: LauncherHotkeyKind.main.conflictMessageKey)
        let quickSearchKey = LauncherHotkeyKind.quickSearch.storageKey
        let storedQuickSearch = defaults.string(forKey: quickSearchKey) ?? ""
        if storedQuickSearch.isEmpty {
            defaults.set(LauncherHotkey.defaultQuickSearch.serialized, forKey: quickSearchKey)
            defaults.removeObject(forKey: LauncherHotkeyKind.quickSearch.pendingStorageKey)
            defaults.removeObject(forKey: LauncherHotkeyKind.quickSearch.statusKey)
            defaults.removeObject(forKey: LauncherHotkeyKind.quickSearch.conflictMessageKey)
        }
        defaults.set(currentSchemaVersion, forKey: schemaVersionKey)
    }
}
Apptag/ApptagApp.swift
@@ -33,7 +33,6 @@
    private static let statusItemButtonIdentifier = NSUserInterfaceItemIdentifier("TagLauncherStatusItemButton")
    private static let statusItemAccessibilityLabel = "TagLauncher"
    private static let showAppListMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherShowAppListMenuItem")
    private static let showAppListShortcutGlyphs = "⌥⇧␣"
    private static let overlayDefaultLevel = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
    private static let overlayTextInputLevel = NSWindow.Level.modalPanel
@@ -41,7 +40,12 @@
    private var overlayWindow: NSWindow?
    private var overlayKeyMonitor: Any?
    private var settingsWindow: NSWindow?    // Track Settings window to keep it above overlay
    private var hotkeyRef: EventHotKeyRef?
    private var mainHotkeyRef: EventHotKeyRef?
    private var quickSearchHotkeyRef: EventHotKeyRef?
    private var hotkeyEventHandlerInstalled = false
    private var isQuickSearchOpen = false
    private var isModalInteractionActive = false
    private var areHotkeysPausedForCapture = false
    private var isInEditMode = false  // Suppress auto-dismiss during editing
    private var isEditingAppNote = false
    private var isConfiguringApplicationMenu = false
@@ -61,11 +65,12 @@
        migrateDefaultGroupName()
        TagDatabase.seedDefaultTags()
        syncChromeSettings(force: true)
        registerHotkey()
        registerConfiguredHotkeys()
        observeOtherWindows()
        observeSettingsClose()
        observeEditMode()
        observeAppNoteEditing()
        observeQuickSearch()
        observePreferencesRequests()
        observeApplicationMenuChanges()
        observeChromeSettings()
@@ -333,7 +338,10 @@
    }
    private var showAppListMenuTitle: String {
        "\(tr("menu.showAppList"))  \(Self.showAppListShortcutGlyphs)"
        if let hotkey = LauncherHotkeyStore.hotkey(for: .main) {
            return "\(tr("menu.showAppList"))  \(hotkey.displayString)"
        }
        return tr("menu.showAppList")
    }
    private func observeApplicationMenuChanges() {
@@ -502,7 +510,7 @@
        }
    }
    private func showOverlay() {
    private func showOverlay(initialQuickSearchSource: String? = nil) {
        // Use the screen under the mouse cursor — works in fullscreen spaces
        let mousePoint = NSEvent.mouseLocation
        guard let screen = NSScreen.screens.first(where: {
@@ -513,7 +521,7 @@
        if let existingWindow = overlayWindow {
            window = existingWindow
        } else {
            window = makeOverlayWindow(on: screen)
            window = makeOverlayWindow(on: screen, initialQuickSearchSource: initialQuickSearchSource)
            overlayWindow = window
        }
@@ -521,6 +529,14 @@
        window.level = isEditingAppNote ? Self.overlayTextInputLevel : Self.overlayDefaultLevel
        installOverlayKeyMonitor()
        if let initialQuickSearchSource {
            NotificationCenter.default.post(
                name: .tagLauncherQuickSearchRequested,
                object: nil,
                userInfo: ["source": initialQuickSearchSource]
            )
        }
        window.makeKeyAndOrderFront(nil)
        window.orderFrontRegardless()
@@ -530,12 +546,58 @@
    private func installOverlayKeyMonitor() {
        guard overlayKeyMonitor == nil else { return }
        overlayKeyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
            guard let self else { return event }
            if event.keyCode == 53 { // Escape
                self?.hideOverlay()
                if self.isQuickSearchOpen {
                    NotificationCenter.default.post(name: .tagLauncherQuickSearchDismissRequested, object: nil)
                    return nil
                }
                self.hideOverlay()
                return nil
            }
            if self.shouldOpenQuickSearch(for: event) {
                NotificationCenter.default.post(
                    name: .tagLauncherQuickSearchRequested,
                    object: nil,
                    userInfo: ["source": QuickSearchOpenSource.mainOverlay]
                )
                return nil
            }
            return event
        }
    }
    private func shouldOpenQuickSearch(for event: NSEvent) -> Bool {
        guard event.keyCode == UInt16(kVK_Space),
              !event.isARepeat,
              event.modifierFlags.intersection(.deviceIndependentFlagsMask).isEmpty,
              overlayWindow?.isVisible == true,
              !isQuickSearchOpen,
              !isInEditMode,
              !isEditingAppNote,
              !isModalInteractionActive
        else { return false }
        guard let firstResponder = overlayWindow?.firstResponder else { return true }
        if firstResponder is NSText || firstResponder is NSTextField {
            return false
        }
        if let responderView = firstResponder as? NSView,
           viewOrAncestorHandlesSpace(responderView) {
            return false
        }
        return true
    }
    private func viewOrAncestorHandlesSpace(_ view: NSView) -> Bool {
        var current: NSView? = view
        while let candidate = current {
            if candidate is NSButton || candidate is NSSegmentedControl || candidate is NSSlider {
                return true
            }
            current = candidate.superview
        }
        return false
    }
    private func removeOverlayKeyMonitor() {
@@ -545,7 +607,7 @@
        }
    }
    private func makeOverlayWindow(on screen: NSScreen) -> NSWindow {
    private func makeOverlayWindow(on screen: NSScreen, initialQuickSearchSource: String? = nil) -> NSWindow {
        let panel = OverlayPanel(
            contentRect: screen.frame,
            styleMask: [.borderless, .fullSizeContentView, .nonactivatingPanel],
@@ -568,9 +630,12 @@
        panel.titleVisibility = .hidden
        panel.isReleasedWhenClosed = false
        panel.contentView = DismissibleHostingView(
            rootView: ContentView(hideOverlay: { [weak self] in
                self?.hideOverlay(force: true)
            }),
            rootView: ContentView(
                hideOverlay: { [weak self] in
                    self?.hideOverlay(force: true)
                },
                initialQuickSearchSource: initialQuickSearchSource
            ),
            onBackdropTap: { [weak self] in
                self?.hideOverlay()
            }
@@ -592,48 +657,106 @@
        }
    }
    // MARK: - Global Hotkey (Shift+Option+Space)
    // MARK: - Global Hotkeys
    /// Carbon RegisterEventHotKey. If it fails (sandbox, etc.), falls back to menu bar only.
    private func registerHotkey() {
    private func registerConfiguredHotkeys() {
        installHotkeyEventHandlerIfNeeded()
        registerStoredHotkey(.main)
        registerStoredHotkey(.quickSearch)
    }
    @discardableResult
    func applyHotkey(_ hotkey: LauncherHotkey?, for kind: LauncherHotkeyKind) -> Bool {
        installHotkeyEventHandlerIfNeeded()
        if hotkey == nil {
            unregisterHotkey(for: kind)
            LauncherHotkeyStore.save(nil, for: kind)
            LauncherHotkeyStore.setStatus(.disabled, message: nil, for: kind)
            return true
        }
        guard let hotkey else { return false }
        let previousRef = hotkeyRef(for: kind)
        if previousRef != nil, LauncherHotkeyStore.hotkey(for: kind) == hotkey {
            LauncherHotkeyStore.setStatus(.active, message: nil, for: kind)
            return true
        }
        setHotkeyRef(nil, for: kind)
        var hotkeyID = EventHotKeyID()
        hotkeyID.signature = OSType(0x41505447) // 'APTG'
        hotkeyID.id = 1
        hotkeyID.id = kind.eventID
        let modifiers = UInt32(shiftKey | optionKey)
        var ref: EventHotKeyRef?
        var newRef: EventHotKeyRef?
        let status = RegisterEventHotKey(
            UInt32(kVK_Space),
            modifiers,
            hotkey.keyCode,
            hotkey.modifiers,
            hotkeyID,
            GetApplicationEventTarget(),
            0,
            &ref
            &newRef
        )
        hotkeyRef = ref
        if status != noErr {
            print("[TagLauncher] Hotkey registration failed: \(status). Falling back to menu bar only.")
            return
        if status == noErr, let newRef {
            if let previousRef { UnregisterEventHotKey(previousRef) }
            setHotkeyRef(newRef, for: kind)
            LauncherHotkeyStore.save(hotkey, for: kind)
            LauncherHotkeyStore.setStatus(.active, message: nil, for: kind)
            return true
        }
        setHotkeyRef(previousRef, for: kind)
        let message = LauncherHotkeyStore.knownConflictMessage(for: hotkey, status: status)
            ?? tr("quickSearch.hotkeyConflict.generic")
        LauncherHotkeyStore.savePending(hotkey, for: kind)
        LauncherHotkeyStore.setStatus(.conflict, message: message, for: kind)
        print("[TagLauncher] Hotkey registration failed for \(kind.rawValue): \(status)")
        return false
    }
    func retryHotkeyRegistration(for kind: LauncherHotkeyKind) {
        _ = applyHotkey(
            LauncherHotkeyStore.pendingHotkey(for: kind) ?? LauncherHotkeyStore.hotkey(for: kind),
            for: kind
        )
    }
    private func registerStoredHotkey(_ kind: LauncherHotkeyKind) {
        _ = applyHotkey(LauncherHotkeyStore.hotkey(for: kind), for: kind)
    }
    private func installHotkeyEventHandlerIfNeeded() {
        guard !hotkeyEventHandlerInstalled else { return }
        hotkeyEventHandlerInstalled = true
        var eventSpec = EventTypeSpec(
            eventClass: OSType(kEventClassKeyboard),
            eventKind: UInt32(kEventHotKeyPressed)
        )
        let selfPtr = Unmanaged.passUnretained(self).toOpaque()
        InstallEventHandler(
            GetApplicationEventTarget(),
            { (_, _, userData) -> OSStatus in
                guard let userData else { return noErr }
            { (_, event, userData) -> OSStatus in
                guard let event, let userData else { return noErr }
                var hotkeyID = EventHotKeyID()
                let status = GetEventParameter(
                    event,
                    EventParamName(kEventParamDirectObject),
                    EventParamType(typeEventHotKeyID),
                    nil,
                    MemoryLayout<EventHotKeyID>.size,
                    nil,
                    &hotkeyID
                )
                guard status == noErr else { return noErr }
                let delegate = Unmanaged<AppDelegate>
                    .fromOpaque(userData)
                    .takeUnretainedValue()
                DispatchQueue.main.async {
                    delegate.toggleOverlay()
                    delegate.handleHotkeyEvent(id: hotkeyID.id)
                }
                return noErr
            },
@@ -642,6 +765,40 @@
            selfPtr,
            nil
        )
    }
    private func handleHotkeyEvent(id: UInt32) {
        if id == LauncherHotkeyKind.quickSearch.eventID {
            showQuickSearchFromGlobalHotkey()
        } else {
            toggleOverlay()
        }
    }
    private func showQuickSearchFromGlobalHotkey() {
        guard overlayWindow?.isVisible != true else { return }
        showOverlay(initialQuickSearchSource: QuickSearchOpenSource.globalHidden)
    }
    private func hotkeyRef(for kind: LauncherHotkeyKind) -> EventHotKeyRef? {
        switch kind {
        case .main: return mainHotkeyRef
        case .quickSearch: return quickSearchHotkeyRef
        }
    }
    private func setHotkeyRef(_ ref: EventHotKeyRef?, for kind: LauncherHotkeyKind) {
        switch kind {
        case .main: mainHotkeyRef = ref
        case .quickSearch: quickSearchHotkeyRef = ref
        }
    }
    private func unregisterHotkey(for kind: LauncherHotkeyKind) {
        if let ref = hotkeyRef(for: kind) {
            UnregisterEventHotKey(ref)
            setHotkeyRef(nil, for: kind)
        }
    }
    // MARK: - Preferences
@@ -780,6 +937,38 @@
        }
    }
    private func observeQuickSearch() {
        NotificationCenter.default.addObserver(
            forName: .tagLauncherQuickSearchVisibilityChanged,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            self?.isQuickSearchOpen = (notification.userInfo?["active"] as? Bool) ?? false
        }
        NotificationCenter.default.addObserver(
            forName: .tagLauncherModalInteractionChanged,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            guard let self else { return }
            let active = (notification.userInfo?["active"] as? Bool) ?? false
            self.isModalInteractionActive = active
            if (notification.userInfo?["source"] as? String) == "hotkeyRecording" {
                self.setHotkeysPausedForCapture(active)
            }
        }
        NotificationCenter.default.addObserver(
            forName: .tagLauncherHotkeysChanged,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.setupMenuBar()
            self?.configureApplicationMenuWhenAvailable(retries: 2)
        }
    }
    private func updateOverlayLevelForTextInput() {
        guard let overlayWindow else { return }
        overlayWindow.level = isEditingAppNote ? Self.overlayTextInputLevel : Self.overlayDefaultLevel
@@ -788,6 +977,17 @@
        }
    }
    private func setHotkeysPausedForCapture(_ paused: Bool) {
        guard areHotkeysPausedForCapture != paused else { return }
        areHotkeysPausedForCapture = paused
        if paused {
            unregisterHotkey(for: .main)
            unregisterHotkey(for: .quickSearch)
        } else {
            registerConfiguredHotkeys()
        }
    }
    /// The overlay buttons live inside SwiftUI; use an app-level notification like edit mode does.
    private func observePreferencesRequests() {
        NotificationCenter.default.addObserver(
Apptag/ContentView.swift
@@ -337,6 +337,7 @@
struct ContentView: View {
    let hideOverlay: () -> Void
    private let initialQuickSearchSource: String?
    @State private var allApps: [AppInfo] = []
    @State private var tagColors: [String: Int] = [:]
@@ -377,6 +378,27 @@
    @State private var bubbleDraftNote = ""
    @FocusState private var bubbleNoteFocused: Bool
    // Quick Search
    @State private var quickSearchVisible = false
    @State private var quickSearchQuery = ""
    @State private var quickSearchDocuments: [QuickSearchDocument] = []
    @State private var quickSearchResults: [QuickSearchResult] = []
    @State private var quickSearchSelectedID: URL? = nil
    @State private var quickSearchManualSelection = false
    @State private var quickSearchFocusToken = 0
    @State private var quickSearchCloseHidesOverlay = false
    @State private var quickSearchErrorMessage: String? = nil
    @State private var initialQuickSearchConsumed = false
    init(hideOverlay: @escaping () -> Void, initialQuickSearchSource: String? = nil) {
        self.hideOverlay = hideOverlay
        self.initialQuickSearchSource = initialQuickSearchSource
        let startsInQuickSearch = initialQuickSearchSource != nil
        _quickSearchVisible = State(initialValue: startsInQuickSearch)
        _quickSearchCloseHidesOverlay = State(initialValue: initialQuickSearchSource == QuickSearchOpenSource.globalHidden)
        _quickSearchFocusToken = State(initialValue: startsInQuickSearch ? 1 : 0)
    }
    // Configurable defaults
    @AppStorage("defaultGroupName") private var defaultGroupName = "Other"
    @AppStorage("tagFontSize") private var tagFontSize: Double = AppDefaults.tagFontSize
@@ -411,37 +433,47 @@
        displayMode == "coloredContainer" || displayMode == "coloredGridContainer"
    }
    private var quickSearchOnlyMode: Bool {
        quickSearchVisible && quickSearchCloseHidesOverlay
    }
    var body: some View {
        ZStack {
            VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)
                .ignoresSafeArea()
                .allowsHitTesting(false)
            if !quickSearchOnlyMode {
                VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)
                    .ignoresSafeArea()
                    .allowsHitTesting(false)
            }
            if notchHeight > 0 {
                VStack {
                    Rectangle().fill(.black)
                        .frame(height: notchHeight)
                        .ignoresSafeArea(edges: .top)
                    Spacer()
            if !quickSearchOnlyMode {
                if notchHeight > 0 {
                    VStack {
                        Rectangle().fill(.black)
                            .frame(height: notchHeight)
                            .ignoresSafeArea(edges: .top)
                        Spacer()
                    }
                    .allowsHitTesting(false)
                }
                .allowsHitTesting(false)
                switch editPhase {
                case .none:
                    normalContent
                case .editingTags:
                    editTagsView
                case .editingApps:
                    editAppsView
                }
                uncommonAppBubbleOverlay
                smartStartNoticeOverlay
                editActionFeedbackOverlay
                uncategorizedDropConfirmOverlay
            }
            switch editPhase {
            case .none:
                normalContent
            case .editingTags:
                editTagsView
            case .editingApps:
                editAppsView
            }
            quickSearchOverlay
            uncommonAppBubbleOverlay
            smartStartNoticeOverlay
            editActionFeedbackOverlay
            uncategorizedDropConfirmOverlay
            if let message = dropWarningToast {
            if !quickSearchOnlyMode, let message = dropWarningToast {
                Text(message)
                    .font(.system(size: 16, weight: .semibold))
                    .foregroundStyle(.primary)
@@ -456,7 +488,7 @@
                    .allowsHitTesting(false)
            }
            if dropRefreshVisible {
            if !quickSearchOnlyMode && dropRefreshVisible {
                Color.black.opacity(0.08)
                    .ignoresSafeArea()
                    .transition(.opacity)
@@ -479,6 +511,17 @@
        .onAppear {
            refreshNotchHeight()
            refreshApps()
            if let initialQuickSearchSource, !initialQuickSearchConsumed {
                initialQuickSearchConsumed = true
                quickSearchCloseHidesOverlay = initialQuickSearchSource == QuickSearchOpenSource.globalHidden
                quickSearchVisible = true
                quickSearchFocusToken &+= 1
                NotificationCenter.default.post(
                    name: .tagLauncherQuickSearchVisibilityChanged,
                    object: nil,
                    userInfo: ["active": true]
                )
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidShow)) { _ in
            resetTransientDragState()
@@ -487,6 +530,14 @@
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidHide)) { _ in
            resetTransientDragState()
            closeQuickSearch(notify: true, hideOverlayIfNeeded: false)
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchRequested)) { notification in
            let source = notification.userInfo?["source"] as? String ?? QuickSearchOpenSource.mainOverlay
            openQuickSearch(source: source)
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchDismissRequested)) { _ in
            closeQuickSearch()
        }
        .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
            guard allApps.isEmpty, !refreshInProgress else { return }
@@ -517,6 +568,10 @@
            if editingBubble != nil && !focused {
                commitBubbleNote()
            }
        }
        .onChange(of: quickSearchQuery) { _, _ in
            quickSearchErrorMessage = nil
            refreshQuickSearchResults()
        }
        .onChange(of: pendingUncategorizedDrop != nil) { _, active in
            NotificationCenter.default.post(
@@ -551,6 +606,179 @@
                NotificationCenter.default.post(name: .tagLauncherEditModeChanged, object: nil, userInfo: ["active": false])
            }
        }
    }
    // MARK: - Quick Search
    private var quickSearchOverlay: some View {
        GeometryReader { proxy in
            if quickSearchVisible {
                QuickSearchBackdropClickView {
                    closeQuickSearch()
                }
                    .frame(width: proxy.size.width, height: proxy.size.height)
                    .ignoresSafeArea()
                    .zIndex(899)
                QuickSearchOverlayView(
                    query: $quickSearchQuery,
                    results: quickSearchResults,
                    selectedID: quickSearchSelectedID,
                    focusToken: quickSearchFocusToken,
                    isLoading: quickSearchDocuments.isEmpty && refreshInProgress,
                    maxVisibleRows: quickSearchMaxVisibleRows(in: proxy.size),
                    errorMessage: quickSearchErrorMessage,
                    onCommand: handleQuickSearchCommand,
                    onHover: selectQuickSearchResult,
                    onLaunch: launchQuickSearchResult
                )
                .position(
                    x: proxy.size.width / 2,
                    y: quickSearchPanelCenterY(in: proxy.size)
                )
                .transition(.scale(scale: 0.98).combined(with: .opacity))
                .zIndex(900)
            }
        }
        .ignoresSafeArea()
        .animation(.easeOut(duration: 0.12), value: quickSearchVisible)
    }
    private func quickSearchPanelCenterY(in size: CGSize) -> CGFloat {
        let visibleRows = max(1, min(quickSearchResults.isEmpty ? 1 : quickSearchResults.count, quickSearchMaxVisibleRows(in: size)))
        let estimatedPanelHeight = CGFloat(visibleRows) * 76 + 122
        return quickSearchPanelTopY(in: size) + estimatedPanelHeight / 2
    }
    private func quickSearchPanelTopY(in size: CGSize) -> CGFloat {
        max(notchHeight + 54, min(112, size.height * 0.12))
    }
    private func quickSearchMaxVisibleRows(in size: CGSize) -> Int {
        let bottomClearance: CGFloat = 84
        let chromeHeight: CGFloat = 122
        let rowHeightWithSpacing: CGFloat = 76
        let availableHeight = max(0, size.height - quickSearchPanelTopY(in: size) - bottomClearance - chromeHeight)
        return max(1, min(8, Int(floor(availableHeight / rowHeightWithSpacing))))
    }
    private func openQuickSearch(source: String) {
        guard canOpenQuickSearch else { return }
        dismissAppBubble()
        quickSearchCloseHidesOverlay = source == QuickSearchOpenSource.globalHidden
        quickSearchVisible = true
        quickSearchQuery = ""
        quickSearchManualSelection = false
        quickSearchErrorMessage = nil
        quickSearchFocusToken &+= 1
        refreshQuickSearchResults()
        NotificationCenter.default.post(
            name: .tagLauncherQuickSearchVisibilityChanged,
            object: nil,
            userInfo: ["active": true]
        )
    }
    private var canOpenQuickSearch: Bool {
        editPhase == .none
            && pendingUncategorizedDrop == nil
            && smartStartNotice == nil
            && !dropRefreshVisible
            && !quickSearchVisible
    }
    private func closeQuickSearch(notify: Bool = true, hideOverlayIfNeeded: Bool = true) {
        let shouldHideOverlay = quickSearchVisible && quickSearchCloseHidesOverlay && hideOverlayIfNeeded
        guard quickSearchVisible else { return }
        quickSearchVisible = false
        quickSearchQuery = ""
        quickSearchResults = []
        quickSearchSelectedID = nil
        quickSearchManualSelection = false
        quickSearchErrorMessage = nil
        quickSearchCloseHidesOverlay = false
        if notify {
            NotificationCenter.default.post(
                name: .tagLauncherQuickSearchVisibilityChanged,
                object: nil,
                userInfo: ["active": false]
            )
        }
        if shouldHideOverlay {
            hideOverlay()
        }
    }
    private func refreshQuickSearchResults() {
        let previousSelection = quickSearchSelectedID
        quickSearchResults = QuickSearchEngine.search(quickSearchQuery, documents: quickSearchDocuments)
        if quickSearchResults.isEmpty {
            quickSearchSelectedID = nil
            quickSearchManualSelection = false
            return
        }
        if quickSearchManualSelection,
           let previousSelection,
           quickSearchResults.contains(where: { $0.id == previousSelection }) {
            quickSearchSelectedID = previousSelection
        } else {
            quickSearchSelectedID = quickSearchResults.first?.id
            quickSearchManualSelection = false
        }
    }
    private func handleQuickSearchCommand(_ command: QuickSearchCommand) {
        switch command {
        case .moveUp:
            moveQuickSearchSelection(by: -1)
        case .moveDown:
            moveQuickSearchSelection(by: 1)
        case .submit:
            guard let selected = selectedQuickSearchResult else { return }
            launchQuickSearchResult(selected)
        case .dismiss:
            closeQuickSearch()
        }
    }
    private var selectedQuickSearchResult: QuickSearchResult? {
        guard let quickSearchSelectedID else { return nil }
        return quickSearchResults.first { $0.id == quickSearchSelectedID }
    }
    private func moveQuickSearchSelection(by delta: Int) {
        guard !quickSearchResults.isEmpty else { return }
        let currentIndex = quickSearchSelectedID.flatMap { id in
            quickSearchResults.firstIndex { $0.id == id }
        } ?? 0
        let nextIndex = min(max(currentIndex + delta, 0), quickSearchResults.count - 1)
        quickSearchSelectedID = quickSearchResults[nextIndex].id
        quickSearchManualSelection = true
    }
    private func selectQuickSearchResult(_ result: QuickSearchResult) {
        quickSearchSelectedID = result.id
        quickSearchManualSelection = true
    }
    private func launchQuickSearchResult(_ result: QuickSearchResult) {
        quickSearchErrorMessage = nil
        launchApp(
            result.app,
            closeQuickSearchOnSuccess: true,
            closeOverlayOnSuccess: true,
            onFailure: {
                quickSearchErrorMessage = tr("quickSearch.launchFailed")
                quickSearchFocusToken &+= 1
                NSAccessibility.post(
                    element: NSApp.mainWindow as Any,
                    notification: .announcementRequested,
                    userInfo: [.announcement: tr("quickSearch.launchFailed")]
                )
            }
        )
    }
    // MARK: - Normal Content
@@ -2304,12 +2532,17 @@
            )
            let store = smartStartResult.store
            let apps = TagEditor.annotate(apps: scannedApps, store: store)
            let quickSearchDocs = QuickSearchEngine.makeDocuments(apps: apps, store: store)
            let colors = store.tags.mapValues { $0.color }
            let order = TagEditor.orderedTagNames()
            DispatchQueue.main.async {
                allApps = apps
                quickSearchDocuments = quickSearchDocs
                tagColors = colors
                draggedTagNames = order
                if quickSearchVisible {
                    refreshQuickSearchResults()
                }
                handleSmartStartRunResult(smartStartResult)
                if forceLayoutRefresh {
                    finishDropRefreshAfterMinimumDuration()
@@ -2325,15 +2558,37 @@
        }
    }
    func openApp(_ app: AppInfo) {
    private func launchApp(
        _ app: AppInfo,
        closeQuickSearchOnSuccess: Bool = false,
        closeOverlayOnSuccess: Bool = true,
        onFailure: (() -> Void)? = nil
    ) {
        appDragModeActive = false
        endTagNavReorder()
        hideOverlay()
        DispatchQueue.global(qos: .utility).async {
            TagEditor.recordLauncherOpen(for: app.path.path)
        }
        let configuration = NSWorkspace.OpenConfiguration()
        NSWorkspace.shared.openApplication(at: app.path, configuration: configuration)
        NSWorkspace.shared.openApplication(at: app.path, configuration: configuration) { _, error in
            DispatchQueue.main.async {
                guard error == nil else {
                    onFailure?()
                    return
                }
                DispatchQueue.global(qos: .utility).async {
                    TagEditor.recordLauncherOpen(for: app.path.path)
                }
                if closeQuickSearchOnSuccess {
                    closeQuickSearch(hideOverlayIfNeeded: false)
                }
                if closeOverlayOnSuccess {
                    hideOverlay()
                }
            }
        }
    }
    func openApp(_ app: AppInfo) {
        launchApp(app)
    }
}
Apptag/DataLayer.swift
@@ -339,6 +339,7 @@
        var uncommonAppPaths: [String] = []  // special marker; does not affect normal groups
        var uncommonSources: [String: UncommonSource] = [:]  // current uncommon source: auto/manual
        var appOpenCounts: [String: Int] = [:]  // launches opened from TagLauncher
        var appLastOpenedAt: [String: Date] = [:]  // successful launches opened from TagLauncher
        var knownAppPaths: [String] = []  // baseline set to detect newly installed apps
        var appNotes: [String: String] = [:]  // path → user note; retained even if marker is removed
        var disabledSystemCategoryIDs: [SmartCategoryID] = []  // system categories the user deleted
@@ -353,6 +354,7 @@
            case uncommonAppPaths
            case uncommonSources
            case appOpenCounts
            case appLastOpenedAt
            case knownAppPaths
            case appNotes
            case disabledSystemCategoryIDs
@@ -371,6 +373,7 @@
            uncommonAppPaths = try container.decodeIfPresent([String].self, forKey: .uncommonAppPaths) ?? []
            uncommonSources = try container.decodeIfPresent([String: UncommonSource].self, forKey: .uncommonSources) ?? [:]
            appOpenCounts = try container.decodeIfPresent([String: Int].self, forKey: .appOpenCounts) ?? [:]
            appLastOpenedAt = try container.decodeIfPresent([String: Date].self, forKey: .appLastOpenedAt) ?? [:]
            knownAppPaths = try container.decodeIfPresent([String].self, forKey: .knownAppPaths) ?? []
            appNotes = try container.decodeIfPresent([String: String].self, forKey: .appNotes) ?? [:]
            disabledSystemCategoryIDs = try container.decodeIfPresent(
@@ -1181,7 +1184,21 @@
        }
        let newPaths = scannedPaths.subtracting(knownPaths)
        guard !newPaths.isEmpty else { return store }
        let removedPaths = knownPaths.subtracting(scannedPaths)
        if !removedPaths.isEmpty {
            for path in removedPaths {
                store.appOpenCounts.removeValue(forKey: path)
                store.appLastOpenedAt.removeValue(forKey: path)
            }
            store.knownAppPaths = knownPaths.subtracting(removedPaths).sorted()
        }
        guard !newPaths.isEmpty else {
            if !removedPaths.isEmpty {
                TagDatabase.save(store)
            }
            return store
        }
        var uncommonPaths = Set(store.uncommonAppPaths)
        let appsByPath = Dictionary(uniqueKeysWithValues: apps.map { ($0.path.path, $0) })
@@ -1201,7 +1218,7 @@
        let newApps = apps.filter { newPaths.contains($0.path.path) }
        _ = seedDefaultAppleAppNotes(for: newApps, in: &store)
        store.uncommonAppPaths = uncommonPaths.sorted()
        store.knownAppPaths = knownPaths.union(newPaths).sorted()
        store.knownAppPaths = Set(store.knownAppPaths).union(newPaths).sorted()
        TagDatabase.save(store)
        return store
    }
@@ -1253,6 +1270,7 @@
    static func recordLauncherOpen(for path: String) {
        var store = TagDatabase.load()
        store.appOpenCounts[path] = (store.appOpenCounts[path] ?? 0) + 1
        store.appLastOpenedAt[path] = Date()
        if store.uncommonSources[path] == .auto,
           store.appOpenCounts[path, default: 0] >= TagDatabase.autoUncommonOpenThreshold {
Apptag/Info.plist
@@ -19,9 +19,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>7.3.5</string>
    <string>7.5.1</string>
    <key>CFBundleVersion</key>
    <string>744</string>
    <string>751</string>
    <key>LSMinimumSystemVersion</key>
    <string>15.0</string>
    <key>NSHighResolutionCapable</key>
Apptag/Localization/ar-Najdi.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "مخطط البداية الذكي للنظام",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/ar.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "مخطط البداية الذكي للنظام",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/cs.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Chytré počáteční schéma systému",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/da.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Systemets smarte startskema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/de.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Intelligentes System-Startschema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/en.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "System smart initial scheme",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/es.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Esquema inicial inteligente del sistema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/fr.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Schéma initial intelligent du système",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/id.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Skema awal pintar sistem",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/it.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Schema iniziale intelligente di sistema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/ja.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "システム初期スマート分類",
  "settings.bubbleDisplayScope": "吹き出しの表示対象:",
  "settings.bubbleAllApps": "すべてのアプリ",
  "settings.bubbleUncommonOnly": "あまり使わないアプリのみ"
  "settings.bubbleUncommonOnly": "あまり使わないアプリのみ",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/ko.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "시스템 스마트 초기 분류",
  "settings.bubbleDisplayScope": "말풍선 표시 대상:",
  "settings.bubbleAllApps": "모든 앱",
  "settings.bubbleUncommonOnly": "자주 쓰지 않는 앱만"
  "settings.bubbleUncommonOnly": "자주 쓰지 않는 앱만",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/ms.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Skema awal pintar sistem",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/nb.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Systemets smarte startskjema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/nl.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Slim beginschema van systeem",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/nn.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Systemets smarte startskjema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/no.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Systemets smarte startskjema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/pl.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Inteligentny schemat początkowy systemu",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/pt-BR.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Esquema inicial inteligente do sistema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/ro.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Schema inteligentă inițială a sistemului",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/ru.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Интеллектуальная начальная схема системы",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/sr-Cyrl.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Паметна почетна шема система",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/sv.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Systemets smarta startschema",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/th.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "ชุดเริ่มต้นอัจฉริยะของระบบ",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/tr.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Sistem akıllı başlangıç şeması",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/uk.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Розумна початкова схема системи",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/vi.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "Sơ đồ khởi tạo thông minh của hệ thống",
  "settings.bubbleDisplayScope": "Bubble tips show for:",
  "settings.bubbleAllApps": "All apps",
  "settings.bubbleUncommonOnly": "Uncommon apps only"
  "settings.bubbleUncommonOnly": "Uncommon apps only",
  "quickSearch.title": "Quick Search",
  "quickSearch.placeholder": "Search apps, tags, and notes",
  "quickSearch.inputAccessibility": "Quick Search input",
  "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.",
  "quickSearch.noResults": "No apps found.",
  "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.",
  "quickSearch.tagPrefix": "Tag",
  "quickSearch.hotkeys": "Keyboard shortcuts",
  "quickSearch.mainHotkey": "App list",
  "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.",
  "quickSearch.directHotkey": "Quick Search (global)",
  "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.record": "Change",
  "quickSearch.recording": "Recording",
  "quickSearch.clear": "Clear",
  "quickSearch.restoreDefault": "Restore default",
  "quickSearch.retry": "Retry registration",
  "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.",
  "quickSearch.disabled": "Disabled",
  "quickSearch.blocked": "Blocked",
  "quickSearch.previousStillActive": "Previous shortcut remains active:",
  "quickSearch.status.Active": "Active",
  "quickSearch.status.Conflict": "Conflict",
  "quickSearch.status.Disabled": "Disabled",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.",
  "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.",
  "quickSearch.loading": "Preparing search index…",
  "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings",
  "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)",
  "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.",
  "quickSearch.internalHotkeyStatus": "App list only",
  "quickSearch.globalHotkey": "Quick Search (global)",
  "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.",
  "quickSearch.spaceDisplay": "Space",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here."
}
Apptag/Localization/zh-Hans.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "系统智能化初始分类",
  "settings.bubbleDisplayScope": "气泡提示框显示位置:",
  "settings.bubbleAllApps": "全部应用",
  "settings.bubbleUncommonOnly": "仅不常用应用"
  "settings.bubbleUncommonOnly": "仅不常用应用",
  "quickSearch.title": "快捷搜索",
  "quickSearch.placeholder": "搜索 App、标签和备注",
  "quickSearch.inputAccessibility": "快捷搜索输入框",
  "quickSearch.emptyPrompt": "开始输入以搜索 App、标签和备注。",
  "quickSearch.noResults": "未找到 App。",
  "quickSearch.launchFailed": "无法打开这个 App。请重试或选择其他 App。",
  "quickSearch.tagPrefix": "标签",
  "quickSearch.hotkeys": "快捷键",
  "quickSearch.mainHotkey": "主界面",
  "quickSearch.mainHotkeyDesc": "打开 TagLauncher 应用列表。",
  "quickSearch.directHotkey": "快捷搜索(全局)",
  "quickSearch.directHotkeyDesc": "不在 TagLauncher 主界面时,直接打开快捷搜索。全局有效。",
  "quickSearch.record": "修改",
  "quickSearch.recording": "录制中",
  "quickSearch.clear": "清除",
  "quickSearch.restoreDefault": "恢复默认",
  "quickSearch.retry": "重试注册",
  "quickSearch.recordingHint": "按下新的全局快捷键。按 Esc 取消。",
  "quickSearch.disabled": "已关闭",
  "quickSearch.blocked": "被阻塞",
  "quickSearch.previousStillActive": "之前可用的快捷键仍保持可用:",
  "quickSearch.status.Active": "已启用",
  "quickSearch.status.Conflict": "冲突",
  "quickSearch.status.Disabled": "已关闭",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space 已被 Spotlight 使用。请在 macOS 键盘快捷键中修改 Spotlight,或选择其他快捷键。",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space 已被 Finder 搜索窗口使用。请在 macOS 键盘快捷键中修改,或选择其他快捷键。",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space 已被“上一个输入法”使用。请在 macOS 键盘快捷键中修改,或选择其他快捷键。",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space 已被“下一个输入法”使用。请在 macOS 键盘快捷键中修改,或选择其他快捷键。",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space 已被“表情与符号”使用。请在 macOS 键盘快捷键中修改,或选择其他快捷键。",
  "quickSearch.hotkeyConflict.generic": "这个快捷键已被 macOS 或其他 App 使用。请选择其他快捷键,或先修改其他 App 中的快捷键后再重试。",
  "quickSearch.loading": "正在准备搜索索引…",
  "quickSearch.openKeyboardSettings": "打开 macOS 键盘快捷键设置",
  "quickSearch.internalHotkey": "快捷搜索(TagLauncher 内部)",
  "quickSearch.internalHotkeyDesc": "仅在 TagLauncher 主界面有效。",
  "quickSearch.internalHotkeyStatus": "仅主界面有效",
  "quickSearch.globalHotkey": "快捷搜索(全局)",
  "quickSearch.globalHotkeyDesc": "不在 TagLauncher 主界面时,直接打开快捷搜索。全局有效。",
  "quickSearch.spaceDisplay": "Space(空格键)",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space 可能已被 macOS 的输入法或地球键快捷键占用。请打开 macOS 键盘快捷键设置关闭或改掉对应系统快捷键,或在这里选择其他组合。"
}
Apptag/Localization/zh-Hant.json
@@ -142,5 +142,44 @@
  "scheme.systemSmartStart": "系統智慧化初始分類",
  "settings.bubbleDisplayScope": "氣泡提示框顯示位置:",
  "settings.bubbleAllApps": "全部 App",
  "settings.bubbleUncommonOnly": "僅不常用 App"
  "settings.bubbleUncommonOnly": "僅不常用 App",
  "quickSearch.title": "快捷搜尋",
  "quickSearch.placeholder": "搜尋 App、標籤和備註",
  "quickSearch.inputAccessibility": "快捷搜尋輸入框",
  "quickSearch.emptyPrompt": "開始輸入以搜尋 App、標籤和備註。",
  "quickSearch.noResults": "找不到 App。",
  "quickSearch.launchFailed": "無法打開這個 App。請重試或選擇其他 App。",
  "quickSearch.tagPrefix": "標籤",
  "quickSearch.hotkeys": "快捷鍵",
  "quickSearch.mainHotkey": "主介面",
  "quickSearch.mainHotkeyDesc": "打開 TagLauncher 應用列表。",
  "quickSearch.directHotkey": "快捷搜尋(全域)",
  "quickSearch.directHotkeyDesc": "不在 TagLauncher 主介面時,直接打開快捷搜尋。全域有效。",
  "quickSearch.record": "修改",
  "quickSearch.recording": "錄製中",
  "quickSearch.clear": "清除",
  "quickSearch.restoreDefault": "恢復預設",
  "quickSearch.retry": "重試註冊",
  "quickSearch.recordingHint": "按下新的全域快捷鍵。按 Esc 取消。",
  "quickSearch.disabled": "已關閉",
  "quickSearch.blocked": "被阻塞",
  "quickSearch.previousStillActive": "之前可用的快捷鍵仍保持可用:",
  "quickSearch.status.Active": "已啟用",
  "quickSearch.status.Conflict": "衝突",
  "quickSearch.status.Disabled": "已關閉",
  "quickSearch.hotkeyConflict.spotlight": "Command + Space 已被 Spotlight 使用。請在 macOS 鍵盤快捷鍵中修改 Spotlight,或選擇其他快捷鍵。",
  "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space 已被 Finder 搜尋視窗使用。請在 macOS 鍵盤快捷鍵中修改,或選擇其他快捷鍵。",
  "quickSearch.hotkeyConflict.previousInput": "Control + Space 已被「上一個輸入法」使用。請在 macOS 鍵盤快捷鍵中修改,或選擇其他快捷鍵。",
  "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space 已被「下一個輸入法」使用。請在 macOS 鍵盤快捷鍵中修改,或選擇其他快捷鍵。",
  "quickSearch.hotkeyConflict.emoji": "Control + Command + Space 已被「表情與符號」使用。請在 macOS 鍵盤快捷鍵中修改,或選擇其他快捷鍵。",
  "quickSearch.hotkeyConflict.generic": "這個快捷鍵已被 macOS 或其他 App 使用。請選擇其他快捷鍵,或先修改其他 App 中的快捷鍵後再重試。",
  "quickSearch.loading": "正在準備搜尋索引…",
  "quickSearch.openKeyboardSettings": "打開 macOS 鍵盤快捷鍵設定",
  "quickSearch.internalHotkey": "快捷搜尋(TagLauncher 內部)",
  "quickSearch.internalHotkeyDesc": "僅在 TagLauncher 主介面有效。",
  "quickSearch.internalHotkeyStatus": "僅主介面有效",
  "quickSearch.globalHotkey": "快捷搜尋(全域)",
  "quickSearch.globalHotkeyDesc": "不在 TagLauncher 主介面時,直接打開快捷搜尋。全域有效。",
  "quickSearch.spaceDisplay": "Space(空格鍵)",
  "quickSearch.hotkeyConflict.fnSpace": "Fn + Space 可能已被 macOS 的輸入法或地球鍵快捷鍵佔用。請打開 macOS 鍵盤快捷鍵設定關閉或改掉對應系統快捷鍵,或在這裡選擇其他組合。"
}
Apptag/PreferencesView.swift
@@ -31,6 +31,9 @@
    @State private var categoryScheme = TagDatabase.CategorySchemeState()
    @State private var isApplyingSystemScheme = false
    @State private var showApplySystemSchemeConfirmation = false
    @State private var recordingHotkeyKind: LauncherHotkeyKind? = nil
    @State private var hotkeyCaptureMonitor: Any? = nil
    @State private var hotkeyRefreshToken = 0
    private func scanApps() {
        DispatchQueue.global(qos: .userInitiated).async {
@@ -339,6 +342,130 @@
        .contentShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
    }
    private func applyHotkey(_ hotkey: LauncherHotkey?, for kind: LauncherHotkeyKind) {
        (NSApp.delegate as? AppDelegate)?.applyHotkey(hotkey, for: kind)
        hotkeyRefreshToken &+= 1
    }
    private func retryHotkey(_ kind: LauncherHotkeyKind) {
        (NSApp.delegate as? AppDelegate)?.retryHotkeyRegistration(for: kind)
        hotkeyRefreshToken &+= 1
    }
    private func openKeyboardShortcutsSettings() {
        let urls = [
            URL(string: "x-apple.systempreferences:com.apple.Keyboard-Settings.extension?Shortcuts"),
            URL(string: "x-apple.systempreferences:com.apple.preference.keyboard?shortcuts")
        ].compactMap { $0 }
        guard let url = urls.first else { return }
        NSWorkspace.shared.open(url)
    }
    private func hotkeySettingsPanel() -> some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                Text(tr("quickSearch.hotkeys"))
                    .font(.system(size: 13, weight: .semibold))
                    .foregroundStyle(.secondary)
                Spacer()
                Button(action: openKeyboardShortcutsSettings) {
                    Label(tr("quickSearch.openKeyboardSettings"), systemImage: "keyboard")
                        .font(.system(size: 13, weight: .semibold))
                        .lineLimit(1)
                }
                .buttonStyle(.bordered)
                .help(tr("quickSearch.openKeyboardSettings"))
                .accessibilityLabel(tr("quickSearch.openKeyboardSettings"))
            }
            HotkeySettingRow(
                title: tr("quickSearch.mainHotkey"),
                description: tr("quickSearch.mainHotkeyDesc"),
                kind: .main,
                isRecording: recordingHotkeyKind == .main,
                allowsEditing: false,
                refreshToken: hotkeyRefreshToken,
                onBeginRecording: { },
                onRestoreDefault: nil,
                onRetry: { },
                onCancelRecording: cancelHotkeyRecording
            )
            StaticHotkeyInfoRow(
                title: tr("quickSearch.internalHotkey"),
                description: tr("quickSearch.internalHotkeyDesc"),
                displayText: tr("quickSearch.spaceDisplay"),
                statusText: tr("quickSearch.internalHotkeyStatus")
            )
            HotkeySettingRow(
                title: tr("quickSearch.globalHotkey"),
                description: tr("quickSearch.globalHotkeyDesc"),
                kind: .quickSearch,
                isRecording: recordingHotkeyKind == .quickSearch,
                allowsEditing: true,
                refreshToken: hotkeyRefreshToken,
                onBeginRecording: { beginHotkeyRecording(.quickSearch) },
                onRestoreDefault: { applyHotkey(.defaultQuickSearch, for: .quickSearch) },
                onRetry: { retryHotkey(.quickSearch) },
                onCancelRecording: cancelHotkeyRecording
            )
        }
        .id(hotkeyRefreshToken)
    }
    private func beginHotkeyRecording(_ kind: LauncherHotkeyKind) {
        removeHotkeyCaptureMonitor()
        recordingHotkeyKind = kind
        postHotkeyRecordingState(active: true)
        hotkeyCaptureMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
            handleCapturedHotkeyEvent(event, for: kind)
        }
    }
    private func handleCapturedHotkeyEvent(_ event: NSEvent, for kind: LauncherHotkeyKind) -> NSEvent? {
        if event.keyCode == UInt16(kVK_Escape) {
            cancelHotkeyRecording()
            return nil
        }
        guard let hotkey = LauncherHotkey.from(event: event) else {
            NSSound.beep()
            return nil
        }
        finishHotkeyRecording()
        applyHotkey(hotkey, for: kind)
        return nil
    }
    private func cancelHotkeyRecording() {
        finishHotkeyRecording()
    }
    private func finishHotkeyRecording() {
        guard recordingHotkeyKind != nil || hotkeyCaptureMonitor != nil else { return }
        recordingHotkeyKind = nil
        removeHotkeyCaptureMonitor()
        postHotkeyRecordingState(active: false)
    }
    private func removeHotkeyCaptureMonitor() {
        if let hotkeyCaptureMonitor {
            NSEvent.removeMonitor(hotkeyCaptureMonitor)
            self.hotkeyCaptureMonitor = nil
        }
    }
    private func postHotkeyRecordingState(active: Bool) {
        NotificationCenter.default.post(
            name: .tagLauncherModalInteractionChanged,
            object: nil,
            userInfo: [
                "active": active,
                "source": "hotkeyRecording"
            ]
        )
    }
    var body: some View {
        ZStack {
            TabView {
@@ -380,102 +507,112 @@
                    .frame(maxHeight: 250)
                    Spacer(minLength: 0)
                    ShortcutHintView()
                        .frame(maxWidth: .infinity, alignment: .center)
                        .padding(.bottom, 2)
                }
                .frame(maxWidth: settingsContentWidth, alignment: .leading)
                .tabItem { Label(tr("settings.language"), systemImage: "globe") }
                .padding()
                // Tab 2: General
            VStack(alignment: .leading, spacing: 0) {
                // Toggle row — centered as a rectangular block
                HStack(spacing: 20) {
                    if AppDelegate.supportsLaunchAtLogin {
                        Toggle(tr("settings.launchAtLogin"), isOn: $launchAtLogin)
                            .onChange(of: launchAtLogin) { _, enabled in
                                if enabled {
                                    AppDelegate.enableLaunchAtLogin()
                                } else {
                                    AppDelegate.disableLaunchAtLogin()
                ScrollView(.vertical, showsIndicators: true) {
                    VStack(alignment: .leading, spacing: 0) {
                        // Toggle row — centered as a rectangular block
                        HStack(spacing: 20) {
                            if AppDelegate.supportsLaunchAtLogin {
                                Toggle(tr("settings.launchAtLogin"), isOn: $launchAtLogin)
                                    .onChange(of: launchAtLogin) { _, enabled in
                                        if enabled {
                                            AppDelegate.enableLaunchAtLogin()
                                        } else {
                                            AppDelegate.disableLaunchAtLogin()
                                        }
                                    }
                            }
                            Toggle(tr("settings.showInDock"), isOn: $showDockIcon)
                                .onChange(of: showDockIcon) { _, _ in
                                    AppDelegate.refreshChromeSettings()
                                }
                            }
                    }
                    Toggle(tr("settings.showInDock"), isOn: $showDockIcon)
                        .onChange(of: showDockIcon) { _, _ in
                            AppDelegate.refreshChromeSettings()
                            Toggle(tr("settings.hideAppNames"), isOn: $hideAppNames)
                        }
                    Toggle(tr("settings.hideAppNames"), isOn: $hideAppNames)
                }
                .frame(maxWidth: .infinity, alignment: .center)
                .padding(.bottom, 16)
                        .frame(maxWidth: .infinity, alignment: .center)
                        .padding(.bottom, 16)
                Divider()
                    .padding(.bottom, 16)
                        Divider()
                            .padding(.bottom, 16)
                VStack(spacing: 18) {
                    generalSettingRow(tr("settings.appListStyle"), description: tr("settings.flatDesc")) {
                        VStack(spacing: 8) {
                            DisplayModeOptionButton(
                                mode: "flat",
                                title: tr("settings.flat"),
                                isSelected: displayMode == "flat"
                            ) {
                                displayMode = "flat"
                            }
                            LazyVGrid(columns: displayModeColumns, alignment: .leading, spacing: 8) {
                                ForEach(containerDisplayModeOptions, id: \.id) { option in
                        VStack(spacing: 18) {
                            generalSettingRow(tr("settings.appListStyle"), description: tr("settings.flatDesc")) {
                                VStack(spacing: 8) {
                                    DisplayModeOptionButton(
                                        mode: option.id,
                                        title: option.title,
                                        isSelected: displayMode == option.id
                                        mode: "flat",
                                        title: tr("settings.flat"),
                                        isSelected: displayMode == "flat"
                                    ) {
                                        displayMode = option.id
                                        displayMode = "flat"
                                    }
                                    LazyVGrid(columns: displayModeColumns, alignment: .leading, spacing: 8) {
                                        ForEach(containerDisplayModeOptions, id: \.id) { option in
                                            DisplayModeOptionButton(
                                                mode: option.id,
                                                title: option.title,
                                                isSelected: displayMode == option.id
                                            ) {
                                                displayMode = option.id
                                            }
                                        }
                                    }
                                }
                                .frame(width: generalControlWidth, alignment: .leading)
                            }
                            generalSettingRow(tr("settings.tagPosition"), description: tr("settings.tagPosDesc")) {
                                Picker("", selection: $tagPosition) {
                                    Text(tr("settings.left")).tag("left")
                                    Text(tr("settings.right")).tag("right")
                                    Text(tr("settings.top")).tag("top")
                                }
                                .pickerStyle(.segmented)
                                .frame(width: compactPickerWidth, alignment: .leading)
                            }
                            generalSettingRow(tr("settings.tagFontSize"), description: tr("settings.tagFontDesc")) {
                                Picker("", selection: $tagFontSize) {
                                    ForEach([16.0, 18.0, 20.0, 22.0, 24.0, 26.0], id: \.self) { size in
                                        Text("\(Int(size))").tag(size)
                                    }
                                }
                                .pickerStyle(.segmented)
                                .frame(width: compactPickerWidth, alignment: .leading)
                            }
                            generalSettingRow(tr("settings.iconSize"), description: tr("settings.iconSizeDesc")) {
                                Picker("", selection: $iconSize) {
                                    ForEach([40.0, 48.0, 56.0, 64.0, 72.0, 80.0], id: \.self) { size in
                                        Text("\(Int(size))").tag(size)
                                    }
                                }
                                .pickerStyle(.segmented)
                                .frame(width: compactPickerWidth, alignment: .leading)
                            }
                        }
                        .frame(width: generalControlWidth, alignment: .leading)
                    }
                    generalSettingRow(tr("settings.tagPosition"), description: tr("settings.tagPosDesc")) {
                        Picker("", selection: $tagPosition) {
                            Text(tr("settings.left")).tag("left")
                            Text(tr("settings.right")).tag("right")
                            Text(tr("settings.top")).tag("top")
                        }
                        .pickerStyle(.segmented)
                        .frame(width: compactPickerWidth, alignment: .leading)
                    }
                    generalSettingRow(tr("settings.tagFontSize"), description: tr("settings.tagFontDesc")) {
                        Picker("", selection: $tagFontSize) {
                            ForEach([16.0, 18.0, 20.0, 22.0, 24.0, 26.0], id: \.self) { size in
                                Text("\(Int(size))").tag(size)
                            }
                        }
                        .pickerStyle(.segmented)
                        .frame(width: compactPickerWidth, alignment: .leading)
                    }
                    generalSettingRow(tr("settings.iconSize"), description: tr("settings.iconSizeDesc")) {
                        Picker("", selection: $iconSize) {
                            ForEach([40.0, 48.0, 56.0, 64.0, 72.0, 80.0], id: \.self) { size in
                                Text("\(Int(size))").tag(size)
                            }
                        }
                        .pickerStyle(.segmented)
                        .frame(width: compactPickerWidth, alignment: .leading)
                    }
                    .frame(maxWidth: generalContentWidth, alignment: .center)
                    .padding()
                }
            }
            .frame(maxWidth: generalContentWidth, alignment: .center)
            .padding()
            .tabItem { Label(tr("settings.general"), systemImage: "gearshape") }
                .tabItem { Label(tr("settings.general"), systemImage: "gearshape") }
            // Tab 3: Tags
            // Tab 3: Hotkeys
            ScrollView(.vertical, showsIndicators: true) {
                VStack(alignment: .leading, spacing: 16) {
                    hotkeySettingsPanel()
                        .frame(width: generalContentWidth, alignment: .leading)
                }
                .frame(maxWidth: generalContentWidth, alignment: .center)
                .padding()
            }
            .tabItem { Label(tr("quickSearch.hotkeys"), systemImage: "keyboard") }
            // Tab 4: Tags
            VStack(spacing: 0) {
                TagEditorView(
                    tagColors: $tagColors,
@@ -486,7 +623,7 @@
            .tabItem { Label(tr("settings.tags"), systemImage: "tag.fill") }
            .onAppear { scanApps() }
            // Tab 4: Data
            // Tab 5: Data
            VStack(spacing: 0) {
                Spacer(minLength: 32)
@@ -626,7 +763,7 @@
            .padding()
            .onAppear { refreshDataState() }
            // Tab 5: About
            // Tab 6: About
            VStack(spacing: 0) {
                Spacer(minLength: 72)
@@ -661,8 +798,6 @@
                .frame(width: 560, alignment: .leading)
                Spacer(minLength: 30)
                ShortcutHintView()
                    .frame(maxWidth: .infinity, alignment: .center)
                Spacer(minLength: 18)
            }
            .tabItem { Label(tr("settings.about"), systemImage: "info.circle") }
@@ -683,6 +818,9 @@
            }
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherDataDidChange)) { _ in
                refreshDataState()
            }
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherHotkeysChanged)) { _ in
                hotkeyRefreshToken &+= 1
            }
            if isRefreshingLanguage {
@@ -718,6 +856,9 @@
        .onAppear {
            syncSelectedLanguage()
        }
        .onDisappear {
            finishHotkeyRecording()
        }
    }
    private func showLanguageRefresh() {
@@ -727,6 +868,250 @@
                isRefreshingLanguage = false
            }
        }
    }
}
private struct StaticHotkeyInfoRow: View {
    let title: String
    let description: String
    let displayText: String
    let statusText: String
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack(alignment: .top, spacing: 12) {
                VStack(alignment: .leading, spacing: 3) {
                    Text(title)
                        .font(.system(size: 13, weight: .semibold))
                    Text(description)
                        .font(.caption)
                        .foregroundStyle(.secondary)
                        .fixedSize(horizontal: false, vertical: true)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
                VStack(alignment: .leading, spacing: 4) {
                    Text(displayText)
                        .font(.system(size: 15, weight: .semibold))
                        .lineLimit(1)
                        .minimumScaleFactor(0.85)
                    Text(statusText)
                        .font(.system(size: 11, weight: .semibold))
                        .foregroundStyle(Color.secondary)
                        .padding(.horizontal, 8)
                        .padding(.vertical, 3)
                        .background(Capsule().fill(Color.secondary.opacity(0.13)))
                }
                .frame(width: 180, alignment: .leading)
            }
        }
        .padding(12)
        .background(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color(nsColor: .controlBackgroundColor))
        )
        .overlay(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .stroke(Color.secondary.opacity(0.14), lineWidth: 1)
        )
    }
}
private struct HotkeySettingRow: View {
    let title: String
    let description: String
    let kind: LauncherHotkeyKind
    let isRecording: Bool
    let allowsEditing: Bool
    let refreshToken: Int
    let onBeginRecording: () -> Void
    let onRestoreDefault: (() -> Void)?
    let onRetry: () -> Void
    let onCancelRecording: () -> Void
    private var activeHotkey: LauncherHotkey? {
        _ = refreshToken
        return LauncherHotkeyStore.hotkey(for: kind)
    }
    private var pendingHotkey: LauncherHotkey? {
        _ = refreshToken
        return LauncherHotkeyStore.pendingHotkey(for: kind)
    }
    private var status: LauncherHotkeyStatus {
        _ = refreshToken
        return LauncherHotkeyStore.status(for: kind)
    }
    private var conflictMessage: String {
        _ = refreshToken
        return LauncherHotkeyStore.conflictMessage(for: kind)
    }
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack(alignment: .top, spacing: 12) {
                VStack(alignment: .leading, spacing: 3) {
                    Text(title)
                        .font(.system(size: 13, weight: .semibold))
                    Text(description)
                        .font(.caption)
                        .foregroundStyle(.secondary)
                        .fixedSize(horizontal: false, vertical: true)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
                VStack(alignment: .leading, spacing: 4) {
                    Text(displayText)
                        .font(.system(size: 15, weight: .semibold))
                        .lineLimit(1)
                        .minimumScaleFactor(0.85)
                    statusLabel
                }
                .frame(width: 180, alignment: .leading)
            }
            if allowsEditing {
                HStack(spacing: 8) {
                    hotkeyButton(
                        isRecording ? tr("quickSearch.recording") : tr("quickSearch.record"),
                        systemImage: isRecording ? "keyboard.badge.ellipsis" : "keyboard",
                        prominent: true,
                        action: onBeginRecording
                    )
                    if let onRestoreDefault {
                        hotkeyButton(
                            tr("quickSearch.restoreDefault"),
                            systemImage: "arrow.counterclockwise",
                            prominent: false,
                            action: onRestoreDefault
                        )
                    }
                    if status == .conflict {
                        hotkeyButton(
                            tr("quickSearch.retry"),
                            systemImage: "arrow.clockwise",
                            prominent: false,
                            action: onRetry
                        )
                    }
                    Spacer(minLength: 0)
                }
            }
            if allowsEditing && isRecording {
                HStack(spacing: 8) {
                    Image(systemName: "keyboard")
                    Text(tr("quickSearch.recordingHint"))
                    Spacer(minLength: 0)
                    Button(tr("settings.cancel"), action: onCancelRecording)
                        .buttonStyle(.borderless)
                }
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(Color.accentColor)
                .padding(.horizontal, 10)
                .padding(.vertical, 10)
                .background(RoundedRectangle(cornerRadius: 7).fill(Color.accentColor.opacity(0.10)))
            }
            if status == .conflict && !conflictMessage.isEmpty {
                HotkeyConflictNotice(text: conflictDetailText)
            }
        }
        .padding(12)
        .background(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color(nsColor: .controlBackgroundColor))
        )
        .overlay(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .stroke(Color.secondary.opacity(0.14), lineWidth: 1)
        )
    }
    private var displayText: String {
        if status == .conflict, let pendingHotkey {
            return "\(pendingHotkey.displayString) · \(tr("quickSearch.blocked"))"
        }
        return activeHotkey?.displayString ?? tr("quickSearch.disabled")
    }
    private var conflictDetailText: String {
        if let activeHotkey {
            return "\(conflictMessage) \(tr("quickSearch.previousStillActive")) \(activeHotkey.displayString)"
        }
        return conflictMessage
    }
    private var statusLabel: some View {
        Text(tr("quickSearch.status.\(status.rawValue)"))
            .font(.system(size: 11, weight: .semibold))
            .foregroundStyle(statusColor)
            .padding(.horizontal, 8)
            .padding(.vertical, 3)
            .background(Capsule().fill(statusColor.opacity(0.13)))
    }
    private var statusColor: Color {
        switch status {
        case .active: return .green
        case .conflict: return .orange
        case .disabled: return .secondary
        }
    }
    private func hotkeyButton(
        _ title: String,
        systemImage: String,
        prominent: Bool,
        action: @escaping () -> Void
    ) -> some View {
        Button(action: action) {
            Label(title, systemImage: systemImage)
                .font(.system(size: 13, weight: .semibold))
                .lineLimit(1)
                .minimumScaleFactor(0.88)
                .foregroundStyle(prominent ? Color.white : Color.primary)
                .padding(.horizontal, 10)
                .frame(height: 30)
                .background(
                    RoundedRectangle(cornerRadius: 7, style: .continuous)
                        .fill(prominent ? Color.accentColor : Color(nsColor: .controlBackgroundColor))
                )
                .overlay(
                    RoundedRectangle(cornerRadius: 7, style: .continuous)
                        .stroke(prominent ? Color.accentColor.opacity(0.24) : Color.secondary.opacity(0.22), lineWidth: 1)
                )
        }
        .buttonStyle(.plain)
        .help(title)
        .accessibilityLabel(title)
    }
}
private struct HotkeyConflictNotice: View {
    let text: String
    var body: some View {
        HStack(alignment: .top, spacing: 8) {
            Image(systemName: "exclamationmark.triangle.fill")
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(Color.white.opacity(0.94))
                .padding(.top, 1)
            Text(text)
                .font(.system(size: 12, weight: .medium))
                .foregroundStyle(Color.white)
                .fixedSize(horizontal: false, vertical: true)
            Spacer(minLength: 0)
        }
        .padding(.horizontal, 10)
        .padding(.vertical, 9)
        .background(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.black.opacity(0.92))
        )
        .shadow(color: .black.opacity(0.24), radius: 16, y: 8)
    }
}
@@ -924,60 +1309,5 @@
        }
        let palette: [Color] = [.green, .purple, .blue, .orange]
        return palette[index % palette.count].opacity(0.78)
    }
}
private struct ShortcutHintView: View {
    var body: some View {
        HStack(spacing: 12) {
            ShortcutKeycap(symbol: "⌥")
            Text("+")
                .font(.system(size: 22, weight: .semibold))
                .foregroundStyle(.tertiary)
            ShortcutKeycap(symbol: "⇧")
            Text("+")
                .font(.system(size: 22, weight: .semibold))
                .foregroundStyle(.tertiary)
            SpacebarKeycap()
        }
        .accessibilityLabel("⌥ ⇧ Space")
        .allowsHitTesting(false)
    }
}
private struct ShortcutKeycap: View {
    let symbol: String
    var body: some View {
        Text(symbol)
            .font(.system(size: 36, weight: .medium))
            .foregroundStyle(.secondary)
            .frame(width: 74, height: 58)
            .background(KeycapBackground())
    }
}
private struct SpacebarKeycap: View {
    var body: some View {
        ZStack {
            KeycapBackground()
            Capsule()
                .fill(Color.secondary.opacity(0.38))
                .frame(width: 72, height: 3)
                .offset(y: 13)
        }
        .frame(width: 190, height: 58)
    }
}
private struct KeycapBackground: View {
    var body: some View {
        RoundedRectangle(cornerRadius: 10, style: .continuous)
            .fill(Color(nsColor: .controlBackgroundColor))
            .overlay(
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .stroke(Color.primary.opacity(0.12), lineWidth: 1)
            )
            .shadow(color: .black.opacity(0.10), radius: 8, x: 0, y: 3)
    }
}
Apptag/QuickSearch.swift
New file
@@ -0,0 +1,1080 @@
import SwiftUI
import AppKit
import Carbon
extension Notification.Name {
    static let tagLauncherQuickSearchRequested = Notification.Name("TagLauncherQuickSearchRequested")
    static let tagLauncherQuickSearchDismissRequested = Notification.Name("TagLauncherQuickSearchDismissRequested")
    static let tagLauncherQuickSearchVisibilityChanged = Notification.Name("TagLauncherQuickSearchVisibilityChanged")
    static let tagLauncherHotkeysChanged = Notification.Name("TagLauncherHotkeysChanged")
}
enum QuickSearchOpenSource {
    static let mainOverlay = "mainOverlay"
    static let globalHidden = "globalHidden"
    static let globalVisible = "globalVisible"
}
// MARK: - Hotkeys
struct LauncherHotkey: Equatable {
    let keyCode: UInt32
    let modifiers: UInt32
    var serialized: String {
        "\(keyCode):\(modifiers)"
    }
    var displayString: String {
        let ordered: [(UInt32, String)] = [
            (UInt32(controlKey), "⌃"),
            (UInt32(optionKey), "⌥"),
            (UInt32(shiftKey), "⇧"),
            (UInt32(cmdKey), "⌘"),
            (UInt32(kEventKeyModifierFnMask), "Fn+")
        ]
        let modifierGlyphs = ordered
            .filter { modifiers & $0.0 != 0 }
            .map(\.1)
            .joined()
        return modifierGlyphs + LauncherHotkey.keyDisplayName(for: keyCode)
    }
    static var defaultMain: LauncherHotkey {
        LauncherHotkey(keyCode: UInt32(kVK_Space), modifiers: UInt32(shiftKey | optionKey))
    }
    static var defaultQuickSearch: LauncherHotkey {
        LauncherHotkey(keyCode: UInt32(kVK_Space), modifiers: UInt32(kEventKeyModifierFnMask))
    }
    static func deserialize(_ value: String?) -> LauncherHotkey? {
        guard let value, !value.isEmpty else { return nil }
        let parts = value.split(separator: ":")
        guard parts.count == 2,
              let keyCode = UInt32(parts[0]),
              let modifiers = UInt32(parts[1])
        else { return nil }
        return LauncherHotkey(keyCode: keyCode, modifiers: modifiers)
    }
    static func from(event: NSEvent) -> LauncherHotkey? {
        let ignoredKeyCodes: Set<UInt16> = [54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
        guard !ignoredKeyCodes.contains(event.keyCode) else { return nil }
        var carbonModifiers: UInt32 = 0
        if event.modifierFlags.contains(.shift) { carbonModifiers |= UInt32(shiftKey) }
        if event.modifierFlags.contains(.option) { carbonModifiers |= UInt32(optionKey) }
        if event.modifierFlags.contains(.control) { carbonModifiers |= UInt32(controlKey) }
        if event.modifierFlags.contains(.command) { carbonModifiers |= UInt32(cmdKey) }
        if event.modifierFlags.contains(.function) { carbonModifiers |= UInt32(kEventKeyModifierFnMask) }
        guard carbonModifiers != 0 else { return nil }
        return LauncherHotkey(keyCode: UInt32(event.keyCode), modifiers: carbonModifiers)
    }
    static func keyDisplayName(for keyCode: UInt32) -> String {
        switch Int(keyCode) {
        case kVK_Space: return "Space"
        case kVK_Return: return "Return"
        case kVK_Escape: return "Esc"
        case kVK_Delete: return "Delete"
        case kVK_Tab: return "Tab"
        case kVK_LeftArrow: return "←"
        case kVK_RightArrow: return "→"
        case kVK_UpArrow: return "↑"
        case kVK_DownArrow: return "↓"
        case kVK_F1: return "F1"
        case kVK_F2: return "F2"
        case kVK_F3: return "F3"
        case kVK_F4: return "F4"
        case kVK_F5: return "F5"
        case kVK_F6: return "F6"
        case kVK_F7: return "F7"
        case kVK_F8: return "F8"
        case kVK_F9: return "F9"
        case kVK_F10: return "F10"
        case kVK_F11: return "F11"
        case kVK_F12: return "F12"
        default:
            if let scalar = keyCodeToPrintableScalar[Int(keyCode)] {
                return String(scalar)
            }
            return "Key \(keyCode)"
        }
    }
    private static let keyCodeToPrintableScalar: [Int: Character] = [
        kVK_ANSI_A: "A", kVK_ANSI_B: "B", kVK_ANSI_C: "C", kVK_ANSI_D: "D",
        kVK_ANSI_E: "E", kVK_ANSI_F: "F", kVK_ANSI_G: "G", kVK_ANSI_H: "H",
        kVK_ANSI_I: "I", kVK_ANSI_J: "J", kVK_ANSI_K: "K", kVK_ANSI_L: "L",
        kVK_ANSI_M: "M", kVK_ANSI_N: "N", kVK_ANSI_O: "O", kVK_ANSI_P: "P",
        kVK_ANSI_Q: "Q", kVK_ANSI_R: "R", kVK_ANSI_S: "S", kVK_ANSI_T: "T",
        kVK_ANSI_U: "U", kVK_ANSI_V: "V", kVK_ANSI_W: "W", kVK_ANSI_X: "X",
        kVK_ANSI_Y: "Y", kVK_ANSI_Z: "Z", kVK_ANSI_0: "0", kVK_ANSI_1: "1",
        kVK_ANSI_2: "2", kVK_ANSI_3: "3", kVK_ANSI_4: "4", kVK_ANSI_5: "5",
        kVK_ANSI_6: "6", kVK_ANSI_7: "7", kVK_ANSI_8: "8", kVK_ANSI_9: "9"
    ]
}
enum LauncherHotkeyKind: String {
    case main
    case quickSearch
    var storageKey: String {
        switch self {
        case .main: return "mainHotkey"
        case .quickSearch: return "quickSearchHotkey"
        }
    }
    var statusKey: String {
        switch self {
        case .main: return "mainHotkeyStatus"
        case .quickSearch: return "quickSearchHotkeyStatus"
        }
    }
    var conflictMessageKey: String {
        switch self {
        case .main: return "mainHotkeyConflictMessage"
        case .quickSearch: return "quickSearchHotkeyConflictMessage"
        }
    }
    var pendingStorageKey: String {
        switch self {
        case .main: return "mainHotkeyPending"
        case .quickSearch: return "quickSearchHotkeyPending"
        }
    }
    var eventID: UInt32 {
        switch self {
        case .main: return 1
        case .quickSearch: return 2
        }
    }
}
enum LauncherHotkeyStatus: String {
    case active = "Active"
    case conflict = "Conflict"
    case disabled = "Disabled"
}
enum LauncherHotkeyStore {
    static func hotkey(for kind: LauncherHotkeyKind) -> LauncherHotkey? {
        if kind == .main {
            return .defaultMain
        }
        if kind == .quickSearch && !AppDefaults.hasStoredValue(for: kind.storageKey) {
            return .defaultQuickSearch
        }
        return LauncherHotkey.deserialize(UserDefaults.standard.string(forKey: kind.storageKey))
    }
    static func save(_ hotkey: LauncherHotkey?, for kind: LauncherHotkeyKind) {
        UserDefaults.standard.set(hotkey?.serialized ?? "", forKey: kind.storageKey)
        UserDefaults.standard.removeObject(forKey: kind.pendingStorageKey)
    }
    static func savePending(_ hotkey: LauncherHotkey, for kind: LauncherHotkeyKind) {
        UserDefaults.standard.set(hotkey.serialized, forKey: kind.pendingStorageKey)
    }
    static func pendingHotkey(for kind: LauncherHotkeyKind) -> LauncherHotkey? {
        LauncherHotkey.deserialize(UserDefaults.standard.string(forKey: kind.pendingStorageKey))
    }
    static func status(for kind: LauncherHotkeyKind) -> LauncherHotkeyStatus {
        let raw = UserDefaults.standard.string(forKey: kind.statusKey)
        return LauncherHotkeyStatus(rawValue: raw ?? "") ?? (hotkey(for: kind) == nil ? .disabled : .active)
    }
    static func setStatus(_ status: LauncherHotkeyStatus, message: String?, for kind: LauncherHotkeyKind) {
        UserDefaults.standard.set(status.rawValue, forKey: kind.statusKey)
        if let message, !message.isEmpty {
            UserDefaults.standard.set(message, forKey: kind.conflictMessageKey)
        } else {
            UserDefaults.standard.removeObject(forKey: kind.conflictMessageKey)
        }
        NotificationCenter.default.post(name: .tagLauncherHotkeysChanged, object: nil)
    }
    static func conflictMessage(for kind: LauncherHotkeyKind) -> String {
        UserDefaults.standard.string(forKey: kind.conflictMessageKey) ?? ""
    }
    static func knownConflictMessage(for hotkey: LauncherHotkey, status: OSStatus) -> String? {
        guard status != noErr else { return nil }
        switch (hotkey.keyCode, hotkey.modifiers) {
        case (UInt32(kVK_Space), UInt32(cmdKey)):
            return tr("quickSearch.hotkeyConflict.spotlight")
        case (UInt32(kVK_Space), UInt32(optionKey | cmdKey)):
            return tr("quickSearch.hotkeyConflict.finderSearch")
        case (UInt32(kVK_Space), UInt32(controlKey)):
            return tr("quickSearch.hotkeyConflict.previousInput")
        case (UInt32(kVK_Space), UInt32(controlKey | optionKey)):
            return tr("quickSearch.hotkeyConflict.nextInput")
        case (UInt32(kVK_Space), UInt32(controlKey | cmdKey)):
            return tr("quickSearch.hotkeyConflict.emoji")
        case (UInt32(kVK_Space), UInt32(kEventKeyModifierFnMask)):
            return tr("quickSearch.hotkeyConflict.fnSpace")
        default:
            return nil
        }
    }
}
// MARK: - Search Documents
struct QuickSearchDocument: Identifiable {
    var id: URL { app.id }
    let app: AppInfo
    let localizedNames: [String]
    let internalBundleNames: [String]
    let tagNames: [String]
    let note: String
    let bundleIdentifier: String
    let lastOpenedAt: Date?
    let openCount: Int
}
struct QuickSearchResult: Identifiable {
    var id: URL { document.id }
    let document: QuickSearchDocument
    let finalScore: Double
    let textScore: Double
    let bestFieldRank: Int
    let matchedTagName: String?
    let noteSnippet: String?
    var app: AppInfo { document.app }
}
enum QuickSearchEngine {
    private enum FieldKind: Int {
        case name = 0
        case tag = 1
        case note = 2
        case bundleIdentifier = 3
        case internalBundleName = 4
        var weight: Double {
            switch self {
            case .name: return 100
            case .tag: return 70
            case .note: return 45
            case .internalBundleName: return 30
            case .bundleIdentifier: return 20
            }
        }
    }
    private enum MatchKind {
        case exact
        case prefix
        case substring
        case acronym
        case fuzzy
        var weight: Double {
            switch self {
            case .exact: return 100
            case .prefix: return 80
            case .substring: return 60
            case .acronym: return 55
            case .fuzzy: return 35
            }
        }
    }
    private struct Field {
        let kind: FieldKind
        let text: String
        let normalized: String
        let acronym: String
        let pinyinCandidates: [String]
    }
    private struct MatchOptions {
        let allowSubstring: Bool
        let allowFuzzySubsequence: Bool
    }
    private struct TokenMatch {
        let score: Double
        let fieldRank: Int
        let fieldKind: FieldKind
        let originalText: String
    }
    static func makeDocuments(apps: [AppInfo], store: TagDatabase.Store) -> [QuickSearchDocument] {
        apps.map { app in
            QuickSearchDocument(
                app: app,
                localizedNames: localizedNames(for: app),
                internalBundleNames: internalBundleNames(for: app),
                tagNames: app.tags,
                note: store.appNotes[app.path.path] ?? app.note ?? "",
                bundleIdentifier: app.bundleIdentifier ?? "",
                lastOpenedAt: store.appLastOpenedAt[app.path.path],
                openCount: store.appOpenCounts[app.path.path] ?? 0
            )
        }
    }
    static func search(_ query: String, documents: [QuickSearchDocument], limit: Int = 50) -> [QuickSearchResult] {
        let normalizedQuery = normalizeQuery(query)
        guard !normalizedQuery.isEmpty else {
            return emptyQueryResults(documents: documents, limit: min(limit, 6))
        }
        let tokens = normalizedQuery.split(separator: " ").map(String.init)
        let results = documents.compactMap { result(for: $0, tokens: tokens) }
        return results.sorted(by: rank).prefix(limit).map { $0 }
    }
    static func normalizeQuery(_ value: String) -> String {
        value
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .components(separatedBy: .whitespacesAndNewlines)
            .filter { !$0.isEmpty }
            .joined(separator: " ")
            .folding(options: [.caseInsensitive, .diacriticInsensitive], locale: .current)
            .lowercased()
    }
    private static func result(for document: QuickSearchDocument, tokens: [String]) -> QuickSearchResult? {
        let fields = searchableFields(for: document)
        var textScore: Double = 0
        var bestFieldRank = Int.max
        var matchedTagName: String?
        var noteSnippet: String?
        for token in tokens {
            guard let tokenMatch = fields
                .compactMap({ match(token: token, field: $0) })
                .max(by: { $0.score < $1.score })
            else {
                return nil
            }
            textScore += tokenMatch.score
            bestFieldRank = min(bestFieldRank, tokenMatch.fieldRank)
            if tokenMatch.fieldKind == .tag {
                matchedTagName = tokenMatch.originalText
            } else if tokenMatch.fieldKind == .note {
                noteSnippet = snippet(from: document.note, token: token)
            }
        }
        let finalScore = textScore + behaviorBoost(for: document)
        return QuickSearchResult(
            document: document,
            finalScore: finalScore,
            textScore: textScore,
            bestFieldRank: bestFieldRank,
            matchedTagName: matchedTagName,
            noteSnippet: noteSnippet
        )
    }
    private static func searchableFields(for document: QuickSearchDocument) -> [Field] {
        let names = uniqueOrdered([document.app.name] + document.localizedNames)
        let nameFields = names.map {
            Field(
                kind: .name,
                text: $0,
                normalized: normalizeField($0),
                acronym: acronym(for: $0),
                pinyinCandidates: pinyinCandidates(for: $0)
            )
        }
        let tagFields = document.tagNames.map {
            Field(
                kind: .tag,
                text: $0,
                normalized: normalizeField($0),
                acronym: "",
                pinyinCandidates: pinyinCandidates(for: $0)
            )
        }
        let noteFields = document.note.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? [] : [
            Field(
                kind: .note,
                text: document.note,
                normalized: normalizeField(document.note),
                acronym: "",
                pinyinCandidates: pinyinCandidates(for: document.note)
            )
        ]
        let bundleFields = document.bundleIdentifier.isEmpty ? [] : [
            Field(
                kind: .bundleIdentifier,
                text: document.bundleIdentifier,
                normalized: normalizeField(document.bundleIdentifier),
                acronym: "",
                pinyinCandidates: []
            )
        ]
        let internalBundleNameFields = document.internalBundleNames.map {
            Field(
                kind: .internalBundleName,
                text: $0,
                normalized: normalizeField($0),
                acronym: "",
                pinyinCandidates: []
            )
        }
        return nameFields + tagFields + noteFields + bundleFields + internalBundleNameFields
    }
    private static func match(token: String, field: Field) -> TokenMatch? {
        guard let candidate = bestMatchCandidate(token: token, field: field) else { return nil }
        let positionBoost = candidate.0 == .exact ? 0 : max(0, 10 - min(candidate.1, 10))
        let score = field.kind.weight + candidate.0.weight + Double(positionBoost)
        return TokenMatch(
            score: score,
            fieldRank: field.kind.rawValue,
            fieldKind: field.kind,
            originalText: field.text
        )
    }
    private static func bestMatchCandidate(token: String, field: Field) -> (MatchKind, Int)? {
        var candidates: [(MatchKind, Int)] = []
        if let textCandidate = matchCandidate(
            token: token,
            normalized: field.normalized,
            options: matchOptions(for: field.kind)
        ) {
            candidates.append(textCandidate)
        }
        if field.kind == .name && !field.acronym.isEmpty && field.acronym.hasPrefix(token) {
            candidates.append((.acronym, 0))
        }
        for pinyin in field.pinyinCandidates {
            if let pinyinCandidate = matchPinyinCandidate(token: token, normalized: pinyin) {
                candidates.append(pinyinCandidate)
            }
        }
        return candidates.max { lhs, rhs in
            if lhs.0.weight != rhs.0.weight { return lhs.0.weight < rhs.0.weight }
            return lhs.1 > rhs.1
        }
    }
    private static func matchOptions(for fieldKind: FieldKind) -> MatchOptions {
        switch fieldKind {
        case .name:
            return MatchOptions(allowSubstring: true, allowFuzzySubsequence: true)
        case .tag:
            return MatchOptions(allowSubstring: true, allowFuzzySubsequence: true)
        case .note:
            return MatchOptions(allowSubstring: true, allowFuzzySubsequence: false)
        case .bundleIdentifier, .internalBundleName:
            return MatchOptions(allowSubstring: false, allowFuzzySubsequence: false)
        }
    }
    private static func matchCandidate(
        token: String,
        normalized: String,
        options: MatchOptions
    ) -> (MatchKind, Int)? {
        guard !normalized.isEmpty else { return nil }
        if normalized == token {
            return (.exact, 0)
        }
        if normalized.hasPrefix(token) {
            return (.prefix, 0)
        }
        if options.allowSubstring, let range = normalized.range(of: token) {
            return (.substring, normalized.distance(from: normalized.startIndex, to: range.lowerBound))
        }
        if options.allowFuzzySubsequence, token.count >= 4, isSubsequence(token, of: normalized) {
            return (.fuzzy, 10)
        }
        return nil
    }
    private static func matchPinyinCandidate(token: String, normalized: String) -> (MatchKind, Int)? {
        guard !normalized.isEmpty else { return nil }
        if normalized == token {
            return (.exact, 0)
        }
        if normalized.hasPrefix(token) {
            return (.prefix, 0)
        }
        return nil
    }
    private static func emptyQueryResults(documents: [QuickSearchDocument], limit: Int) -> [QuickSearchResult] {
        var used = Set<URL>()
        let recent = documents
            .filter { $0.lastOpenedAt != nil }
            .sorted {
                if ($0.lastOpenedAt ?? .distantPast) != ($1.lastOpenedAt ?? .distantPast) {
                    return ($0.lastOpenedAt ?? .distantPast) > ($1.lastOpenedAt ?? .distantPast)
                }
                return $0.app.name.localizedStandardCompare($1.app.name) == .orderedAscending
            }
        let frequent = documents
            .filter { $0.openCount > 0 }
            .sorted {
                if $0.openCount != $1.openCount { return $0.openCount > $1.openCount }
                return $0.app.name.localizedStandardCompare($1.app.name) == .orderedAscending
            }
        let ordered = (recent + frequent).filter { used.insert($0.id).inserted }
        return ordered.prefix(limit).map {
            QuickSearchResult(
                document: $0,
                finalScore: behaviorBoost(for: $0),
                textScore: 0,
                bestFieldRank: Int.max,
                matchedTagName: nil,
                noteSnippet: nil
            )
        }
    }
    private static func rank(_ lhs: QuickSearchResult, _ rhs: QuickSearchResult) -> Bool {
        if lhs.finalScore != rhs.finalScore { return lhs.finalScore > rhs.finalScore }
        if lhs.textScore != rhs.textScore { return lhs.textScore > rhs.textScore }
        if lhs.bestFieldRank != rhs.bestFieldRank { return lhs.bestFieldRank < rhs.bestFieldRank }
        let leftDate = lhs.document.lastOpenedAt ?? .distantPast
        let rightDate = rhs.document.lastOpenedAt ?? .distantPast
        if leftDate != rightDate { return leftDate > rightDate }
        if lhs.document.openCount != rhs.document.openCount {
            return lhs.document.openCount > rhs.document.openCount
        }
        if lhs.app.name.count != rhs.app.name.count {
            return lhs.app.name.count < rhs.app.name.count
        }
        return lhs.app.name.localizedStandardCompare(rhs.app.name) == .orderedAscending
    }
    private static func behaviorBoost(for document: QuickSearchDocument) -> Double {
        min(recentBoost(for: document.lastOpenedAt) + frequencyBoost(for: document.openCount), 20)
    }
    private static func recentBoost(for date: Date?) -> Double {
        guard let date else { return 0 }
        let age = Date().timeIntervalSince(date)
        if age <= 24 * 60 * 60 { return 15 }
        if age <= 7 * 24 * 60 * 60 { return 10 }
        if age <= 30 * 24 * 60 * 60 { return 5 }
        return 2
    }
    private static func frequencyBoost(for openCount: Int) -> Double {
        min(Double(openCount), 10)
    }
    private static func normalizeField(_ value: String) -> String {
        value
            .folding(options: [.caseInsensitive, .diacriticInsensitive], locale: .current)
            .lowercased()
    }
    private static func pinyinCandidates(for value: String) -> [String] {
        guard value.range(of: #"\p{Han}"#, options: .regularExpression) != nil else { return [] }
        let mutable = NSMutableString(string: value)
        CFStringTransform(mutable, nil, kCFStringTransformToLatin, false)
        CFStringTransform(mutable, nil, kCFStringTransformStripCombiningMarks, false)
        let spaced = normalizeField(mutable as String)
            .components(separatedBy: CharacterSet.alphanumerics.inverted)
            .filter { !$0.isEmpty }
            .joined(separator: " ")
        guard !spaced.isEmpty else { return [] }
        let compact = spaced.replacingOccurrences(of: " ", with: "")
        let initials = spaced
            .split(separator: " ")
            .compactMap(\.first)
            .map(String.init)
            .joined()
        return uniqueOrdered([spaced, compact, initials].filter { !$0.isEmpty })
    }
    private static func isSubsequence(_ token: String, of value: String) -> Bool {
        var searchStart = value.startIndex
        for character in token {
            guard let index = value[searchStart...].firstIndex(of: character) else { return false }
            searchStart = value.index(after: index)
        }
        return true
    }
    private static func acronym(for value: String) -> String {
        var parts: [Character] = []
        var nextStartsWord = true
        var previousWasLowercase = false
        for character in value {
            let current = String(character)
            let isAlphanumeric = current.rangeOfCharacter(from: .alphanumerics) != nil
            guard isAlphanumeric else {
                nextStartsWord = true
                previousWasLowercase = false
                continue
            }
            let isUppercase = current.rangeOfCharacter(from: .uppercaseLetters) != nil
            let isLowercase = current.rangeOfCharacter(from: .lowercaseLetters) != nil
            if nextStartsWord || (isUppercase && previousWasLowercase) {
                parts.append(character)
            }
            nextStartsWord = false
            previousWasLowercase = isLowercase
        }
        return normalizeField(String(parts))
    }
    private static func localizedNames(for app: AppInfo) -> [String] {
        guard let bundle = Bundle(url: app.path) else { return [] }
        let values = [
            bundle.localizedInfoDictionary?["CFBundleDisplayName"] as? String,
            bundle.infoDictionary?["CFBundleDisplayName"] as? String,
            FileManager.default.displayName(atPath: app.path.path).replacingOccurrences(of: ".app", with: "")
        ]
        return uniqueOrdered(values.compactMap { $0 }.compactMap { value in
            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
            return trimmed.isEmpty || trimmed == app.name ? nil : trimmed
        })
    }
    private static func internalBundleNames(for app: AppInfo) -> [String] {
        guard let bundle = Bundle(url: app.path) else { return [] }
        let values = [
            bundle.localizedInfoDictionary?["CFBundleName"] as? String,
            bundle.infoDictionary?["CFBundleName"] as? String
        ]
        return uniqueOrdered(values.compactMap { $0 }.compactMap { value in
            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
            return trimmed.isEmpty || trimmed == app.name ? nil : trimmed
        })
    }
    private static func snippet(from note: String, token: String) -> String {
        let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)
        guard trimmed.count > 80 else { return trimmed }
        return String(trimmed.prefix(77)) + "..."
    }
    private static func uniqueOrdered(_ values: [String]) -> [String] {
        var seen = Set<String>()
        return values.filter { seen.insert(normalizeField($0)).inserted }
    }
}
// MARK: - Quick Search UI
enum QuickSearchCommand {
    case moveUp
    case moveDown
    case submit
    case dismiss
}
struct QuickSearchOverlayView: View {
    @Binding var query: String
    let results: [QuickSearchResult]
    let selectedID: URL?
    let focusToken: Int
    let isLoading: Bool
    let maxVisibleRows: Int
    let errorMessage: String?
    let onCommand: (QuickSearchCommand) -> Void
    let onHover: (QuickSearchResult) -> Void
    let onLaunch: (QuickSearchResult) -> Void
    private let panelWidth: CGFloat = 760
    private let rowHeight: CGFloat = 74
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            HStack(spacing: 18) {
                Image(systemName: "magnifyingglass")
                    .font(.system(size: 29, weight: .regular))
                    .foregroundStyle(Color.primary.opacity(0.48))
                    .frame(width: 34)
                QuickSearchTextField(
                    text: $query,
                    placeholder: tr("quickSearch.placeholder"),
                    focusToken: focusToken,
                    onCommand: onCommand
                )
                .frame(height: 44)
            }
            .padding(.horizontal, 28)
            .padding(.top, 22)
            .padding(.bottom, 18)
            Divider().opacity(0.35)
            if isLoading {
                HStack(spacing: 10) {
                    ProgressView()
                        .controlSize(.small)
                    Text(tr("quickSearch.loading"))
                        .font(.system(size: 15, weight: .medium))
                        .foregroundStyle(.secondary)
                    Spacer(minLength: 0)
                }
                .padding(.horizontal, 28)
                .padding(.vertical, 24)
                .frame(minHeight: 86)
            } else if let errorMessage {
                QuickSearchMessageRow(
                    systemImage: "exclamationmark.triangle.fill",
                    message: errorMessage,
                    tint: .orange
                )
            } else if results.isEmpty {
                QuickSearchMessageRow(
                    systemImage: query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "keyboard" : "magnifyingglass",
                    message: query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
                        ? tr("quickSearch.emptyPrompt")
                        : tr("quickSearch.noResults"),
                    tint: .secondary
                )
            } else {
                ScrollViewReader { scrollProxy in
                    ScrollView(.vertical, showsIndicators: results.count > maxVisibleRows) {
                        LazyVStack(spacing: 2) {
                            ForEach(results) { result in
                                QuickSearchResultRow(
                                    result: result,
                                    isSelected: result.id == selectedID
                                )
                                .frame(height: rowHeight)
                                .id(result.id)
                                .contentShape(Rectangle())
                                .onHover { hovering in
                                    if hovering { onHover(result) }
                                }
                                .onTapGesture {
                                    onLaunch(result)
                                }
                            }
                        }
                        .padding(.horizontal, 10)
                        .padding(.vertical, 10)
                    }
                    .frame(height: CGFloat(min(results.count, maxVisibleRows)) * (rowHeight + 2) + 20)
                    .onChange(of: selectedID) { _, id in
                        guard let id else { return }
                        withAnimation(.easeOut(duration: 0.08)) {
                            scrollProxy.scrollTo(id, anchor: .center)
                        }
                    }
                }
            }
        }
        .frame(width: panelWidth)
        .background(
            RoundedRectangle(cornerRadius: 34, style: .continuous)
                .fill(.regularMaterial)
                .shadow(color: .black.opacity(0.18), radius: 36, y: 18)
        )
        .overlay(
            RoundedRectangle(cornerRadius: 34, style: .continuous)
                .stroke(Color.primary.opacity(0.10), lineWidth: 1)
        )
        .accessibilityElement(children: .contain)
        .accessibilityLabel(tr("quickSearch.title"))
    }
}
private struct QuickSearchResultRow: View {
    let result: QuickSearchResult
    let isSelected: Bool
    @Environment(\.colorScheme) private var colorScheme
    var body: some View {
        HStack(spacing: 16) {
            Image(nsImage: result.app.icon)
                .resizable()
                .frame(width: 46, height: 46)
                .cornerRadius(10)
            VStack(alignment: .leading, spacing: 4) {
                Text(result.app.name)
                    .font(.system(size: 20, weight: .semibold))
                    .foregroundStyle(.primary)
                    .lineLimit(1)
                    .truncationMode(.tail)
                if let detailText {
                    Text(detailText)
                        .font(.system(size: 16, weight: .medium))
                        .foregroundStyle(Color.primary.opacity(0.38))
                        .lineLimit(1)
                        .truncationMode(.tail)
                }
            }
            .layoutPriority(1)
            Spacer(minLength: 8)
            if let tagName = rightTagName {
                Text(tagName)
                    .font(.system(size: 14, weight: .semibold))
                    .foregroundStyle(Color.primary.opacity(0.46))
                    .lineLimit(1)
                    .truncationMode(.tail)
                    .padding(.horizontal, 12)
                    .frame(height: 32)
                    .frame(maxWidth: 128)
                    .background(
                        Capsule(style: .continuous)
                            .fill(Color.primary.opacity(colorScheme == .dark ? 0.12 : 0.07))
                    )
            }
        }
        .padding(.horizontal, 18)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
        .background(
            RoundedRectangle(cornerRadius: 18, style: .continuous)
                .fill(isSelected ? selectedFill : Color.clear)
        )
        .accessibilityLabel(accessibilityText)
    }
    private var selectedFill: Color {
        colorScheme == .dark ? Color.white.opacity(0.14) : Color.black.opacity(0.075)
    }
    private var detailText: String? {
        if let note = result.noteSnippet, !note.isEmpty {
            return note
        }
        let note = result.document.note.trimmingCharacters(in: .whitespacesAndNewlines)
        if !note.isEmpty {
            return note.count > 72 ? String(note.prefix(69)) + "..." : note
        }
        return nil
    }
    private var rightTagName: String? {
        let tag = result.matchedTagName ?? result.document.tagNames.first
        guard let tag, !tag.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return nil }
        return tag
    }
    private var accessibilityText: String {
        [result.app.name, detailText].compactMap { $0 }.joined(separator: ", ")
    }
}
private struct QuickSearchMessageRow: View {
    let systemImage: String
    let message: String
    let tint: Color
    var body: some View {
        HStack(spacing: 10) {
            Image(systemName: systemImage)
                .font(.system(size: 20, weight: .regular))
                .foregroundStyle(tint)
                .frame(width: 28)
            Text(message)
                .font(.system(size: 15, weight: .medium))
                .foregroundStyle(.secondary)
                .lineLimit(2)
                .fixedSize(horizontal: false, vertical: true)
            Spacer(minLength: 0)
        }
        .padding(.horizontal, 28)
        .padding(.vertical, 24)
        .frame(minHeight: 86)
        .accessibilityLabel(message)
    }
}
private struct QuickSearchTextField: NSViewRepresentable {
    @Binding var text: String
    let placeholder: String
    let focusToken: Int
    let onCommand: (QuickSearchCommand) -> Void
    func makeNSView(context: Context) -> QuickSearchNativeTextField {
        let field = QuickSearchNativeTextField()
        field.isBordered = false
        field.isBezeled = false
        field.drawsBackground = false
        field.focusRingType = .none
        field.font = NSFont.systemFont(ofSize: 28, weight: .regular)
        field.placeholderString = placeholder
        field.delegate = context.coordinator
        field.onCommand = onCommand
        field.setAccessibilityLabel(tr("quickSearch.inputAccessibility"))
        context.coordinator.field = field
        DispatchQueue.main.async {
            field.window?.makeFirstResponder(field)
        }
        return field
    }
    func updateNSView(_ field: QuickSearchNativeTextField, context: Context) {
        if field.stringValue != text {
            field.stringValue = text
        }
        field.placeholderString = placeholder
        field.onCommand = onCommand
        if context.coordinator.lastFocusToken != focusToken {
            context.coordinator.lastFocusToken = focusToken
            DispatchQueue.main.async {
                field.window?.makeFirstResponder(field)
            }
        }
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text)
    }
    final class Coordinator: NSObject, NSTextFieldDelegate {
        var text: Binding<String>
        var lastFocusToken = 0
        weak var field: NSTextField?
        init(text: Binding<String>) {
            self.text = text
        }
        func controlTextDidChange(_ obj: Notification) {
            guard let field = obj.object as? NSTextField else { return }
            text.wrappedValue = field.stringValue
        }
    }
}
private final class QuickSearchNativeTextField: NSTextField {
    var onCommand: ((QuickSearchCommand) -> Void)?
    override func keyDown(with event: NSEvent) {
        switch Int(event.keyCode) {
        case kVK_UpArrow:
            onCommand?(.moveUp)
        case kVK_DownArrow:
            onCommand?(.moveDown)
        case kVK_Return:
            onCommand?(.submit)
        case kVK_Escape:
            onCommand?(.dismiss)
        default:
            super.keyDown(with: event)
        }
    }
}
struct QuickSearchBackdropClickView: NSViewRepresentable {
    let onClick: () -> Void
    func makeNSView(context: Context) -> QuickSearchBackdropNSView {
        let view = QuickSearchBackdropNSView()
        view.onClick = onClick
        return view
    }
    func updateNSView(_ view: QuickSearchBackdropNSView, context: Context) {
        view.onClick = onClick
    }
}
final class QuickSearchBackdropNSView: NSView {
    var onClick: (() -> Void)?
    override func hitTest(_ point: NSPoint) -> NSView? {
        bounds.contains(point) ? self : nil
    }
    override func mouseDown(with event: NSEvent) {
        onClick?()
    }
}
// MARK: - Hotkey Capture UI
struct HotkeyCaptureView: NSViewRepresentable {
    let isActive: Bool
    let onCapture: (LauncherHotkey) -> Void
    let onCancel: () -> Void
    func makeNSView(context: Context) -> HotkeyCaptureNSView {
        let view = HotkeyCaptureNSView()
        view.onCapture = onCapture
        view.onCancel = onCancel
        return view
    }
    func updateNSView(_ view: HotkeyCaptureNSView, context: Context) {
        view.onCapture = onCapture
        view.onCancel = onCancel
        view.setActive(isActive)
    }
}
final class HotkeyCaptureNSView: NSView {
    var onCapture: ((LauncherHotkey) -> Void)?
    var onCancel: (() -> Void)?
    private var keyMonitor: Any?
    override var acceptsFirstResponder: Bool { true }
    deinit {
        removeKeyMonitor()
    }
    func setActive(_ active: Bool) {
        if active {
            installKeyMonitorIfNeeded()
            DispatchQueue.main.async { [weak self] in
                guard let self else { return }
                self.window?.makeFirstResponder(self)
            }
        } else {
            removeKeyMonitor()
        }
    }
    override func keyDown(with event: NSEvent) {
        handle(event)
    }
    private func installKeyMonitorIfNeeded() {
        guard keyMonitor == nil else { return }
        keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
            guard let self else { return event }
            self.handle(event)
            return nil
        }
    }
    private func removeKeyMonitor() {
        if let keyMonitor {
            NSEvent.removeMonitor(keyMonitor)
            self.keyMonitor = nil
        }
    }
    private func handle(_ event: NSEvent) {
        if event.keyCode == UInt16(kVK_Escape) {
            onCancel?()
            return
        }
        if let hotkey = LauncherHotkey.from(event: event) {
            onCapture?(hotkey)
        } else {
            NSSound.beep()
        }
    }
}
CHANGELOG.md
@@ -1,5 +1,24 @@
# Apptag Changelog
## [7.5.1] — 2026-05-19
- 设置页新增独立「快捷键」页签,将主界面快捷键与 Quick Search 直接快捷键管理从「通用」页拆出
- 移除设置页中与当前快捷键设置重复的静态按键帽提示,避免「关于」等页面继续展示旧快捷键视觉元素
- 按 C 版规则收窄「快捷搜索」匹配:短查询不再放大无关结果,备注恢复拼音前缀匹配,Tag 支持原文非连续字符匹配,Bundle ID 与内部 Bundle 名仅支持精确/前缀命中
- 版本号更新为 `7.5.1`,Build 更新为 `751`
## [7.5.0] — 2026-05-19
- 新增 Quick Search:在 App list 中按 `Space` 打开快速搜索浮层,支持按 App 名称、本地化名称、标签、备注和 Bundle ID 本地搜索
- Quick Search 支持精确、前缀、子串、首字母缩写、非连续字符、中文直接匹配和本地中文拼音候选匹配
- 空查询显示最近/常用 App 建议,支持键盘上下选择、回车启动、鼠标悬停选择和点击启动
- 新增直接打开 Quick Search 的可配置全局快捷键,默认关闭,并支持快捷键录制、清除、恢复默认、重试注册和冲突状态提示
- 快捷键冲突状态区分 `Active` / `Conflict` / `Disabled`,已知 macOS 默认快捷键显示专用说明,注册失败时保留旧快捷键可用
- Quick Search 使用 macOS Spotlight 风格玻璃面板,支持外部点击关闭、Esc 两级退出、结果滚动和稳定顶部定位
- 启动历史增加最近成功启动时间,用于 Quick Search 空查询建议排序;启动失败不更新历史
- 所有 Quick Search 用户可见文案接入本地化资源
- 版本号更新为 `7.5.0`,Build 更新为 `750`
## [7.3.5] — 2026-05-18
- 为菜单栏图标设置稳定 `NSStatusItem.autosaveName`,让 macOS 与 Thaw 更容易识别为同一个菜单栏项