Ariver
2026-06-30 70a1cc2e63d2c49a135bd89cec3ffb1d427d90ee
Freeze prep TagLauncher 8.2.9 Pro status UI
35 files modified
229 ■■■■■ changed files
CODEGRAPH.md 6 ●●●● patch | view | raw | blame | history
src/Apptag/Info.plist 4 ●●●● patch | view | raw | blame | history
src/Apptag/Localization/ar-Najdi.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/ar.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/cs.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/da.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/de.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/en.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/es.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/fr.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/id.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/it.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/ja.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/ko.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/ms.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/nb.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/nl.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/nn.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/no.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/pl.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/pt-BR.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/ro.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/ru.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/sr-Cyrl.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/sv.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/th.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/tr.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/uk.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/vi.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/zh-Hans.json 2 ●●● patch | view | raw | blame | history
src/Apptag/Localization/zh-Hant.json 2 ●●● patch | view | raw | blame | history
src/Apptag/PreferencesView.swift 116 ●●●●● patch | view | raw | blame | history
src/CHANGELOG.md 7 ●●●●● patch | view | raw | blame | history
src/Scripts/pro_feature_gate_qa.sh 31 ●●●●● patch | view | raw | blame | history
src/Scripts/pro_localization_qa.sh 7 ●●●●● patch | view | raw | blame | history
CODEGRAPH.md
@@ -36,14 +36,14 @@
- `src/Apptag/PreferencesView.swift`
  - `PreferencesView`:设置窗口、语言、通用、主题、快捷键、标签、数据、Pro、关于等设置页。
  - 顶部设置页签必须保持 8 个标签总宽不超过旧 7 标签宽度;图标统一使用线型 SF Symbols,`Pro` 页签位于数据之后、关于之前,使用线型皇冠。
  - 设置页签下方按页签渲染统一固定位置的 `settingsFixedProHeader`:通用 / 主题 / 快捷键 / 标签 / 数据 / Pro 页显示该行且位置不漂移;语言和关于页不显示该 Pro 引导行。免费态显示当前 tab 对应购买引导和解锁/恢复按钮;Pro 已解锁态只显示权益状态说明,不显示恢复购买按钮。
  - 设置页签下方按页签渲染统一固定位置的 `settingsFixedProHeader`:语言 / 通用 / 主题 / 快捷键 / 标签 / 数据 / Pro / 关于 8 个页签都显示该行且位置不漂移。免费态左侧状态用“当前免费版用户”完整句而不是“免费版”胶囊;通用 / 主题 / 快捷键 / 标签 / 数据页显示当前 tab 对应购买引导和解锁/恢复按钮,语言和关于页只显示状态与操作按钮,不显示购买说明文字;已解锁 Pro 时,语言 / Pro / 关于页统一居中显示 `Pro 已解锁` 胶囊加 `settings.proStatus.proUser` 文案,不显示恢复购买按钮,且 `settings.proStatus.proUser` 本地化文字不再内置皇冠图标。
  - Pro 专属主题、彩色容器 / 彩色网格、自定义快捷键、自定义标签颜色和数据导入导出入口即使已解锁,也应保留皇冠 Pro 标识。
  - Language tab 去掉重复标题说明,语言矩阵在主内容区域上下左右居中;About tab 应用信息块也在主内容区域居中。
  - Theme tab 只写视觉偏好 `appGridThemeID` 和炫彩主题调色值,炫彩滑轨只能影响 AppGrid 背景,不得改变标签、排序、备注或分类数据。
  - 免费用户预览 Pro 主题时,Theme tab 状态行和当前预览主题卡片显示基于 `ProThemePreviewState.endsAt` 推导的 `MM:SS` 倒计时;倒计时到期只退出预览,不弹阻塞提示。
  - Pro tab 展示免费版 / Pro 版对比表;对比表不显示独立标题,Pro 列标题前必须有线型皇冠标识。
  - Pro tab 对比表包含自定义标签颜色、彩色容器 / 彩色网格、高级主题、自定义快捷键、自定义应用备注和数据导入导出 / 分类与布局备份恢复;免费列锁定功能显示“不可用”,彩色容器 / 彩色网格和高级主题显示“只可预览 5 分钟”,备注显示“免费 3 个”;Pro 列未购买时显示带勾的“支持”,备注行显示带勾的“无限备注”,已购买后功能行显示带勾的“已解锁”。表格内所有状态胶囊使用与“免费 3 个”一致的中性样式,不显示皇冠图标或黄色 Pro 背景。
  - About tab 只保留应用信息、帮助文档和联系信息,不放 Pro 汇总卡或免费 / Pro 差异表。
  - Pro tab 对比表包含自定义标签颜色、彩色容器 / 彩色网格、高级主题、自定义快捷键、自定义应用备注和数据导入导出 / 分类与布局备份恢复;免费列锁定功能显示“不可用”,彩色容器 / 彩色网格和高级主题显示“只可预览 5 分钟”,备注显示“免费 3 个”;Pro 列未购买时显示带勾的“支持”,备注行显示带勾的“无限备注”,已购买后功能行显示带勾的“已解锁”。表格内免费列和 Pro 列的表头及状态胶囊都在列内左对齐;所有状态胶囊使用与“免费 3 个”一致的中性样式,不显示皇冠图标或黄色 Pro 背景。
  - About tab 保留顶部固定 Pro 状态行;主内容只保留应用信息、帮助文档和联系信息,不放 Pro 汇总卡或免费 / Pro 差异表。
