From fae673d17c0d7c4f9acb076218c7de291bf507ea Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 09 Jun 2026 18:26:01 +0800
Subject: [PATCH] Release 7.8.25 multilingual app names

---
 src/CHANGELOG.md                        |    8 
 src/TODO.md                             |    6 
 src/Apptag/Info.plist                   |    4 
 src/Apptag/DataLayer.swift              |  217 +++++++++++++++++++----
 src/Scripts/quick_search_app_name_qa.sh |  230 +++++++++++++++++++++++--
 src/Apptag/QuickSearch.swift            |   65 +++----
 6 files changed, 432 insertions(+), 98 deletions(-)

diff --git a/src/Apptag/DataLayer.swift b/src/Apptag/DataLayer.swift
index 17a0605..d6cd436 100644
--- a/src/Apptag/DataLayer.swift
+++ b/src/Apptag/DataLayer.swift
@@ -12,46 +12,18 @@
     let bundleIdentifier: String?
     let localizedNames: [String]
     let localizedNamesByLanguage: [String: String]
+    let systemDisplayNames: [String]
+    let bundleDisplayNames: [String]
     let icon: NSImage  // Pre-loaded during background scan
     var isUncommon: Bool = false
     var note: String? = nil
 
     var displayName: String {
-        localizedDisplayName(for: L10n.currentCode)
+        AppDisplayNameResolver.displayName(for: self, languageCode: 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
-            }
-        }
-        if let localizedName = localizedNames.first, !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)
+        AppDisplayNameResolver.displayName(for: self, languageCode: languageCode)
     }
 
     fileprivate static func uniqueLanguageCodes(_ codes: [String]) -> [String] {
@@ -67,6 +39,141 @@
 
     func hash(into hasher: inout Hasher) { hasher.combine(path) }
     static func == (lhs: AppInfo, rhs: AppInfo) -> Bool { lhs.path == rhs.path }
+}
+
+enum AppDisplayNameResolver {
+    static func displayName(for app: AppInfo, languageCode: String) -> String {
+        let languageFallbacks = displayLanguageFallbacks(for: languageCode, includeEnglish: false)
+
+        if let localizedName = firstLocalizedName(
+            for: languageFallbacks,
+            in: app.localizedNamesByLanguage
+        ) {
+            return localizedName
+        }
+
+        if languageCode == "en",
+           let englishBaseName = firstDisplayCandidate([app.name] + app.bundleDisplayNames) {
+            return englishBaseName
+        }
+
+        if let systemDisplayName = firstDisplayCandidate(app.systemDisplayNames) {
+            return systemDisplayName
+        }
+
+        if let englishName = firstLocalizedName(for: ["en"], in: app.localizedNamesByLanguage) {
+            return englishName
+        }
+
+        if let bundleDisplayName = firstDisplayCandidate(app.bundleDisplayNames) {
+            return bundleDisplayName
+        }
+
+        let skippedLanguageCodes = Set(languageFallbacks + ["en"])
+        let remainingLocalizedNames = L10n.supported.compactMap { language -> String? in
+            guard !skippedLanguageCodes.contains(language.code) else { return nil }
+            return app.localizedNamesByLanguage[language.code]
+        }
+        if let localizedName = firstDisplayCandidate(remainingLocalizedNames) {
+            return localizedName
+        }
+
+        if let alias = firstDisplayCandidate(app.localizedNames) {
+            return alias
+        }
+
+        return app.name
+    }
+
+    static func searchAliases(for app: AppInfo) -> [String] {
+        uniqueDisplayNames(
+            L10n.supported.compactMap { app.localizedNamesByLanguage[$0.code] }
+                + app.systemDisplayNames
+                + app.bundleDisplayNames
+                + app.localizedNames
+                + [app.displayName],
+            excluding: app.name
+        )
+    }
+
+    private static func displayLanguageFallbacks(
+        for languageCode: String,
+        includeEnglish: Bool
+    ) -> [String] {
+        var candidates = [
+            languageCode,
+            languageCode.replacingOccurrences(of: "_", with: "-")
+        ]
+
+        switch languageCode {
+        case "zh-Hans":
+            candidates.append("zh-Hant")
+        case "zh-Hant":
+            candidates.append("zh-Hans")
+        case "pt-BR":
+            candidates.append("pt")
+        case "sr-Cyrl":
+            candidates.append("sr")
+        case "ar-Najdi":
+            candidates.append("ar")
+        case "nb":
+            candidates.append(contentsOf: ["no", "nn"])
+        case "nn":
+            candidates.append(contentsOf: ["no", "nb"])
+        case "no":
+            candidates.append(contentsOf: ["nb", "nn"])
+        default:
+            if let base = languageCode.split(separator: "-").first.map(String.init) {
+                candidates.append(base)
+            }
+        }
+
+        if includeEnglish {
+            candidates.append("en")
+        }
+        return AppInfo.uniqueLanguageCodes(candidates)
+    }
+
+    private static func firstLocalizedName(
+        for languageCodes: [String],
+        in localizedNamesByLanguage: [String: String]
+    ) -> String? {
+        firstDisplayCandidate(languageCodes.compactMap { localizedNamesByLanguage[$0] })
+    }
+
+    private static func firstDisplayCandidate(_ values: [String]) -> String? {
+        values.lazy.compactMap(normalizedDisplayName).first
+    }
+
+    private static func uniqueDisplayNames(_ values: [String], excluding excludedValue: String) -> [String] {
+        let normalizedExcluded = normalizedKey(excludedValue)
+        var seen = Set<String>()
+        var result: [String] = []
+
+        for value in values {
+            guard let trimmed = normalizedDisplayName(value) else { continue }
+            let normalized = normalizedKey(trimmed)
+            guard normalized != normalizedExcluded,
+                  seen.insert(normalized).inserted
+            else { continue }
+            result.append(trimmed)
+        }
+        return result
+    }
+
+    private static func normalizedDisplayName(_ value: String) -> String? {
+        let trimmed = value
+            .replacingOccurrences(of: ".app", with: "")
+            .trimmingCharacters(in: .whitespacesAndNewlines)
+        return trimmed.isEmpty ? nil : trimmed
+    }
+
+    private static func normalizedKey(_ value: String) -> String {
+        value
+            .folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
+            .lowercased()
+            .trimmingCharacters(in: .whitespacesAndNewlines)
+    }
 }
 
 struct TagGroup: Identifiable {
@@ -282,10 +389,13 @@
                 }
             }
         }
