Ariver
2026-05-24 d13e673115da89982bc29d1868b022afc062b801
Add automatic language mode and localized help menu
33 files modified
302 ■■■■ changed files
Apptag/ApptagApp.swift 44 ●●●●● patch | view | raw | blame | history
Apptag/L10n.swift 67 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ar-Najdi.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/ar.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/cs.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/da.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/de.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/en.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/es.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/fr.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/id.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/it.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/ja.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/ko.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/ms.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/nb.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/nl.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/nn.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/no.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/pl.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/pt-BR.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/ro.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/ru.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/sr-Cyrl.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/sv.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/th.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/tr.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/uk.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/vi.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/zh-Hans.json 4 ●●● patch | view | raw | blame | history
Apptag/Localization/zh-Hant.json 4 ●●● patch | view | raw | blame | history
Apptag/PreferencesView.swift 44 ●●●●● patch | view | raw | blame | history
Scripts/window_logic_qa.sh 31 ●●●● patch | view | raw | blame | history
Apptag/ApptagApp.swift
@@ -43,6 +43,8 @@
    private static let statusItemButtonIdentifier = NSUserInterfaceItemIdentifier("TagLauncherStatusItemButton")
    private static let statusItemAccessibilityLabel = AppIdentity.displayName
    private static let showAppListMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherShowAppListMenuItem")
    private static let helpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherHelpMenu")
    private static let downloadHelpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherDownloadHelpMenuItem")
    private static let externalActivationNotification = Notification.Name("TagLauncherExternalActivationRequested")
    private static let externalActivationObject = AppIdentity.bundleIdentifier
    private static let launcherOverlayLevel = NSWindow.Level(rawValue: NSWindow.Level.mainMenu.rawValue - 1)
@@ -590,7 +592,7 @@
            }
            self.configureApplicationMenu()
            self.removeHelpMenu()
            self.configureHelpMenu()
            if retries > 0 && self.applicationMenuNeedsCleanup() {
                self.configureApplicationMenuWhenAvailable(retries: retries - 1)
            }
@@ -709,12 +711,12 @@
        }
    }
    private func removeHelpMenu() {
    private func configureHelpMenu() {
        guard let mainMenu = NSApp.mainMenu else { return }
        NSApp.helpMenu = nil
        for item in mainMenu.items.reversed() {
            let isHelpMenu = item.submenu === NSApp.helpMenu
            let isHelpMenu = item.identifier == Self.helpMenuItemIdentifier
                || item.title.localizedCaseInsensitiveContains("help")
                || item.title == tr("menu.help")
                || item.submenu?.title.localizedCaseInsensitiveContains("help") == true
@@ -723,6 +725,26 @@
                mainMenu.removeItem(item)
            }
        }
        let menuItem = NSMenuItem(title: tr("menu.help"), action: nil, keyEquivalent: "")
        let helpMenu = NSMenu(title: tr("menu.help"))
        menuItem.identifier = Self.helpMenuItemIdentifier
        menuItem.submenu = helpMenu
        let downloadItem = NSMenuItem()
        downloadItem.identifier = Self.downloadHelpMenuItemIdentifier
        downloadItem.title = tr("help.downloadPDF")
        downloadItem.action = #selector(openLocalizedHelp(_:))
        downloadItem.target = self
        downloadItem.keyEquivalent = ""
        downloadItem.keyEquivalentModifierMask = []
        downloadItem.isEnabled = true
        helpMenu.addItem(downloadItem)
        mainMenu.addItem(menuItem)
    }
    @objc private func openLocalizedHelp(_ sender: Any? = nil) {
        NSWorkspace.shared.open(HelpDocument.currentURL)
    }
    private func removeMenuBarItem() {
@@ -1163,8 +1185,16 @@
                return
            }
            if self.isMenuTrackingWindow(keyWindow) {
                return
            }
            if self.isAppOwnedDocumentWindow(keyWindow) {
                self.prepareSettingsWindow(keyWindow)
                return
            }
            if NSApp.windows.contains(keyWindow) {
                return
            }