## AppGrid 显示与交互
src/Apptag/Info.plist
@@ -19,7 +19,7 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>8.2.8</string>
    <string>8.2.9</string>
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
@@ -32,7 +32,7 @@
        </dict>
    </array>
    <key>CFBundleVersion</key>
    <string>20260630.0128</string>
    <string>20260630.1617</string>
    <key>LSApplicationCategoryType</key>
    <string>public.app-category.utilities</string>
    <key>LSMinimumSystemVersion</key>
src/Apptag/Localization/ar-Najdi.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "عيّن أي لون لكل وسم، بما في ذلك وسوم فئات النظام SmartStart.",
  "pro.prompt.customTagColors": "ألوان الوسوم المخصصة من ميزات Pro. افتح Pro لمنح كل وسم أي لون وجعل الفئات أسهل تمييزًا.",
  "settings.proStatus.freeUser": "أنت حاليًا مستخدم مجاني",
  "settings.proStatus.proUser": "أنت حاليًا مستخدم 👑Pro مميز",
  "settings.proStatus.proUser": "أنت حاليًا مستخدم Pro مميز",
  "settings.proStatus.lockedListTitle": "ميزات Pro بانتظار الفتح",
  "settings.proStatus.unlockedListTitle": "الميزات المفتوحة",
  "settings.proStatus.lockedFeature": "✅ مدعوم",
src/Apptag/Localization/ar.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "عيّن أي لون لكل وسم، بما في ذلك وسوم فئات النظام SmartStart.",
  "pro.prompt.customTagColors": "ألوان الوسوم المخصصة من ميزات Pro. افتح Pro لمنح كل وسم أي لون وجعل الفئات أسهل تمييزًا.",
  "settings.proStatus.freeUser": "أنت حاليًا مستخدم مجاني",
  "settings.proStatus.proUser": "أنت حاليًا مستخدم 👑Pro مميز",
  "settings.proStatus.proUser": "أنت حاليًا مستخدم Pro مميز",
  "settings.proStatus.lockedListTitle": "ميزات Pro بانتظار الفتح",
  "settings.proStatus.unlockedListTitle": "الميزات المفتوحة",
  "settings.proStatus.lockedFeature": "✅ مدعوم",
src/Apptag/Localization/cs.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Nastavte libovolnou barvu každému štítku včetně systémových kategorií SmartStart.",
  "pro.prompt.customTagColors": "Vlastní barvy štítků jsou funkce Pro. Odemkněte Pro, dejte každému štítku libovolnou barvu a snáze rozeznáte kategorie.",
  "settings.proStatus.freeUser": "Právě jste uživatel bezplatné verze",
  "settings.proStatus.proUser": "Právě jste vážený uživatel 👑Pro",
  "settings.proStatus.proUser": "Právě jste vážený uživatel Pro",
  "settings.proStatus.lockedListTitle": "Funkce Pro k odemčení",
  "settings.proStatus.unlockedListTitle": "Odemčené funkce",
  "settings.proStatus.lockedFeature": "✅ Podporováno",
src/Apptag/Localization/da.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Vælg en hvilken som helst farve til hvert tag, også SmartStart-systemkategorier.",
  "pro.prompt.customTagColors": "Egne tagfarver er en Pro-funktion. Lås Pro op for at give hvert tag en valgfri farve og gøre kategorier lettere at kende.",
  "settings.proStatus.freeUser": "Du er i øjeblikket gratisbruger",
  "settings.proStatus.proUser": "Du er i øjeblikket en værdsat 👑Pro-bruger",
  "settings.proStatus.proUser": "Du er i øjeblikket en værdsat Pro-bruger",
  "settings.proStatus.lockedListTitle": "Pro-funktioner at låse op",
  "settings.proStatus.unlockedListTitle": "Oplåste funktioner",
  "settings.proStatus.lockedFeature": "✅ Understøttet",
src/Apptag/Localization/de.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Lege für jedes Tag eine beliebige Farbe fest, auch für SmartStart-Systemkategorien.",
  "pro.prompt.customTagColors": "Eigene Tag-Farben sind eine Pro-Funktion. Mit Pro gibst du jedem Tag eine beliebige Farbe und erkennst Kategorien leichter.",
  "settings.proStatus.freeUser": "Du bist derzeit Nutzer der Gratisversion",
  "settings.proStatus.proUser": "Du bist derzeit ein geschätzter 👑Pro-Nutzer",
  "settings.proStatus.proUser": "Du bist derzeit ein geschätzter Pro-Nutzer",
  "settings.proStatus.lockedListTitle": "Freizuschaltende Pro-Funktionen",
  "settings.proStatus.unlockedListTitle": "Freigeschaltete Funktionen",
  "settings.proStatus.lockedFeature": "✅ Unterstützt",
src/Apptag/Localization/en.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Set any color for every tag, including SmartStart system category tags.",
  "pro.prompt.customTagColors": "Custom tag colors are a Pro feature. Unlock Pro to give every tag any color and make categories easier to recognize.",
  "settings.proStatus.freeUser": "You are currently a Free user",
  "settings.proStatus.proUser": "You are currently a valued 👑Pro user",
  "settings.proStatus.proUser": "You are currently a valued Pro user",
  "settings.proStatus.lockedListTitle": "Pro features to unlock",
  "settings.proStatus.unlockedListTitle": "Unlocked features",
  "settings.proStatus.lockedFeature": "✅ Supported",