+        let systemDisplayNames = systemDisplayNames(for: displayURL, fallbackName: name)
+        let bundleDisplayNames = bundleDisplayNames(bundle: bundle, fallbackName: name)
         let localizedNames = localizedAppNames(
-            for: displayURL,
             fallbackName: name,
-            localizedNamesByLanguage: localizedNamesByLanguage
+            localizedNamesByLanguage: localizedNamesByLanguage,
+            systemDisplayNames: systemDisplayNames,
+            bundleDisplayNames: bundleDisplayNames
         )
 
         apps.append(AppInfo(
@@ -295,6 +405,8 @@
             bundleIdentifier: bundleId,
             localizedNames: localizedNames,
             localizedNamesByLanguage: localizedNamesByLanguage,
+            systemDisplayNames: systemDisplayNames,
+            bundleDisplayNames: bundleDisplayNames,
             icon: icon
         ))
     }
@@ -325,16 +437,39 @@
     }
 
     private static func localizedAppNames(
-        for appURL: URL,
         fallbackName: String,
-        localizedNamesByLanguage: [String: String]
+        localizedNamesByLanguage: [String: String],
+        systemDisplayNames: [String],
+        bundleDisplayNames: [String]
     ) -> [String] {
         var values = L10n.supported.map { localizedNamesByLanguage[$0.code] }
-        values.append(resourceLocalizedName(for: appURL))
-        values.append(spotlightDisplayName(for: appURL))
-        values.append(FileManager.default.displayName(atPath: appURL.path).replacingOccurrences(of: ".app", with: ""))
+        values.append(contentsOf: systemDisplayNames.map { Optional($0) })
+        values.append(contentsOf: bundleDisplayNames.map { Optional($0) })
 
         return uniqueLocalizedNames(values, excluding: fallbackName)
+    }
+
+    private static func systemDisplayNames(for appURL: URL, fallbackName: String) -> [String] {
+        uniqueLocalizedNames(
+            [
+                resourceLocalizedName(for: appURL),
+                spotlightDisplayName(for: appURL),
+                FileManager.default.displayName(atPath: appURL.path)
+            ],
+            excluding: fallbackName
+        )
+    }
+
+    private static func bundleDisplayNames(bundle: Bundle?, fallbackName: String) -> [String] {
+        uniqueLocalizedNames(
+            [
+                bundle?.localizedInfoDictionary?["CFBundleDisplayName"] as? String,
+                bundle?.localizedInfoDictionary?["CFBundleName"] as? String,
+                bundle?.infoDictionary?["CFBundleDisplayName"] as? String,
+                bundle?.infoDictionary?["CFBundleName"] as? String
+            ],
+            excluding: fallbackName
+        )
     }
 
     private static func localizedAppName(
@@ -488,7 +623,7 @@
             .trimmingCharacters(in: .whitespacesAndNewlines)
     }
 