@@ -1239,6 +1269,14 @@
            && window != overlayWindow
            && window.isVisible
            && !(window is NSPanel)
            && !isMenuTrackingWindow(window)
            && window.styleMask.contains(.titled)
    }
    private func isMenuTrackingWindow(_ window: NSWindow) -> Bool {
        let className = NSStringFromClass(type(of: window))
        return className.localizedCaseInsensitiveContains("Menu")
            || className.localizedCaseInsensitiveContains("Popup")
    }
    private func settingsWindowTitleCandidates() -> Set<String> {
Apptag/L10n.swift
@@ -7,6 +7,7 @@
}
enum L10n {
    static let automaticCode = "auto"
    static var current: [String: String] = [:]
    private(set) static var currentCode = "en"
@@ -48,13 +49,52 @@
        ("sv", "Svenska"),
    ]
    static var selectedLanguageCode: String {
        UserDefaults.standard.string(forKey: "appLanguage") ?? automaticCode
    }
    static var isAutomatic: Bool {
        UserDefaults.standard.string(forKey: "appLanguage") == nil
    }
    static func switchSelection(to code: String) {
        if code == automaticCode {
            switchToAutomatic()
        } else {
            switchTo(code)
        }
    }
    static func switchToAutomatic() {
        let code = fallbackCode()
        let previousCode = currentCode
        load(code)
        UserDefaults.standard.removeObject(forKey: "appLanguage")
        let relocalizedTags = previousCode != currentCode
            ? TagDatabase.relocalizeSystemTagsForCurrentLanguage()
            : false
        NotificationCenter.default.post(
            name: .appLanguageDidChange,
            object: nil,
            userInfo: ["code": automaticCode, "effectiveCode": currentCode]
        )
        if relocalizedTags {
            NotificationCenter.default.post(name: .tagLauncherDataDidChange, object: nil)
        }
    }
    static func switchTo(_ code: String) {
        guard supported.contains(where: { $0.code == code }) else { return }
        guard code != currentCode else { return }
        let previousSelection = selectedLanguageCode
        guard code != currentCode || previousSelection == automaticCode else { return }
        load(code)
        let relocalizedTags = TagDatabase.relocalizeSystemTagsForCurrentLanguage()
        UserDefaults.standard.set(code, forKey: "appLanguage")
        NotificationCenter.default.post(name: .appLanguageDidChange, object: nil, userInfo: ["code": currentCode])
        NotificationCenter.default.post(
            name: .appLanguageDidChange,
            object: nil,
            userInfo: ["code": currentCode, "effectiveCode": currentCode]
        )
        if relocalizedTags {
            NotificationCenter.default.post(name: .tagLauncherDataDidChange, object: nil)
        }
@@ -87,7 +127,7 @@
        currentCode = code
    }
    private static func fallbackCode() -> String {
    static func fallbackCode() -> String {
        let lang = Locale.preferredLanguages.first ?? "en"
        if lang.hasPrefix("zh-Hant") || lang.hasPrefix("zh-HK") || lang.hasPrefix("zh-TW") { return "zh-Hant" }
        if lang.hasPrefix("zh") { return "zh-Hans" }
@@ -124,3 +164,24 @@
func tr(_ key: String) -> String {
    L10n.current[key] ?? key
}
enum HelpDocument {
    private static let baseURL = "https://github.com/shanghai3168/taglauncher/releases/download/v7.6.0"
    static var currentURL: URL {
        URL(string: "\(baseURL)/Taglauncher-help-\(languageCode(for: L10n.currentCode)).pdf")!
    }
    private static func languageCode(for appLanguageCode: String) -> String {
        switch appLanguageCode {
        case "zh-Hans": return "zh"
        case "zh-Hant": return "zh-Hant"
        case "en", "ar", "ar-Najdi", "cs", "da", "de", "es", "fr", "id",
             "it", "ja", "ko", "ms", "nb", "nl", "nn", "no", "pl", "pt-BR",
             "ro", "ru", "sr-Cyrl", "sv", "th", "tr", "uk", "vi":
            return appLanguageCode
        default:
            return "en"
        }
    }
}
Apptag/Localization/ar-Najdi.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "تلقائي",
  "help.downloadPDF": "تنزيل ملف المساعدة PDF"
}
Apptag/Localization/ar.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "تلقائي",
  "help.downloadPDF": "تنزيل ملف المساعدة PDF"
}
Apptag/Localization/cs.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automaticky",
  "help.downloadPDF": "Stáhnout PDF nápovědy"
}
Apptag/Localization/da.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatisk",
  "help.downloadPDF": "Download hjælp-PDF"
}
Apptag/Localization/de.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Hilfe-PDF öffnen",
  "help.copyLink": "Link zum Hilfe-PDF kopieren",
  "help.currentLanguage": "Öffnet das Online-PDF für die aktuelle App-Sprache."
  "help.currentLanguage": "Öffnet das Online-PDF für die aktuelle App-Sprache.",
  "settings.language.auto": "Automatisch",
  "help.downloadPDF": "Hilfe-PDF herunterladen"
}
Apptag/Localization/en.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatic",
  "help.downloadPDF": "Download Help PDF"
}
Apptag/Localization/es.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Abrir PDF de ayuda",
  "help.copyLink": "Copiar enlace del PDF de ayuda",
  "help.currentLanguage": "Abre el PDF en línea para el idioma actual de la app."
  "help.currentLanguage": "Abre el PDF en línea para el idioma actual de la app.",
  "settings.language.auto": "Automático",
  "help.downloadPDF": "Descargar PDF de ayuda"
}
Apptag/Localization/fr.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Ouvrir le PDF d’aide",
  "help.copyLink": "Copier le lien du PDF d’aide",
  "help.currentLanguage": "Ouvre le PDF en ligne correspondant à la langue actuelle de l’app."
  "help.currentLanguage": "Ouvre le PDF en ligne correspondant à la langue actuelle de l’app.",
  "settings.language.auto": "Automatique",
  "help.downloadPDF": "Télécharger le PDF d’aide"
}
Apptag/Localization/id.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Otomatis",
  "help.downloadPDF": "Unduh PDF bantuan"
}
Apptag/Localization/it.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Apri PDF di aiuto",
  "help.copyLink": "Copia link del PDF di aiuto",
  "help.currentLanguage": "Apre il PDF online per la lingua attuale dell’app."
  "help.currentLanguage": "Apre il PDF online per la lingua attuale dell’app.",
  "settings.language.auto": "Automatico",
  "help.downloadPDF": "Scarica PDF di aiuto"
}
Apptag/Localization/ja.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "ヘルプPDFを開く",
  "help.copyLink": "ヘルプPDFのリンクをコピー",
  "help.currentLanguage": "現在のアプリ言語に対応するオンラインPDFを開きます。"
  "help.currentLanguage": "現在のアプリ言語に対応するオンラインPDFを開きます。",
  "settings.language.auto": "自動",
  "help.downloadPDF": "ヘルプPDFをダウンロード"
}
Apptag/Localization/ko.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "도움말 PDF 열기",
  "help.copyLink": "도움말 PDF 링크 복사",
  "help.currentLanguage": "현재 앱 언어에 맞는 온라인 PDF를 엽니다."
  "help.currentLanguage": "현재 앱 언어에 맞는 온라인 PDF를 엽니다.",
  "settings.language.auto": "자동",
  "help.downloadPDF": "도움말 PDF 다운로드"
}
Apptag/Localization/ms.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatik",
  "help.downloadPDF": "Muat turun PDF bantuan"
}
Apptag/Localization/nb.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatisk",
  "help.downloadPDF": "Last ned hjelp-PDF"
}
Apptag/Localization/nl.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatisch",
  "help.downloadPDF": "Help-PDF downloaden"
}
Apptag/Localization/nn.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatisk",
  "help.downloadPDF": "Last ned hjelp-PDF"
}
Apptag/Localization/no.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatisk",
  "help.downloadPDF": "Last ned hjelp-PDF"
}
Apptag/Localization/pl.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatycznie",
  "help.downloadPDF": "Pobierz PDF pomocy"
}
Apptag/Localization/pt-BR.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automático",
  "help.downloadPDF": "Baixar PDF de ajuda"
}
Apptag/Localization/ro.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automat",
  "help.downloadPDF": "Descarcă PDF-ul de ajutor"
}
Apptag/Localization/ru.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Открыть PDF справки",
  "help.copyLink": "Скопировать ссылку на PDF справки",
  "help.currentLanguage": "Открывает онлайн-PDF для текущего языка приложения."
  "help.currentLanguage": "Открывает онлайн-PDF для текущего языка приложения.",
  "settings.language.auto": "Автоматически",
  "help.downloadPDF": "Скачать PDF справки"
}
Apptag/Localization/sr-Cyrl.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Аутоматски",
  "help.downloadPDF": "Преузми PDF помоћи"
}
Apptag/Localization/sv.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Automatiskt",
  "help.downloadPDF": "Ladda ner hjälp-PDF"
}
Apptag/Localization/th.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "อัตโนมัติ",
  "help.downloadPDF": "ดาวน์โหลด PDF วิธีใช้"
}
Apptag/Localization/tr.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Otomatik",
  "help.downloadPDF": "Yardım PDF’sini indir"
}
Apptag/Localization/uk.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Автоматично",
  "help.downloadPDF": "Завантажити PDF довідки"
}
Apptag/Localization/vi.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "Network Tools",
  "help.openPDF": "Open Help PDF",
  "help.copyLink": "Copy Help PDF link",
  "help.currentLanguage": "Opens the online PDF for the current app language."
  "help.currentLanguage": "Opens the online PDF for the current app language.",
  "settings.language.auto": "Tự động",
  "help.downloadPDF": "Tải PDF trợ giúp"
}
Apptag/Localization/zh-Hans.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "网络工具",
  "help.openPDF": "打开帮助文档",
  "help.copyLink": "复制帮助文档链接",
  "help.currentLanguage": "将按当前应用语言打开对应版本的在线 PDF。"
  "help.currentLanguage": "将按当前应用语言打开对应版本的在线 PDF。",
  "settings.language.auto": "自动",
  "help.downloadPDF": "下载帮助文档"
}
Apptag/Localization/zh-Hant.json
@@ -191,5 +191,7 @@
  "smart.category.network-tools": "網路工具",
  "help.openPDF": "打開說明文件",
  "help.copyLink": "複製說明文件連結",
  "help.currentLanguage": "將依目前應用程式語言開啟對應版本的線上 PDF。"
  "help.currentLanguage": "將依目前應用程式語言開啟對應版本的線上 PDF。",
  "settings.language.auto": "自動",
  "help.downloadPDF": "下載說明文件"
}
Apptag/PreferencesView.swift
@@ -25,7 +25,7 @@
    @AppStorage("showUncommonAppBubbles") private var showUncommonAppBubbles = AppDefaults.showUncommonAppBubbles
    @AppStorage("mainHotkeyRegistrationState") private var mainHotkeyRegistrationState = LauncherHotkeyRegistrationState.active.rawValue
    @AppStorage("quickSearchHotkeyRegistrationState") private var quickSearchHotkeyRegistrationState = LauncherHotkeyRegistrationState.active.rawValue
    @State private var selectedLanguage = L10n.currentCode
    @State private var selectedLanguage = L10n.selectedLanguageCode
    @State private var isRefreshingLanguage = false
    @State private var allApps: [AppInfo] = []
    @State private var tagColors: [String: Int] = [:]