src/Apptag/Localization/es.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Elige cualquier color para cada etiqueta, incluidas las categorías del sistema SmartStart.",
  "pro.prompt.customTagColors": "Los colores personalizados de etiquetas son una función Pro. Desbloquea Pro para dar cualquier color a cada etiqueta y reconocer mejor tus categorías.",
  "settings.proStatus.freeUser": "Actualmente eres usuario gratuito",
  "settings.proStatus.proUser": "Actualmente eres un usuario 👑Pro distinguido",
  "settings.proStatus.proUser": "Actualmente eres un usuario Pro distinguido",
  "settings.proStatus.lockedListTitle": "Funciones Pro por desbloquear",
  "settings.proStatus.unlockedListTitle": "Funciones desbloqueadas",
  "settings.proStatus.lockedFeature": "✅ Compatible",
src/Apptag/Localization/fr.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Choisissez n’importe quelle couleur pour chaque tag, y compris les catégories système SmartStart.",
  "pro.prompt.customTagColors": "Les couleurs de tags personnalisées sont une fonction Pro. Débloquez Pro pour donner toute couleur à chaque tag et mieux reconnaître les catégories.",
  "settings.proStatus.freeUser": "Vous êtes actuellement utilisateur gratuit",
  "settings.proStatus.proUser": "Vous êtes actuellement un utilisateur 👑Pro privilégié",
  "settings.proStatus.proUser": "Vous êtes actuellement un utilisateur Pro privilégié",
  "settings.proStatus.lockedListTitle": "Fonctions Pro à débloquer",
  "settings.proStatus.unlockedListTitle": "Fonctions débloquées",
  "settings.proStatus.lockedFeature": "✅ Pris en charge",
src/Apptag/Localization/id.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Atur warna apa pun untuk tiap tag, termasuk tag kategori sistem SmartStart.",
  "pro.prompt.customTagColors": "Warna tag khusus adalah fitur Pro. Buka Pro untuk memberi tiap tag warna apa pun agar kategori lebih mudah dikenali.",
  "settings.proStatus.freeUser": "Saat ini Anda pengguna Gratis",
  "settings.proStatus.proUser": "Saat ini Anda pengguna 👑Pro istimewa",
  "settings.proStatus.proUser": "Saat ini Anda pengguna Pro istimewa",
  "settings.proStatus.lockedListTitle": "Fitur Pro untuk dibuka",
  "settings.proStatus.unlockedListTitle": "Fitur yang terbuka",
  "settings.proStatus.lockedFeature": "✅ Didukung",
src/Apptag/Localization/it.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Imposta qualsiasi colore per ogni tag, incluse le categorie di sistema SmartStart.",
  "pro.prompt.customTagColors": "I colori tag personalizzati sono una funzione Pro. Sblocca Pro per dare qualsiasi colore a ogni tag e riconoscere meglio le categorie.",
  "settings.proStatus.freeUser": "Al momento sei un utente gratuito",
  "settings.proStatus.proUser": "Al momento sei un utente 👑Pro privilegiato",
  "settings.proStatus.proUser": "Al momento sei un utente Pro privilegiato",
  "settings.proStatus.lockedListTitle": "Funzioni Pro da sbloccare",
  "settings.proStatus.unlockedListTitle": "Funzioni sbloccate",
  "settings.proStatus.lockedFeature": "✅ Supportato",
src/Apptag/Localization/ja.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "SmartStart のシステムカテゴリタグを含め、各タグに任意の色を設定できます。",
  "pro.prompt.customTagColors": "カスタムタグ色は Pro 機能です。Pro を解除すると各タグに任意の色を設定でき、カテゴリを見分けやすくなります。",
  "settings.proStatus.freeUser": "現在は無料版ユーザーです",
  "settings.proStatus.proUser": "現在は特別な 👑Pro ユーザーです",
  "settings.proStatus.proUser": "現在は特別な Pro ユーザーです",
  "settings.proStatus.lockedListTitle": "解除待ちの Pro 機能",
  "settings.proStatus.unlockedListTitle": "解除済み機能",
  "settings.proStatus.lockedFeature": "✅ 対応",
src/Apptag/Localization/ko.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "SmartStart 시스템 카테고리 태그를 포함해 모든 태그에 원하는 색상을 설정합니다.",
  "pro.prompt.customTagColors": "사용자 지정 태그 색상은 Pro 기능입니다. Pro를 잠금 해제하면 각 태그에 원하는 색을 지정해 카테고리를 더 쉽게 구분할 수 있습니다.",
  "settings.proStatus.freeUser": "현재 무료 사용자입니다",
  "settings.proStatus.proUser": "현재 특별 👑Pro 사용자입니다",
  "settings.proStatus.proUser": "현재 특별 Pro 사용자입니다",
  "settings.proStatus.lockedListTitle": "잠금 해제할 Pro 기능",
  "settings.proStatus.unlockedListTitle": "잠금 해제된 기능",
  "settings.proStatus.lockedFeature": "✅ 지원",
src/Apptag/Localization/ms.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Tetapkan sebarang warna untuk setiap tag, termasuk tag kategori sistem SmartStart.",
  "pro.prompt.customTagColors": "Warna tag tersuai ialah ciri Pro. Buka Pro untuk memberi setiap tag sebarang warna dan mengenal pasti kategori dengan lebih mudah.",
  "settings.proStatus.freeUser": "Anda kini pengguna Percuma",
  "settings.proStatus.proUser": "Anda kini pengguna 👑Pro istimewa",
  "settings.proStatus.proUser": "Anda kini pengguna Pro istimewa",
  "settings.proStatus.lockedListTitle": "Ciri Pro untuk dibuka",
  "settings.proStatus.unlockedListTitle": "Ciri dibuka",
  "settings.proStatus.lockedFeature": "✅ Disokong",
