From 6dea81ebd24bcdb3c62ff8d2251a2670cb8b64c5 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 07 Jun 2026 01:13:34 +0800
Subject: [PATCH] Freeze TagLauncher 7.8.15 source recovery

---
 Apptag/DataLayer.swift |  255 +++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 238 insertions(+), 17 deletions(-)

diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index d1a09a8..b77bf4c 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -11,9 +11,50 @@
     let tags: [String]
     let bundleIdentifier: String?
     let localizedNames: [String]
+    let localizedNamesByLanguage: [String: String]
     let icon: NSImage  // Pre-loaded during background scan
     var isUncommon: Bool = false
     var note: String? = nil
+
+    var displayName: String {
+        localizedDisplayName(for: L10n.currentCode)
+    }
+
+    func localizedDisplayName(for languageCode: String) -> String {
+        let candidates = AppInfo.displayLanguageFallbacks(for: languageCode)
+        for code in candidates {
+            if let localizedName = localizedNamesByLanguage[code],
+               !localizedName.isEmpty {
+                return localizedName
+            }
+        }
+        return name
+    }
+
+    private static func displayLanguageFallbacks(for languageCode: String) -> [String] {
+        var candidates = [languageCode]
+        switch languageCode {
+        case "zh-Hans":
+            candidates.append("zh-Hant")
+        case "zh-Hant":
+            candidates.append("zh-Hans")
+        case "nb":
+            candidates.append("no")
+        case "nn":
+            candidates.append("no")
+        case "no":
+            candidates.append(contentsOf: ["nb", "nn"])
+        default:
+            break
+        }
+        candidates.append("en")
+        return uniqueLanguageCodes(candidates)
+    }
+
+    fileprivate static func uniqueLanguageCodes(_ codes: [String]) -> [String] {
+        var seen = Set<String>()
+        return codes.filter { seen.insert($0).inserted }
+    }
 
     /// True if this is an Apple pre-installed app.
     var isAppleApp: Bool {
@@ -53,14 +94,22 @@
 // MARK: - App Scanner
 
 enum AppIndexer {
+    private struct SearchPathSignature: Equatable {
+        let path: String
+        let exists: Bool
+        let contentModificationTime: TimeInterval?
+    }
+
     private static let scanCacheLock = NSLock()
     private static var cachedScanApps: [AppInfo]? = nil
     private static var cachedScanAt: Date? = nil
+    private static var cachedSearchPathSignature: [SearchPathSignature]? = nil
     private static let scanCacheTTL: TimeInterval = 1800
 
     static let searchPaths: [URL] = [
         URL(fileURLWithPath: "/Applications"),
         URL(fileURLWithPath: "/System/Applications"),
+        URL(fileURLWithPath: "/System/Library/CoreServices/Applications"),
         URL(fileURLWithPath: "/System/Cryptexes/App/System/Applications"),
         URL(fileURLWithPath: "/System/Volumes/Preboot/Cryptexes/App/System/Applications"),
         FileManager.default.homeDirectoryForCurrentUser
@@ -69,6 +118,7 @@
 
     private static let systemAppPathPrefixes = [
         "/System/Applications/",
+        "/System/Library/CoreServices/Applications/",
         "/System/Cryptexes/App/System/Applications/",
         "/System/Volumes/Preboot/Cryptexes/App/System/Applications/"
     ]
@@ -79,12 +129,13 @@
 
     /// Scan all standard locations. Tags are annotated from TagDatabase by the caller.
     static func scan(useCache: Bool = true) -> [AppInfo] {
-        if useCache, let cached = cachedScanIfFresh() {
+        let searchPathSignature = currentSearchPathSignature()
+        if useCache, let cached = cachedScanIfFresh(matching: searchPathSignature) {
             return cached
         }
 
         let apps = performScan()
-        updateScanCache(apps)
+        updateScanCache(apps, searchPathSignature: searchPathSignature)
         return apps
     }
 
@@ -92,25 +143,59 @@
         scanCacheLock.lock()
         cachedScanApps = nil
         cachedScanAt = nil
+        cachedSearchPathSignature = nil
         scanCacheLock.unlock()
     }
 
-    private static func cachedScanIfFresh() -> [AppInfo]? {
+    static func shouldRefreshForSearchPathChanges() -> Bool {
+        scanCacheLock.lock()
+        let hasCachedApps = cachedScanApps != nil
+        let cachedSignature = cachedSearchPathSignature
+        scanCacheLock.unlock()
+
+        guard hasCachedApps else { return true }
+        return cachedSignature != currentSearchPathSignature()
+    }
+
+    private static func cachedScanIfFresh(matching searchPathSignature: [SearchPathSignature]) -> [AppInfo]? {
         scanCacheLock.lock()
         defer { scanCacheLock.unlock() }
 
         guard let cachedScanApps,
               let cachedScanAt,
+              cachedSearchPathSignature == searchPathSignature,
               Date().timeIntervalSince(cachedScanAt) < scanCacheTTL
         else { return nil }
         return cachedScanApps
     }
 
-    private static func updateScanCache(_ apps: [AppInfo]) {
+    private static func updateScanCache(_ apps: [AppInfo], searchPathSignature: [SearchPathSignature]) {
         scanCacheLock.lock()
         cachedScanApps = apps
         cachedScanAt = Date()
+        cachedSearchPathSignature = searchPathSignature
         scanCacheLock.unlock()
+    }
+
+    private static func currentSearchPathSignature() -> [SearchPathSignature] {
+        searchPaths.map { url in
+            var isDirectory: ObjCBool = false
+            let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)
+            guard exists, isDirectory.boolValue else {
+                return SearchPathSignature(
+                    path: url.path,
+                    exists: false,
+                    contentModificationTime: nil
+                )
+            }
+
+            let values = try? url.resourceValues(forKeys: [.contentModificationDateKey])
+            return SearchPathSignature(
+                path: url.path,
+                exists: true,
+                contentModificationTime: values?.contentModificationDate?.timeIntervalSinceReferenceDate
+            )
+        }
     }
 
     private static func performScan() -> [AppInfo] {
@@ -174,16 +259,21 @@
         guard !isNestedInsideAppBundle(resolvedURL) else { return }
         guard seenResolvedPaths.insert(resolvedURL.path).inserted else { return }
 
+        let bundle = Bundle(url: displayURL) ?? Bundle(url: resolvedURL)
+        let bundleId = bundle?.bundleIdentifier
+        guard !isTagLauncherBundle(bundleId) else { return }
+
         let name = displayURL.deletingPathExtension().lastPathComponent
         let icon = NSWorkspace.shared.icon(forFile: displayURL.path)
         icon.size = NSSize(width: 96, height: 96)
-
-        let bundle = Bundle(url: displayURL) ?? Bundle(url: resolvedURL)
-        let bundleId = bundle?.bundleIdentifier
-        let localizedNames = localizedAppNames(
-            for: displayURL,
+        let localizedNamesByLanguage = localizedAppNameMap(
             bundle: bundle,
             fallbackName: name
+        )
+        let localizedNames = localizedAppNames(
+            for: displayURL,
+            fallbackName: name,
+            localizedNamesByLanguage: localizedNamesByLanguage
         )
 
         apps.append(AppInfo(
@@ -192,24 +282,154 @@
             tags: [],
             bundleIdentifier: bundleId,
             localizedNames: localizedNames,
+            localizedNamesByLanguage: localizedNamesByLanguage,
             icon: icon
         ))
     }
 
-    private static func localizedAppNames(
-        for appURL: URL,
+    private static func isTagLauncherBundle(_ bundleIdentifier: String?) -> Bool {
+        bundleIdentifier?.caseInsensitiveCompare(AppIdentity.bundleIdentifier) == .orderedSame
+    }
+
+    private static func localizedAppNameMap(
         bundle: Bundle?,
         fallbackName: String
+    ) -> [String: String] {
+        guard let bundle else { return [:] }
+        let loctable = infoPlistLoctable(in: bundle)
+        var result: [String: String] = [:]
+
+        for language in L10n.supported {
+            guard let localizedName = localizedAppName(
+                for: language.code,
+                bundle: bundle,
+                loctable: loctable,
+                fallbackName: fallbackName
+            ) else { continue }
+            result[language.code] = localizedName
+        }
+
+        return result
+    }
+
+    private static func localizedAppNames(
+        for appURL: URL,
+        fallbackName: String,
+        localizedNamesByLanguage: [String: String]
     ) -> [String] {
-        var values: [String?] = []
-        values.append(bundle?.localizedInfoDictionary?["CFBundleDisplayName"] as? String)
-        values.append(bundle?.localizedInfoDictionary?["CFBundleName"] as? String)
-        values.append(bundle?.infoDictionary?["CFBundleDisplayName"] as? String)
-        values.append(bundle?.infoDictionary?["CFBundleName"] as? String)
+        var values = L10n.supported.map { localizedNamesByLanguage[$0.code] }
         values.append(spotlightDisplayName(for: appURL))
         values.append(FileManager.default.displayName(atPath: appURL.path).replacingOccurrences(of: ".app", with: ""))
 
         return uniqueLocalizedNames(values, excluding: fallbackName)
+    }
+
+    private static func localizedAppName(
+        for languageCode: String,
+        bundle: Bundle,
+        loctable: [String: [String: Any]],
+        fallbackName: String
+    ) -> String? {
+        for localization in localizationCandidates(for: languageCode) {
+            if let table = loctable[localization],
+               let value = firstValidLocalizedName(
+                   [table["CFBundleDisplayName"], table["CFBundleName"]],
+                   excluding: fallbackName
+               ) {
+                return value
+            }
+
+            if let strings = infoPlistStrings(in: bundle, localization: localization),
+               let value = firstValidLocalizedName(
+                   [strings["CFBundleDisplayName"], strings["CFBundleName"]],
+                   excluding: fallbackName
+               ) {
+                return value
+            }
+        }
+
+        if languageCode == "en",
+           let value = firstValidLocalizedName(
+               [
+                   bundle.infoDictionary?["CFBundleDisplayName"],
+                   bundle.infoDictionary?["CFBundleName"]
+               ],
+               excluding: fallbackName
+           ) {
+            return value
+        }
+
+        return nil
+    }
+
+    private static func infoPlistLoctable(in bundle: Bundle) -> [String: [String: Any]] {
+        guard let url = bundle.url(forResource: "InfoPlist", withExtension: "loctable"),
+              let rawTable = NSDictionary(contentsOf: url) as? [String: Any]
+        else { return [:] }
+
+        var result: [String: [String: Any]] = [:]
+        for (key, value) in rawTable {
+            if let localizedTable = value as? [String: Any] {
+                result[key] = localizedTable
+            }
+        }
+        return result
+    }
+
+    private static func infoPlistStrings(
+        in bundle: Bundle,
+        localization: String
+    ) -> [String: Any]? {
+        guard let url = bundle.url(
+            forResource: "InfoPlist",
+            withExtension: "strings",
+            subdirectory: nil,
+            localization: localization
+        ) else { return nil }
+        return NSDictionary(contentsOf: url) as? [String: Any]
+    }
+
+    private static func localizationCandidates(for languageCode: String) -> [String] {
+        var candidates = [languageCode, languageCode.replacingOccurrences(of: "-", with: "_")]
+        switch languageCode {
+        case "zh-Hans":
+            candidates.append(contentsOf: ["zh_CN", "zh"])
+        case "zh-Hant":
+            candidates.append(contentsOf: ["zh_TW", "zh_HK", "zh"])
+        case "pt-BR":
+            candidates.append(contentsOf: ["pt_BR", "pt"])
+        case "sr-Cyrl":
+            candidates.append(contentsOf: ["sr_Cyrl", "sr"])
+        case "ar-Najdi":
+            candidates.append(contentsOf: ["ar_Najdi", "ar"])
+        case "nb":
+            candidates.append(contentsOf: ["nb", "no"])
+        case "nn":
+            candidates.append(contentsOf: ["nn", "no"])
+        case "no":
+            candidates.append(contentsOf: ["no", "nb", "nn"])
+        default:
+            if let base = languageCode.split(separator: "-").first.map(String.init) {
+                candidates.append(base)
+            }
+        }
+        return AppInfo.uniqueLanguageCodes(candidates)
+    }
+
+    private static func firstValidLocalizedName(
+        _ values: [Any?],
+        excluding excludedValue: String
+    ) -> String? {
+        let normalizedExcluded = normalizedLocalizedName(excludedValue)
+        for value in values {
+            guard let value = value as? String else { continue }
+            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
+            guard !trimmed.isEmpty,
+                  normalizedLocalizedName(trimmed) != normalizedExcluded
+            else { continue }
+            return trimmed
+        }
+        return nil
     }
 
     private static func spotlightDisplayName(for appURL: URL) -> String? {
@@ -272,7 +492,7 @@
     private static func deduplicationIdentity(for app: AppInfo) -> String {
         if let bundleIdentifier = app.bundleIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines),
            !bundleIdentifier.isEmpty {
-            return "bundle:\(bundleIdentifier.lowercased())"
+            return "bundle:\(bundleIdentifier.lowercased())|name:\(normalizedAppName(app.name))"
         }
         return "name:\(normalizedAppName(app.name))"
     }
@@ -1081,6 +1301,7 @@
                 name: app.name, path: app.path, tags: appTags,
                 bundleIdentifier: app.bundleIdentifier,
                 localizedNames: app.localizedNames,
+                localizedNamesByLanguage: app.localizedNamesByLanguage,
                 icon: app.icon,
                 isUncommon: uncommonPaths.contains(app.path.path),
                 note: store.appNotes[app.path.path]

--
Gitblit v1.9.3