@@ -360,7 +360,7 @@
    }
    private func syncSelectedLanguage() {
        let code = L10n.currentCode
        let code = L10n.selectedLanguageCode
        guard selectedLanguage != code else { return }
        selectedLanguage = code
    }
@@ -371,20 +371,8 @@
    private var buildVersion: String {
        Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "?"
    }
    private var helpPDFLanguageCode: String {
        switch L10n.currentCode {
        case "zh-Hans": return "zh"
        case "zh-Hant": return "zh-Hant"
        case "en", "ar", "ar-Najdi", "cs", "da", "de", "es", "fr", "id",
             "it", "ja", "ko", "ms", "nb", "nl", "nn", "no", "pl", "pt-BR",
             "ro", "ru", "sr-Cyrl", "sv", "th", "tr", "uk", "vi":
            return L10n.currentCode
        default:
            return "en"
        }
    }
    private var helpPDFURL: URL {
        URL(string: "https://github.com/shanghai3168/taglauncher/releases/download/v7.6.0/Taglauncher-help-\(helpPDFLanguageCode).pdf")!
        HelpDocument.currentURL
    }
    private var languageColumns: [GridItem] {
        [
@@ -521,6 +509,30 @@
                    ScrollView {
                        LazyVGrid(columns: languageColumns, alignment: .leading, spacing: 6) {
                            Button {
                                selectedLanguage = L10n.automaticCode
                            } label: {
                                HStack(spacing: 8) {
                                    Image(systemName: selectedLanguage == L10n.automaticCode ? "checkmark" : "circle")
                                        .font(.system(size: 12, weight: .semibold))
                                        .foregroundStyle(selectedLanguage == L10n.automaticCode ? Color.accentColor : Color.secondary.opacity(0.28))
                                        .frame(width: 16)
                                    Text(tr("settings.language.auto"))
                                        .foregroundStyle(.primary)
                                        .lineLimit(1)
                                    Text("(\(L10n.supported.first(where: { $0.code == L10n.currentCode })?.name ?? "English"))")
                                        .font(.caption)
                                        .foregroundStyle(.secondary)
                                        .lineLimit(1)
                                    Spacer(minLength: 0)
                                }
                                .padding(.horizontal, 8)
                                .padding(.vertical, 5)
                                .contentShape(Rectangle())
                            }
                            .buttonStyle(.plain)
                            .accessibilityLabel("\(tr("settings.languagePicker")) \(tr("settings.language.auto"))")
                            ForEach(L10n.supported, id: \.code) { language in
                                Button {
                                    selectedLanguage = language.code
@@ -873,7 +885,7 @@
            .padding()
            }
            .onChange(of: selectedLanguage) { _, code in
                L10n.switchTo(code)
                L10n.switchSelection(to: code)
            }
            .onReceive(NotificationCenter.default.publisher(for: .appLanguageDidChange)) { notification in
                if let code = notification.userInfo?["code"] as? String {
Scripts/window_logic_qa.sh
@@ -279,6 +279,7 @@
cat >"$assert_swift" <<'SWIFT'
import AppKit
import AppKit
import CoreGraphics
import Foundation
@@ -442,12 +443,24 @@
SWIFT
cat >"$coords_swift" <<'SWIFT'
import AppKit
import CoreGraphics
import Foundation
let raw = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] ?? []
let tag = raw.filter { ($0[kCGWindowOwnerName as String] as? String) == "TagLauncher" }
let mode = CommandLine.arguments.dropFirst().first ?? ""
func dimension(_ bounds: NSDictionary, _ key: String) -> CGFloat {
    if let value = bounds[key] as? CGFloat {
        return value
    }
    if let value = bounds[key] as? NSNumber {
        return CGFloat(truncating: value)
    }
    return 0
}
switch mode {
case "data-tab":
    guard let settings = tag.first(where: { (($0[kCGWindowName as String] as? String) ?? "").isEmpty == false }),
@@ -468,13 +481,19 @@
    }
    print("\(Int(round(x + 375))) \(Int(round(y + 331)))")
case "overlay-outside":
    guard let overlay = tag.first(where: { (($0[kCGWindowName as String] as? String) ?? "").isEmpty }),
          let overlayBounds = overlay[kCGWindowBounds as String] as? NSDictionary,
          let overlayX = overlayBounds["X"] as? CGFloat,
          let overlayY = overlayBounds["Y"] as? CGFloat else {
        fputs("FAIL: could not find overlay window bounds\n", stderr)
        exit(1)
    guard let overlay = tag.max(by: { lhs, rhs in
        let lhsBounds = lhs[kCGWindowBounds as String] as? NSDictionary ?? [:]
        let rhsBounds = rhs[kCGWindowBounds as String] as? NSDictionary ?? [:]
        let lhsArea = dimension(lhsBounds, "Width") * dimension(lhsBounds, "Height")
        let rhsArea = dimension(rhsBounds, "Width") * dimension(rhsBounds, "Height")
        return lhsArea < rhsArea
    }), let overlayBounds = overlay[kCGWindowBounds as String] as? NSDictionary else {
        let screen = NSScreen.screens.first?.frame ?? CGRect(x: 0, y: 0, width: 1200, height: 800)
        print("\(Int(round(screen.minX + 120))) \(160)")
        exit(0)
    }
    let overlayX = dimension(overlayBounds, "X")
    let overlayY = dimension(overlayBounds, "Y")
    print("\(Int(round(overlayX + 120))) \(Int(round(overlayY + 160)))")
default:
    fputs("FAIL: unknown coords mode \(mode)\n", stderr)