src/Apptag/Localization/nb.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Vælg en hvilken som helst farve til hvert tag, også SmartStart-systemkategorier.",
  "pro.prompt.customTagColors": "Egne tagfarver er en Pro-funktion. Lås Pro op for at give hvert tag en valgfri farve og gøre kategorier lettere at kende.",
  "settings.proStatus.freeUser": "Du er nå gratisbruker",
  "settings.proStatus.proUser": "Du er nå en verdsatt 👑Pro-bruker",
  "settings.proStatus.proUser": "Du er nå en verdsatt Pro-bruker",
  "settings.proStatus.lockedListTitle": "Pro-funksjoner å låse opp",
  "settings.proStatus.unlockedListTitle": "Opplåste funksjoner",
  "settings.proStatus.lockedFeature": "✅ Støttet",
src/Apptag/Localization/nl.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Stel elke kleur in voor elk tag, inclusief SmartStart-systeemcategorieën.",
  "pro.prompt.customTagColors": "Eigen tagkleuren zijn een Pro-functie. Ontgrendel Pro om elk tag een kleur te geven en categorieën makkelijker te herkennen.",
  "settings.proStatus.freeUser": "Je bent momenteel een gratis gebruiker",
  "settings.proStatus.proUser": "Je bent momenteel een gewaardeerde 👑Pro-gebruiker",
  "settings.proStatus.proUser": "Je bent momenteel een gewaardeerde Pro-gebruiker",
  "settings.proStatus.lockedListTitle": "Pro-functies om te ontgrendelen",
  "settings.proStatus.unlockedListTitle": "Ontgrendelde functies",
  "settings.proStatus.lockedFeature": "✅ Ondersteund",
src/Apptag/Localization/nn.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Vælg en hvilken som helst farve til hvert tag, også SmartStart-systemkategorier.",
  "pro.prompt.customTagColors": "Egne tagfarver er en Pro-funktion. Lås Pro op for at give hvert tag en valgfri farve og gøre kategorier lettere at kende.",
  "settings.proStatus.freeUser": "Du er no gratisbrukar",
  "settings.proStatus.proUser": "Du er no ein verdsett 👑Pro-brukar",
  "settings.proStatus.proUser": "Du er no ein verdsett Pro-brukar",
  "settings.proStatus.lockedListTitle": "Pro-funksjonar å låse opp",
  "settings.proStatus.unlockedListTitle": "Opplåste funksjonar",
  "settings.proStatus.lockedFeature": "✅ Støtta",
src/Apptag/Localization/no.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Vælg en hvilken som helst farve til hvert tag, også SmartStart-systemkategorier.",
  "pro.prompt.customTagColors": "Egne tagfarver er en Pro-funktion. Lås Pro op for at give hvert tag en valgfri farve og gøre kategorier lettere at kende.",
  "settings.proStatus.freeUser": "Du er nå gratisbruker",
  "settings.proStatus.proUser": "Du er nå en verdsatt 👑Pro-bruker",
  "settings.proStatus.proUser": "Du er nå en verdsatt Pro-bruker",
  "settings.proStatus.lockedListTitle": "Pro-funksjoner å låse opp",
  "settings.proStatus.unlockedListTitle": "Opplåste funksjoner",
  "settings.proStatus.lockedFeature": "✅ Støttet",
src/Apptag/Localization/pl.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Ustaw dowolny kolor dla każdego tagu, także kategorii systemowych SmartStart.",
  "pro.prompt.customTagColors": "Własne kolory tagów są funkcją Pro. Odblokuj Pro, nadaj każdemu tagowi dowolny kolor i łatwiej rozpoznawaj kategorie.",
  "settings.proStatus.freeUser": "Obecnie jesteś użytkownikiem bezpłatnym",
  "settings.proStatus.proUser": "Obecnie jesteś cenionym użytkownikiem 👑Pro",
  "settings.proStatus.proUser": "Obecnie jesteś cenionym użytkownikiem Pro",
  "settings.proStatus.lockedListTitle": "Funkcje Pro do odblokowania",
  "settings.proStatus.unlockedListTitle": "Odblokowane funkcje",
  "settings.proStatus.lockedFeature": "✅ Obsługiwane",
src/Apptag/Localization/pt-BR.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Defina qualquer cor para cada etiqueta, incluindo categorias de sistema do SmartStart.",
  "pro.prompt.customTagColors": "Cores personalizadas de etiquetas são um recurso Pro. Desbloqueie o Pro para dar qualquer cor a cada etiqueta e reconhecer categorias com mais facilidade.",
  "settings.proStatus.freeUser": "Você está usando a versão gratuita",
  "settings.proStatus.proUser": "Você é um usuário 👑Pro valorizado",
  "settings.proStatus.proUser": "Você é um usuário Pro valorizado",
  "settings.proStatus.lockedListTitle": "Recursos Pro para desbloquear",
  "settings.proStatus.unlockedListTitle": "Recursos desbloqueados",
  "settings.proStatus.lockedFeature": "✅ Compatível",
src/Apptag/Localization/ro.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Setează orice culoare pentru fiecare etichetă, inclusiv categoriile de sistem SmartStart.",
  "pro.prompt.customTagColors": "Culorile personalizate pentru etichete sunt o funcție Pro. Deblochează Pro pentru a colora fiecare etichetă și a recunoaște mai ușor categoriile.",
  "settings.proStatus.freeUser": "Ești în prezent utilizator gratuit",
  "settings.proStatus.proUser": "Ești în prezent un utilizator 👑Pro apreciat",
  "settings.proStatus.proUser": "Ești în prezent un utilizator Pro apreciat",
  "settings.proStatus.lockedListTitle": "Funcții Pro de deblocat",
  "settings.proStatus.unlockedListTitle": "Funcții deblocate",
  "settings.proStatus.lockedFeature": "✅ Acceptat",