-    private static func isNestedInsideAppBundle(_ url: URL) -> Bool {
+    static func isNestedInsideAppBundle(_ url: URL) -> Bool {
         let components = url.standardizedFileURL.pathComponents
         guard let lastAppIndex = components.lastIndex(where: { $0.lowercased().hasSuffix(".app") }) else {
             return false
@@ -1325,6 +1460,8 @@
                 bundleIdentifier: app.bundleIdentifier,
                 localizedNames: app.localizedNames,
                 localizedNamesByLanguage: app.localizedNamesByLanguage,
+                systemDisplayNames: app.systemDisplayNames,
+                bundleDisplayNames: app.bundleDisplayNames,
                 icon: app.icon,
                 isUncommon: uncommonPaths.contains(app.path.path),
                 note: store.appNotes[app.path.path]
diff --git a/src/Apptag/Info.plist b/src/Apptag/Info.plist
index 43384ef..e3c01a5 100644
--- a/src/Apptag/Info.plist
+++ b/src/Apptag/Info.plist
@@ -19,9 +19,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>7.8.24</string>
+	<string>7.8.25</string>
 	<key>CFBundleVersion</key>
-	<string>20260609.1221</string>
+	<string>20260609.1741</string>
 	<key>LSApplicationCategoryType</key>
 	<string>public.app-category.utilities</string>
 	<key>LSMinimumSystemVersion</key>
diff --git a/src/Apptag/QuickSearch.swift b/src/Apptag/QuickSearch.swift
index b933fd5..1fecfb4 100644
--- a/src/Apptag/QuickSearch.swift
+++ b/src/Apptag/QuickSearch.swift
@@ -294,31 +294,33 @@
 
 enum QuickSearchEngine {
     static func makeDocuments(apps: [AppInfo], store: TagDatabase.Store) -> [QuickSearchDocument] {
-        apps.map { app in
-            let localizedNames = uniqueOrdered(app.localizedNames)
-            let internalBundleNames = internalBundleNames(for: app)
-            let note = store.appNotes[app.path.path] ?? app.note ?? ""
-            let bundleIdentifier = app.bundleIdentifier ?? ""
-            let searchableFields = makeSearchableFields(
-                appName: app.name,
-                localizedNames: localizedNames,
-                internalBundleNames: internalBundleNames,
-                tagNames: app.tags,
-                note: note,
-                bundleIdentifier: bundleIdentifier
-            )
-            return QuickSearchDocument(
-                app: app,
-                localizedNames: localizedNames,
-                internalBundleNames: internalBundleNames,
-                tagNames: app.tags,
-                note: note,
-                bundleIdentifier: bundleIdentifier,
-                lastOpenedAt: store.appLastOpenedAt[app.path.path],
-                openCount: store.appOpenCounts[app.path.path] ?? 0,
-                searchableFields: searchableFields
-            )
-        }
+        apps
+            .filter { !AppIndexer.isNestedInsideAppBundle($0.path) }
+            .map { app in
+                let localizedNames = uniqueOrdered(AppDisplayNameResolver.searchAliases(for: app))
+                let internalBundleNames = internalBundleNames(for: app)
+                let note = store.appNotes[app.path.path] ?? app.note ?? ""
+                let bundleIdentifier = app.bundleIdentifier ?? ""
+                let searchableFields = makeSearchableFields(
+                    appName: app.name,
+                    localizedNames: localizedNames,
+                    internalBundleNames: internalBundleNames,
+                    tagNames: app.tags,
+                    note: note,
+                    bundleIdentifier: bundleIdentifier
+                )
+                return QuickSearchDocument(
+                    app: app,
+                    localizedNames: localizedNames,
+                    internalBundleNames: internalBundleNames,
+                    tagNames: app.tags,
+                    note: note,
+                    bundleIdentifier: bundleIdentifier,
+                    lastOpenedAt: store.appLastOpenedAt[app.path.path],
+                    openCount: store.appOpenCounts[app.path.path] ?? 0,
+                    searchableFields: searchableFields
+                )
+            }
     }
 
     static func search(_ query: String, documents: [QuickSearchDocument], limit: Int = 50) -> [QuickSearchResult] {
@@ -688,19 +690,6 @@
             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] {
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 242c8c3..0257aa3 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -1,5 +1,13 @@
 # TagLauncher Changelog
 
+## [7.8.25] — 2026-06-09
+
+- 新增统一 App 显示名解析策略:App Grid 和 Quick Search 标题优先使用当前语种的官方本地化名,再按同语系、系统/Finder/Spotlight 可见名、英文官方名和基础 bundle 名逐级 fallback
+- Quick Search 搜索别名与结果标题分离:内部 bundle 名、可执行名和 bundle id 继续参与搜索匹配,但不再优先作为用户可见标题
+- Quick Search 文档生成增加嵌套 `.app` 防御过滤,避免 wrapper / iOS-on-Mac 应用的内层 helper 作为独立结果漏出
+- Quick Search 名称 QA 增加多语种 fixture、系统显示名 fallback、英文基础名保留和“贝锐向日葵被控 / isunclient”最终文档过滤检查
+- 版本号更新为 `7.8.25`,Build 更新为 `20260609.1741`
+
 ## [7.8.24] — 2026-06-09
 
 - 修复全新用户首次安装后,Smart Start 自动整理完成提示中点击“OK/好的”可能把整个 App Grid 关闭的问题;确认提示现在只关闭 Smart Start 弹窗,App Grid 会继续留在当前页面
diff --git a/src/Scripts/quick_search_app_name_qa.sh b/src/Scripts/quick_search_app_name_qa.sh
index dbf782e..e93b47d 100755
--- a/src/Scripts/quick_search_app_name_qa.sh
+++ b/src/Scripts/quick_search_app_name_qa.sh
@@ -3,12 +3,17 @@
 
 ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
 DATA_LAYER="$ROOT_DIR/Apptag/DataLayer.swift"
+QUICK_SEARCH="$ROOT_DIR/Apptag/QuickSearch.swift"
 SUNLOGIN_APP="${1:-/Applications/贝锐向日葵被控.app}"
 
-rg -Fq 'localizedNames.first' "$DATA_LAYER"
+rg -Fq 'AppDisplayNameResolver.displayName' "$DATA_LAYER"
+rg -Fq 'systemDisplayNames' "$DATA_LAYER"
+rg -Fq 'bundleDisplayNames' "$DATA_LAYER"
 rg -Fq '.localizedNameKey' "$DATA_LAYER"
 rg -Fq 'isNestedInsideAppBundle' "$DATA_LAYER"
 rg -Fq '.skipsPackageDescendants' "$DATA_LAYER"
+rg -Fq 'AppDisplayNameResolver.searchAliases' "$QUICK_SEARCH"
+rg -Fq 'AppIndexer.isNestedInsideAppBundle($0.path)' "$QUICK_SEARCH"
 
 swift - "$SUNLOGIN_APP" <<'SWIFT'
 import Foundation
@@ -54,6 +59,135 @@
     return result
 }
 
+func uniqueDisplayNames(_ values: [String], excluding excludedValue: String) -> [String] {
+    let excluded = normalized(excludedValue)
+    var seen = Set<String>()
+    var result: [String] = []
+    for value in values {
+        guard let trimmed = trim(value) else { continue }
+        let key = normalized(trimmed)
+        guard key != excluded, seen.insert(key).inserted else { continue }
+        result.append(trimmed)
+    }
+    return result
+}
+
+struct NameProfile {
+    let name: String
+    let localizedNames: [String]
+    let localizedNamesByLanguage: [String: String]
+    let systemDisplayNames: [String]
+    let bundleDisplayNames: [String]
+}
+
+let supportedLanguages = [
+    "en", "fr", "it", "de", "es", "pt-BR", "zh-Hans", "zh-Hant", "ko", "ja",
+    "ru", "sr-Cyrl", "uk", "th", "vi", "ar", "ar-Najdi", "tr", "id", "cs",
+    "da", "nl", "no", "nn", "nb", "ms", "pl", "ro", "sv"
+]
+
+func uniqueLanguageCodes(_ codes: [String]) -> [String] {
+    var seen = Set<String>()
+    return codes.filter { seen.insert($0).inserted }
+}
+
+func displayLanguageFallbacks(for languageCode: String, includeEnglish: Bool = false) -> [String] {
+    var candidates = [
+        languageCode,
+        languageCode.replacingOccurrences(of: "_", with: "-")
+    ]
+
+    switch languageCode {
+    case "zh-Hans":
+        candidates.append("zh-Hant")
+    case "zh-Hant":
+        candidates.append("zh-Hans")
+    case "pt-BR":
+        candidates.append("pt")
+    case "sr-Cyrl":
+        candidates.append("sr")
+    case "ar-Najdi":
+        candidates.append("ar")
+    case "nb":
+        candidates.append(contentsOf: ["no", "nn"])
+    case "nn":
+        candidates.append(contentsOf: ["no", "nb"])
+    case "no":
+        candidates.append(contentsOf: ["nb", "nn"])
+    default:
+        if let base = languageCode.split(separator: "-").first.map(String.init) {
+            candidates.append(base)
+        }
+    }
+
+    if includeEnglish {
+        candidates.append("en")
+    }
+    return uniqueLanguageCodes(candidates)
+}
+
+func firstDisplayCandidate(_ values: [String]) -> String? {
+    values.lazy.compactMap(trim).first
+}
+
+func firstLocalizedName(for languageCodes: [String], in localizedNamesByLanguage: [String: String]) -> String? {
+    firstDisplayCandidate(languageCodes.compactMap { localizedNamesByLanguage[$0] })
+}
+
+func displayName(_ profile: NameProfile, languageCode: String) -> String {
+    let languageFallbacks = displayLanguageFallbacks(for: languageCode)
+
+    if let localizedName = firstLocalizedName(
+        for: languageFallbacks,
+        in: profile.localizedNamesByLanguage
+    ) {
+        return localizedName
+    }
+
+    if languageCode == "en",
+       let englishBaseName = firstDisplayCandidate([profile.name] + profile.bundleDisplayNames) {
+        return englishBaseName
+    }
+
+    if let systemDisplayName = firstDisplayCandidate(profile.systemDisplayNames) {
+        return systemDisplayName
+    }
+
+    if let englishName = firstLocalizedName(for: ["en"], in: profile.localizedNamesByLanguage) {
+        return englishName
+    }
+
+    if let bundleDisplayName = firstDisplayCandidate(profile.bundleDisplayNames) {
+        return bundleDisplayName
+    }
+
+    let skippedLanguageCodes = Set(languageFallbacks + ["en"])
+    let remainingLocalizedNames = supportedLanguages.compactMap { languageCode -> String? in
+        guard !skippedLanguageCodes.contains(languageCode) else { return nil }
+        return profile.localizedNamesByLanguage[languageCode]
+    }
+    if let localizedName = firstDisplayCandidate(remainingLocalizedNames) {
+        return localizedName
+    }
+
+    if let alias = firstDisplayCandidate(profile.localizedNames) {
+        return alias
+    }
+
+    return profile.name
+}
+
+func searchAliases(_ profile: NameProfile) -> [String] {
+    uniqueDisplayNames(
+        supportedLanguages.compactMap { profile.localizedNamesByLanguage[$0] }
+            + profile.systemDisplayNames
+            + profile.bundleDisplayNames
+            + profile.localizedNames
+            + [displayName(profile, languageCode: "zh-Hans")],
+        excluding: profile.name
+    )
+}
+
 func isNestedInsideAppBundle(_ url: URL) -> Bool {
     let components = url.standardizedFileURL.pathComponents
     guard let lastAppIndex = components.lastIndex(where: { $0.lowercased().hasSuffix(".app") }) else {
@@ -79,21 +213,68 @@
     return trim(bundle.infoDictionary?["CFBundleDisplayName"] as? String)
 }
 
-func displayName(name: String, localizedNames: [String], localizedNamesByLanguage: [String: String], languageCode: String) -> String {
-    var candidates = [languageCode]
-    if languageCode == "zh-Hans" { candidates.append("zh-Hant") }
-    if languageCode == "zh-Hant" { candidates.append("zh-Hans") }
-    candidates.append("en")
-    var seen = Set<String>()
-    for code in candidates where seen.insert(code).inserted {
-        if let localizedName = localizedNamesByLanguage[code], !localizedName.isEmpty {
-            return localizedName
-        }
-    }
-    if let localizedName = localizedNames.first, !localizedName.isEmpty {
-        return localizedName
-    }
-    return name
+func makeDocumentPaths(appPaths: [URL]) -> [String] {
+    appPaths
+        .filter { !isNestedInsideAppBundle($0) }
+        .map(\.path)
+}
+
+let multilingualProfile = NameProfile(
+    name: "InternalRunner",
+    localizedNames: [],
+    localizedNamesByLanguage: [
+        "en": "English Name",
+        "de": "Deutscher Name",
+        "ja": "日本語名",
+        "zh-Hans": "简体名"
+    ],
+    systemDisplayNames: ["System Visible"],
+    bundleDisplayNames: ["Bundle Visible"]
+)
+guard displayName(multilingualProfile, languageCode: "de") == "Deutscher Name" else {
+    fail("German localized display name was not preferred")
+}
+guard displayName(multilingualProfile, languageCode: "ja") == "日本語名" else {
+    fail("Japanese localized display name was not preferred")
+}
+guard displayName(multilingualProfile, languageCode: "zh-Hant") == "简体名" else {
+    fail("Chinese language fallback did not use paired Chinese localization")
+}
+
+let systemFallbackProfile = NameProfile(
+    name: "internal-helper",
+    localizedNames: [],
+    localizedNamesByLanguage: [:],
+    systemDisplayNames: ["Nom système"],
+    bundleDisplayNames: ["Bundle Visible"]
+)
+guard displayName(systemFallbackProfile, languageCode: "fr") == "Nom système" else {
+    fail("system/Finder display name was not used before bundle/internal names")
+}
+
+let englishBaseProfile = NameProfile(
+    name: "AweSun",
+    localizedNames: [],
+    localizedNamesByLanguage: [:],
+    systemDisplayNames: ["贝锐向日葵"],
+    bundleDisplayNames: []
+)
+guard displayName(englishBaseProfile, languageCode: "en") == "AweSun" else {
+    fail("English users should keep a presentable English base app name")
+}
+guard displayName(englishBaseProfile, languageCode: "zh-Hans") == "贝锐向日葵" else {
+    fail("non-English users should still get system-localized display fallback")
+}
+
+let englishFallbackProfile = NameProfile(
+    name: "inner-name",
+    localizedNames: [],
+    localizedNamesByLanguage: ["en": "Official English"],
+    systemDisplayNames: [],
+    bundleDisplayNames: []
+)
+guard displayName(englishFallbackProfile, languageCode: "sv") == "Official English" else {
+    fail("official English display name was not used as cross-language fallback")
 }
 
 guard FileManager.default.fileExists(atPath: appPath) else {
@@ -156,15 +337,28 @@
     ],
     excluding: "isunclient"
 )
-let helperDisplayName = displayName(
+let helperProfile = NameProfile(
     name: "isunclient",
     localizedNames: helperLocalizedNames,
     localizedNamesByLanguage: [:],
-    languageCode: "zh-Hans"
+    systemDisplayNames: helperLocalizedNames,
+    bundleDisplayNames: []
 )
+let helperDisplayName = displayName(helperProfile, languageCode: "zh-Hans")
 guard helperDisplayName == expectedDisplayName else {
     fail("displayName fallback returned \(helperDisplayName) instead of \(expectedDisplayName)")
 }
 
+let documentPaths = makeDocumentPaths(appPaths: [appURL, nestedURL])
+guard documentPaths.contains(appPath) else {
+    fail("Quick Search document fixture dropped outer wrapper app")
+}
+guard !documentPaths.contains(nestedURL.path) else {
+    fail("Quick Search document fixture included nested helper app: \(nestedURL.path)")
+}
+guard searchAliases(helperProfile).contains(expectedDisplayName) else {
+    fail("display aliases did not retain localized helper name for search")
+}
+
 print("PASS quick search app-name QA: \(expectedDisplayName) wrapper wins over nested isunclient")
 SWIFT
diff --git a/src/TODO.md b/src/TODO.md
index 65d9485..81984bb 100644
--- a/src/TODO.md
+++ b/src/TODO.md
@@ -23,6 +23,12 @@
 
 ## Done
 
+- [2026-06-09] 多语种 App 显示名通用加固。
+  - 结果: 新增统一 `AppDisplayNameResolver`;App Grid 与 Quick Search 标题优先走当前语种/同语系/系统可见名/英文官方名/基础 bundle 名 fallback,内部 bundle 名和 bundle id 只作为搜索别名。
+  - 范围: `AppInfo` 增加系统显示名与 bundle 显示名候选;Quick Search 文档使用 resolver 生成别名并二次过滤嵌套 `.app`;版本更新为 `7.8.25 (20260609.1741)`。
+  - 不改范围: 未做第三方 App 名称机器翻译,未改 Quick Search 排名算法、App Grid 布局、窗口层级或 Smart Start 分类规则。
+  - 验证: `APP_BUILD=20260609.1741 bash build.sh`、`codesign --verify --deep --strict src/build/TagLauncher.app`、`Scripts/quick_search_app_name_qa.sh`、`Scripts/quick_search_system_app_qa.sh` 通过;构建产物 Info.plist 为 `7.8.25 (20260609.1741)`。
+
 - [2026-06-09] 修复首安 Smart Start 提示确认后 App Grid 消失的问题。
   - 结果: Smart Start 自动整理完成提示出现期间会抑制 AppKit 级 backdrop dismiss;点击“OK/好的”只关闭提示弹窗,不再关闭整个 App Grid。
   - 范围: 只改 `ContentView.swift` 的 modal interaction 发布状态,不改 Smart Start 分类规则、不改 App Grid 布局、不改窗口层级。

--
Gitblit v1.9.3