src/Apptag/Localization/ru.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Задавайте любой цвет для каждого тега, включая системные категории SmartStart.",
  "pro.prompt.customTagColors": "Собственные цвета тегов — функция Pro. Откройте Pro, чтобы назначать любому тегу любой цвет и легче различать категории.",
  "settings.proStatus.freeUser": "Сейчас вы пользователь бесплатной версии",
  "settings.proStatus.proUser": "Сейчас вы ценный пользователь 👑Pro",
  "settings.proStatus.proUser": "Сейчас вы ценный пользователь Pro",
  "settings.proStatus.lockedListTitle": "Функции Pro для разблокировки",
  "settings.proStatus.unlockedListTitle": "Разблокированные функции",
  "settings.proStatus.lockedFeature": "✅ Поддерживается",
src/Apptag/Localization/sr-Cyrl.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Подесите било коју боју за сваку ознаку, укључујући SmartStart системске категорије.",
  "pro.prompt.customTagColors": "Прилагођене боје ознака су Pro функција. Откључајте Pro да свакој ознаци дате било коју боју и лакше препознате категорије.",
  "settings.proStatus.freeUser": "Тренутно сте корисник бесплатне верзије",
  "settings.proStatus.proUser": "Тренутно сте цењени 👑Pro корисник",
  "settings.proStatus.proUser": "Тренутно сте цењени Pro корисник",
  "settings.proStatus.lockedListTitle": "Pro функције за откључавање",
  "settings.proStatus.unlockedListTitle": "Откључане функције",
  "settings.proStatus.lockedFeature": "✅ Подржано",
src/Apptag/Localization/sv.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Ange valfri färg för varje tagg, inklusive SmartStart-systemkategorier.",
  "pro.prompt.customTagColors": "Egna taggfärger är en Pro-funktion. Lås upp Pro för att ge varje tagg valfri färg och göra kategorier lättare att känna igen.",
  "settings.proStatus.freeUser": "Du är för närvarande gratisanvändare",
  "settings.proStatus.proUser": "Du är för närvarande en uppskattad 👑Pro-användare",
  "settings.proStatus.proUser": "Du är för närvarande en uppskattad Pro-användare",
  "settings.proStatus.lockedListTitle": "Pro-funktioner att låsa upp",
  "settings.proStatus.unlockedListTitle": "Upplåsta funktioner",
  "settings.proStatus.lockedFeature": "✅ Stöds",
src/Apptag/Localization/th.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "ตั้งค่าสีใดก็ได้ให้ทุกแท็ก รวมถึงแท็กหมวดหมู่ระบบ SmartStart",
  "pro.prompt.customTagColors": "สีแท็กแบบกำหนดเองเป็นฟีเจอร์ Pro ปลดล็อก Pro เพื่อให้ทุกแท็กใช้สีใดก็ได้และแยกหมวดหมู่ได้ง่ายขึ้น",
  "settings.proStatus.freeUser": "ตอนนี้คุณเป็นผู้ใช้ฟรี",
  "settings.proStatus.proUser": "ตอนนี้คุณเป็นผู้ใช้ 👑Pro ระดับพิเศษ",
  "settings.proStatus.proUser": "ตอนนี้คุณเป็นผู้ใช้ Pro ระดับพิเศษ",
  "settings.proStatus.lockedListTitle": "ฟีเจอร์ Pro ที่รอปลดล็อก",
  "settings.proStatus.unlockedListTitle": "ฟีเจอร์ที่ปลดล็อกแล้ว",
  "settings.proStatus.lockedFeature": "✅ รองรับ",
src/Apptag/Localization/tr.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "SmartStart sistem kategorileri dahil her etikete istediğiniz rengi verin.",
  "pro.prompt.customTagColors": "Özel etiket renkleri bir Pro özelliğidir. Pro’yu açarak her etikete istediğiniz rengi verin ve kategorileri daha kolay tanıyın.",
  "settings.proStatus.freeUser": "Şu anda ücretsiz kullanıcısısınız",
  "settings.proStatus.proUser": "Şu anda değerli bir 👑Pro kullanıcısısınız",
  "settings.proStatus.proUser": "Şu anda değerli bir Pro kullanıcısısınız",
  "settings.proStatus.lockedListTitle": "Açılacak Pro özellikleri",
  "settings.proStatus.unlockedListTitle": "Açılmış özellikler",
  "settings.proStatus.lockedFeature": "✅ Desteklenir",
src/Apptag/Localization/uk.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Задавайте будь-який колір для кожного тега, включно із системними категоріями SmartStart.",
  "pro.prompt.customTagColors": "Власні кольори тегів — функція Pro. Відкрийте Pro, щоб давати кожному тегу будь-який колір і легше розпізнавати категорії.",
  "settings.proStatus.freeUser": "Зараз ви користувач безкоштовної версії",
  "settings.proStatus.proUser": "Зараз ви цінний користувач 👑Pro",
  "settings.proStatus.proUser": "Зараз ви цінний користувач Pro",
  "settings.proStatus.lockedListTitle": "Функції Pro для відкриття",
  "settings.proStatus.unlockedListTitle": "Відкриті функції",
  "settings.proStatus.lockedFeature": "✅ Підтримується",
src/Apptag/Localization/vi.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "Đặt bất kỳ màu nào cho mỗi thẻ, kể cả thẻ danh mục hệ thống SmartStart.",
  "pro.prompt.customTagColors": "Màu thẻ tùy chỉnh là tính năng Pro. Mở khóa Pro để đặt màu bất kỳ cho từng thẻ và nhận diện danh mục dễ hơn.",
  "settings.proStatus.freeUser": "Bạn hiện là người dùng Miễn phí",
  "settings.proStatus.proUser": "Bạn hiện là người dùng 👑Pro danh giá",
  "settings.proStatus.proUser": "Bạn hiện là người dùng Pro danh giá",
  "settings.proStatus.lockedListTitle": "Tính năng Pro chờ mở khóa",
  "settings.proStatus.unlockedListTitle": "Tính năng đã mở khóa",
  "settings.proStatus.lockedFeature": "✅ Hỗ trợ",
src/Apptag/Localization/zh-Hans.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "为每个标签设置任意颜色,包括 SmartStart 系统分类标签。",
  "pro.prompt.customTagColors": "自定义标签颜色属于 Pro 功能。解锁 Pro 后,可为每个标签设置任意颜色,让分类更容易识别。",
  "settings.proStatus.freeUser": "您当前是免费版用户",
  "settings.proStatus.proUser": "您当前是尊贵的 👑Pro版用户",
  "settings.proStatus.proUser": "您当前是尊贵的 Pro 用户",
  "settings.proStatus.lockedListTitle": "待解锁的 Pro 功能",
  "settings.proStatus.unlockedListTitle": "已解锁的功能",
  "settings.proStatus.lockedFeature": "✅支持",
src/Apptag/Localization/zh-Hant.json
@@ -318,7 +318,7 @@
  "pro.feature.customTagColors.benefit": "為每個標籤設定任意顏色,包括 SmartStart 系統分類標籤。",
  "pro.prompt.customTagColors": "自訂標籤顏色屬於 Pro 功能。解鎖 Pro 後可為每個標籤設定任意顏色,讓分類更容易辨識。",
  "settings.proStatus.freeUser": "您目前是免費版使用者",
  "settings.proStatus.proUser": "您目前是尊貴的 👑Pro版使用者",
  "settings.proStatus.proUser": "您目前是尊貴的 Pro 使用者",
  "settings.proStatus.lockedListTitle": "待解鎖的 Pro 功能",
  "settings.proStatus.unlockedListTitle": "已解鎖的功能",
  "settings.proStatus.lockedFeature": "✅支援",
src/Apptag/PreferencesView.swift
@@ -688,6 +688,23 @@
        Color(red: 0.56, green: 0.35, blue: 0.03)
    }
    @ViewBuilder
    private func proHeaderStatusLabel(compact: Bool = false) -> some View {
        if proEntitlement.accessState == .free && !proEntitlement.isPreviewingProAppearance {
            Text(tr("settings.proStatus.freeUser"))
                .font(.system(size: compact ? 12 : 13, weight: .semibold))
                .foregroundStyle(.secondary)
                .lineLimit(1)
                .minimumScaleFactor(0.82)
        } else {
            ProStatusPill(
                text: tr(proEntitlement.compactStatusTextKey),
                style: proStatusPillStyle,
                compact: compact
            )
        }
    }
    private func centeredSettingsScrollContent<Content: View>(
        width: CGFloat,
        @ViewBuilder content: @escaping () -> Content
@@ -733,10 +750,7 @@
        compact: Bool = false
    ) -> some View {
        HStack(spacing: compact ? 8 : 12) {
            ProStatusPill(
                text: tr(proEntitlement.compactStatusTextKey),
                style: proStatusPillStyle
            )
            proHeaderStatusLabel(compact: compact)
            .layoutPriority(1)
            Text(tr(textKey))
@@ -811,7 +825,9 @@
        }
        switch selectedTab {
        case .general, .language, .about:
        case .language, .about:
            return ""
        case .general:
            return tr(proEntitlement.isUnlocked ? "settings.proBenefit.general" : "settings.proGuide.general")
        case .theme:
            return themeStatusSummary
@@ -827,43 +843,72 @@
    }
    private var shouldShowSettingsFixedProHeader: Bool {
        selectedTab != .language && selectedTab != .about
        true
    }
    private var shouldCenterUnlockedIdentityHeader: Bool {
        guard proEntitlement.isUnlocked else { return false }
        switch selectedTab {
        case .language, .pro, .about:
            return true
        case .general, .theme, .hotkeys, .tags, .data:
            return false
        }
    }
    private var settingsFixedProHeader: some View {
        HStack {
            HStack(spacing: 12) {
                if selectedTab != .pro {
                if shouldCenterUnlockedIdentityHeader {
                    Spacer(minLength: 0)
                    ProStatusPill(
                        text: tr(proEntitlement.compactStatusTextKey),
                        style: proStatusPillStyle
                        text: tr("pro.status.unlocked"),
                        style: .unlocked,
                        compact: true
                    )
                    .layoutPriority(1)
                }
                Text(settingsProHeaderText)
                    .font(.system(size: 13, weight: .medium))
                    .foregroundStyle(.secondary)
                    .lineLimit(2)
                    .fixedSize(horizontal: false, vertical: true)
                    .layoutPriority(2)
                    Text(tr("settings.proStatus.proUser"))
                        .font(.system(size: 13, weight: .medium))
                        .foregroundStyle(.secondary)
                        .lineLimit(1)
                        .fixedSize(horizontal: false, vertical: true)
                        .layoutPriority(2)
                Spacer(minLength: 8)
                if !proEntitlement.isUnlocked {
                    Button(unlockButtonTitle()) {
                        presentSettingsProPrompt(for: settingsProHeaderFeature)
                    Spacer(minLength: 0)
                } else {
                    if selectedTab != .pro {
                        proHeaderStatusLabel(compact: true)
                        .layoutPriority(1)
                    }
                    .buttonStyle(.borderedProminent)
                    .controlSize(.small)
                    .layoutPriority(3)
                    Button(tr("pro.card.restore")) {
                        proEntitlement.restorePurchases()
                    if !settingsProHeaderText.isEmpty {
                        Text(settingsProHeaderText)
                            .font(.system(size: 13, weight: .medium))
                            .foregroundStyle(.secondary)
                            .lineLimit(2)
                            .fixedSize(horizontal: false, vertical: true)
                            .layoutPriority(2)
                    }
                    .buttonStyle(.bordered)
                    .controlSize(.small)
                    .layoutPriority(3)
                    Spacer(minLength: 8)
                    if !proEntitlement.isUnlocked {
                        Button(unlockButtonTitle()) {
                            presentSettingsProPrompt(for: settingsProHeaderFeature)
                        }
                        .buttonStyle(.borderedProminent)
                        .controlSize(.small)
                        .layoutPriority(3)
                        Button(tr("pro.card.restore")) {
                            proEntitlement.restorePurchases()
                        }
                        .buttonStyle(.bordered)
                        .controlSize(.small)
                        .layoutPriority(3)
                    }
                }
            }
            .padding(.horizontal, 16)
@@ -953,7 +998,7 @@
                    Text(tr("settings.proCompare.free"))
                        .font(.system(size: 12, weight: .semibold))
                        .foregroundStyle(.secondary)
                        .frame(width: 118, alignment: .center)
                        .frame(width: 118, alignment: .leading)
                    HStack(spacing: 4) {
                        Image(systemName: "crown")
                            .font(.system(size: 12, weight: .semibold))
@@ -963,7 +1008,7 @@
                    .foregroundStyle(proAccentColor)
                    .lineLimit(1)
                    .minimumScaleFactor(0.82)
                    .frame(width: 118, alignment: .center)
                    .frame(width: 118, alignment: .leading)
                }
                ForEach(proTabComparisonFeatures) { feature in
@@ -983,14 +1028,14 @@
                            style: feature.freeStyle,
                            compact: true
                        )
                        .frame(width: 118, alignment: .center)
                        .frame(width: 118, alignment: .leading)
                        ProStatusPill(
                            text: tr(proStatusKey),
                            style: proStyle,
                            compact: true
                        )
                        .frame(width: 118, alignment: .center)
                        .frame(width: 118, alignment: .leading)
                    }
                    .padding(.horizontal, 10)
                    .padding(.vertical, 8)
@@ -1354,10 +1399,7 @@
            proUnlockedBenefitLine("pro.theme.status.unlocked", bottomPadding: 0)
        } else {
            HStack(spacing: 12) {
                ProStatusPill(
                    text: tr(proEntitlement.compactStatusTextKey),
                    style: proStatusPillStyle
                )
                proHeaderStatusLabel()
                Text(themeStatusSummary)
                    .font(.system(size: 13, weight: .medium))
src/CHANGELOG.md
@@ -2,6 +2,13 @@
## [Unreleased]
## [8.2.9] — 2026-06-30
- 统一已解锁 Pro 用户在设置页“语言 / Pro / 关于”顶部状态行:居中显示 `Pro 已解锁` 胶囊和“您当前是尊贵的 Pro 用户”
- 移除该 Pro 用户身份文案内的重复皇冠符号,皇冠只由前置胶囊图标承载;同步更新 29 个语种
- 扩展 Pro / 本地化 QA 门禁,防止该状态行回退为非居中、重复皇冠或错误页签范围
- 版本号更新为 `8.2.9`,Build 更新为 `20260630.1617`
## [8.2.8] — 2026-06-30
- 修复 Quick Search 候选列表触摸板 / 鼠标点击仍无法启动应用的问题:结果面板保留原 `hitTest` 路径,并增加按窗口坐标递归命中结果行的几何兜底,复用回车同一启动路径
src/Scripts/pro_feature_gate_qa.sh
@@ -157,10 +157,32 @@
require(preferences, "private var settingsFixedProHeader: some View", "Settings pages must share one fixed-position Pro guidance header")
require(preferences, "private var settingsProHeaderText: String", "fixed Pro guidance header must switch copy by selected tab")
require(preferences, "private var settingsProHeaderFeature: ProFeature", "fixed Pro guidance header must route unlock prompts by selected tab")
require(preferences, "private var shouldShowSettingsFixedProHeader: Bool", "Language/About tabs must be able to hide the fixed Pro header")
require(preferences, "selectedTab != .language && selectedTab != .about", "fixed Pro header must be hidden on Language and About tabs")
require(preferences, "private var shouldCenterUnlockedIdentityHeader: Bool", "Language/Pro/About tabs need the centered unlocked Pro identity header")
center_identity_body = function_body(preferences, "private var shouldCenterUnlockedIdentityHeader: Bool")
require(center_identity_body, "guard proEntitlement.isUnlocked else { return false }", "centered Pro identity header must only apply after Pro unlock")
require(center_identity_body, "case .language, .pro, .about:", "centered Pro identity header must apply to Language, Pro, and About tabs")
require(center_identity_body, "case .general, .theme, .hotkeys, .tags, .data:", "centered Pro identity header must not affect the feature-specific settings tabs")
settings_header_text_body = function_body(preferences, "private var settingsProHeaderText: String")
require(settings_header_text_body, "case .language, .about:", "Language/About tabs must have an explicit fixed-header text branch")
require(settings_header_text_body, 'return ""', "Language/About tabs must not show the general Pro guidance sentence")
if "case .general, .language, .about:" in settings_header_text_body:
    fail("Language/About tabs must not reuse the General tab Pro guidance sentence")
require(preferences, "private func proHeaderStatusLabel(compact: Bool = false) -> some View", "fixed Pro guidance header must render the free status as plain text")
require(preferences, 'Text(tr("settings.proStatus.freeUser"))', "Free status must render as the full current-user sentence instead of a Free pill")
require(preferences, "private var shouldShowSettingsFixedProHeader: Bool", "all settings tabs must use the fixed Pro header gate")
require(function_body(preferences, "private var shouldShowSettingsFixedProHeader: Bool"), "true", "fixed Pro header must be shown on Language and About tabs too")
if "selectedTab != .language && selectedTab != .about" in preferences:
    fail("fixed Pro header must no longer be hidden on Language and About tabs")
require(preferences, "if shouldShowSettingsFixedProHeader", "Settings body must conditionally render the fixed Pro header")
fixed_header_body = function_body(preferences, "private var settingsFixedProHeader: some View")
require(fixed_header_body, "if shouldCenterUnlockedIdentityHeader", "fixed Pro header must use the centered unlocked identity branch")
require(fixed_header_body, 'text: tr("pro.status.unlocked")', "centered Pro identity header must start with the Pro unlocked pill")
require(fixed_header_body, "style: .unlocked", "centered Pro identity pill must use the unlocked style")
require(fixed_header_body, 'Text(tr("settings.proStatus.proUser"))', "centered Pro identity header must render the current Pro user sentence")
if fixed_header_body.find("if shouldCenterUnlockedIdentityHeader") > fixed_header_body.find('Text(tr("settings.proStatus.proUser"))'):
    fail("centered Pro identity sentence must be inside the centered branch")
if fixed_header_body.count("Spacer(minLength: 0)") < 2:
    fail("centered Pro identity header must be horizontally centered with leading and trailing spacers")
require(fixed_header_body, "if !proEntitlement.isUnlocked", "fixed Pro header must branch on unlocked state")
require(fixed_header_body, 'Button(tr("pro.card.restore"))', "fixed Pro header must keep restore purchase for free users")
if fixed_header_body.find("if !proEntitlement.isUnlocked") > fixed_header_body.find('Button(tr("pro.card.restore"))'):
@@ -213,6 +235,11 @@
require(preferences, 'selectedTab == .pro', "fixed Pro header must render the Pro tab user status state")
require(preferences, "proTabComparisonTable(width: dataPanelWidth)", "Pro tab must render the Free-vs-Pro comparison table")
require(preferences, "private struct ProTabComparisonFeature", "Pro tab comparison row model is missing")
comparison_body = function_body(preferences, "private func proTabComparisonTable(width: CGFloat)")
if comparison_body.count(".frame(width: 118, alignment: .leading)") < 4:
    fail("Pro comparison Free/Pro columns and status chips must be left-aligned")
if ".frame(width: 118, alignment: .center)" in comparison_body:
    fail("Pro comparison status columns must not be centered")
if 'Text(tr("settings.proCompare.title"))' in preferences:
    fail("Pro tab comparison table must not render the removed title")
require(preferences, 'Text(tr("settings.proCompare.free"))', "Pro tab comparison table free column is missing")
src/Scripts/pro_localization_qa.sh
@@ -122,6 +122,8 @@
    fail("en.json Pro comparison unlimited notes status must include the leading checkmark")
if english.get("settings.proStatus.unlockedFeature") != "✅ Unlocked":
    fail("en.json Pro comparison unlocked status must be '✅ Unlocked'")
if english.get("settings.proStatus.proUser") != "You are currently a valued Pro user":
    fail("en.json settings.proStatus.proUser must not duplicate the crown icon in text")
zh_hans = load_json(localization_dir / "zh-Hans.json")
if zh_hans.get("settings.proStatus.lockedFeature") != "✅支持":
@@ -130,6 +132,8 @@
    fail("zh-Hans.json Pro comparison unlimited notes status must be '✅无限备注'")
if zh_hans.get("settings.proStatus.unlockedFeature") != "✅已解锁":
    fail("zh-Hans.json Pro comparison unlocked status must be '✅已解锁'")
if zh_hans.get("settings.proStatus.proUser") != "您当前是尊贵的 Pro 用户":
    fail("zh-Hans.json settings.proStatus.proUser must be the approved centered Pro identity sentence")
ko = load_json(localization_dir / "ko.json")
if ko.get("quickSearch.hotkeys") != "키보드 단축키":
@@ -197,6 +201,9 @@
        if copied_english:
            fail(f"{code}.json copies English Pro copy: {', '.join(copied_english)}")
    if "👑" in data.get("settings.proStatus.proUser", ""):
        fail(f"{code}.json settings.proStatus.proUser must not duplicate the crown icon")
print(
    "PASS Pro localization QA: "
    f"{len(language_codes)} languages, {len(pro_keys) + len(REQUIRED_NON_PRO_KEYS) + len(REQUIRED_VISIBLE_SETTINGS_KEYS)} Pro/settings keys, "