Ariver
2026-06-26 2c36a4a39da126c30a5a5daedf501229412b2e1a
Release 8.1.0 free Pro unlock
41 files modified
5 files added
3446 ■■■■■ changed files
CODEGRAPH.md 22 ●●●●● patch | view | raw | blame | history
src/Apptag/AppGridSupport.swift 14 ●●●●● patch | view | raw | blame | history
src/Apptag/AppGridTheme.swift 19 ●●●●● patch | view | raw | blame | history
src/Apptag/ApptagApp.swift 1 ●●●● patch | view | raw | blame | history
src/Apptag/ContentView.swift 379 ●●●● patch | view | raw | blame | history
src/Apptag/DataLayer.swift 20 ●●●●● patch | view | raw | blame | history
src/Apptag/Info.plist 4 ●●●● patch | view | raw | blame | history
src/Apptag/L10n.swift 8 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/ar-Najdi.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/ar.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/cs.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/da.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/de.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/en.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/es.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/fr.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/id.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/it.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/ja.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/ko.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/ms.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/nb.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/nl.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/nn.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/no.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/pl.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/pt-BR.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/ro.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/ru.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/sr-Cyrl.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/sv.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/th.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/tr.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/uk.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/vi.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/zh-Hans.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/Localization/zh-Hant.json 47 ●●●●● patch | view | raw | blame | history
src/Apptag/PreferencesView.swift 302 ●●●●● patch | view | raw | blame | history
src/Apptag/ProAccessViews.swift 207 ●●●●● patch | view | raw | blame | history
src/Apptag/ProEntitlement.swift 639 ●●●●● patch | view | raw | blame | history
src/CHANGELOG.md 9 ●●●●● patch | view | raw | blame | history
src/Docs/Requirements/2026-06-26-free-pro-implementation-todo.md 155 ●●●●● patch | view | raw | blame | history
src/Scripts/pro_feature_gate_qa.sh 147 ●●●●● patch | view | raw | blame | history
src/Scripts/pro_localization_qa.sh 106 ●●●●● patch | view | raw | blame | history
src/Scripts/theme_settings_qa.sh 9 ●●●● patch | view | raw | blame | history
src/Scripts/window_logic_qa.sh 42 ●●●●● patch | view | raw | blame | history
CODEGRAPH.md
@@ -1,6 +1,6 @@
# TagLauncher CODEGRAPH
最后更新:2026-06-25
最后更新:2026-06-26
用途:本文件是 TagLauncher 的长期工程地图。修改用户可见行为、状态模型、构建/发布入口、核心源码或 QA 入口前,应先读本文件,再读指向的真实源码。若本文件与源码不一致,以源码为准,并在同一任务内更新本文件。
@@ -20,6 +20,7 @@
  - `TagLauncherApp`:SwiftUI App 入口。
  - `AppDelegate`:菜单栏状态项、Dock 显示策略、热键注册、窗口打开、Quick Search 触发、App 生命周期。
  - 支持从标签导航双击进入设置页的 Tags tab。
  - 启动时初始化 `ProEntitlementCenter`,保证免费 / Pro / 老用户自动 Pro 状态进入全局同步快照。
- `src/Apptag/OverlayWindowController.swift`
  - `OverlayPanel` / `OverlayWindowController`:AppGrid 浮层窗口与显示/隐藏控制。
- `src/Apptag/ContentView.swift`
@@ -44,6 +45,14 @@
- `src/Apptag/AppGridTheme.swift`
  - AppGrid 主题事实源:theme id、localization key、设置页 swatch、全屏渐变背景、玻璃明暗、编辑模式 token。
  - 持久化 key:`appGridThemeID`。
  - 主题是否需要 Pro 只通过 `ProEntitlementConfig.freeThemes` 判断,不在主题 UI 中散落规则。
- `src/Apptag/ProEntitlement.swift`
  - `ProEntitlementCenter`:StoreKit 2 当前权益、购买、恢复、交易更新、离线缓存和 QA 状态注入。
  - `ProEntitlementPolicy`:数据层可同步调用的 Pro 门禁;覆盖主题、导入 / 导出、备注额度和应用排序持久化。
  - `ProEntitlementConfig`:一次性买断商品 ID、免费 + Pro 首个版本、免费主题集合、免费备注额度和 QA 环境变量的单一配置点。
- `src/Apptag/ProAccessViews.swift`
  - `ProStatusPill`:高级功能入口的小型 Pro 胶囊。
  - `ProUpgradePromptView`:功能被锁时的统一解锁提示,购买 / 恢复动作由调用方闭包绑定。
- `src/Apptag/AppGridSupport.swift`
  - 图标气泡、基础 metrics、气泡 placement 支撑。
- `src/Apptag/TagNavigationView.swift`
@@ -96,6 +105,7 @@
  - 语言选择、translation JSON 加载、帮助文档入口。
- `src/Apptag/Localization/*.json`
  - App UI 本地化文案。新增用户可见文案时必须覆盖全部 29 个语言文件,并验证 JSON 合法。
  - `smart.category.*` 是 SmartStart/default system tag 的显示事实源;非英文语言不得直接复制英文初始化标签。
- `src/Apptag/Resources/`
  - SmartStart、Apple default catalog、帮助文档等运行时资源。
- `src/Docs/Requirements/`
@@ -128,6 +138,8 @@
  - Apple 默认备注迁移检查。
- `src/Scripts/smartstart_catalog_resource_qa.sh`
  - SmartStart catalog 资源检查。
- `src/Scripts/smart_category_localization_qa.sh`
  - SmartCategory/default system tag 初始化标签 29 语种本地化检查;禁止非英文语言包直接复制英文标签,保留 `PDF`、`DevOps` 等技术通用词例外。
- `src/Scripts/quick_search_app_name_qa.sh`
  - Quick Search 应用显示名回归。
- `src/Scripts/quick_search_system_app_qa.sh`
@@ -138,6 +150,10 @@
  - 标签双击进入标签设置页回归。
- `src/Scripts/theme_settings_qa.sh`
  - 8 个主题、Theme tab、旧深色偏好迁移、编辑模式默认浅色 override、29 语种主题文案回归。
- `src/Scripts/pro_localization_qa.sh`
  - Pro 文案 29 语种静态门禁;检查 `pro.*` 键完整、占位符完整、无生成污染和非英文语言无英文兜底复制。
- `src/Scripts/pro_feature_gate_qa.sh`
  - Pro 功能源码门禁;检查 StoreKit 权益、老用户自动 Pro、主题门禁、导入导出硬拦截、备注额度、排序预览和 Pro UI 提示。
- `src/Scripts/appgrid_startup_loading_qa.sh`
  - AppGrid 快照复用和延迟转圈回归。
- `src/Scripts/usage_tips_qa.sh`
@@ -154,6 +170,9 @@
- 不得擅自改变源码根目录边界:App 源码只在 `src/` 下维护。
- 不得把主题、视觉偏好、窗口状态混入 `TagDatabase` 的分类/标签业务数据。
- 主题变化只允许影响 AppGrid 视觉,不得改变标签数据、排序、备注、SmartStart/category scheme、Quick Search、拖拽语义或导入导出。
- Pro 门禁不得写脏免费用户真实数据:主题预览不写 `appGridThemeID`,排序预览不写 `containerAppOrder`,第 6 个新备注不写 `appNotes` / `appNoteMetadata`,导入 / 导出拦截必须早于文件面板和文件读写。
- 免费主题固定为默认、深蓝、黑色;其它主题只能预览或在 Pro 解锁后持久化。
- `originalAppVersion` 早于免费 + Pro 首个版本的历史用户必须自动获得 Pro;`originalPurchaseDate` 只能作为版本不可判断时的兜底。
- 默认主题必须保留原浅色毛玻璃 AppGrid。
- 黑色主题背景必须为 100% 纯黑;深蓝和黑色使用深色玻璃;粉色、紫色、绿色、蓝色、炫彩使用浅色玻璃以保证明亮和可读。
- 单个 AppGrid 容器保持统一半透明玻璃面板,不做容器内部渐变。
@@ -162,6 +181,7 @@
- AppGrid 启动或重开不应因为新 `ContentView` 初始 `allApps.isEmpty` 立刻显示转圈;应先复用最近一次完整快照,并延迟显示 loading。
- Quick Search 显示标题应优先使用解析后的 `AppInfo.displayName`,内部 bundle 名只作为搜索字段。
- Apple 默认应用备注必须按当前语言显示,不允许 A 语言出现 B 语言备注。
- SmartStart/default system tag 初始化标签必须按当前语言显示;升级用户启动后应重刷带 `systemCategoryID` 的系统标签,不能长期保留旧英文初始化标签。
- 标签导航单击必须立即滚动;hover 必须保持受控自动滚动;长按排序必须保留;双击可以打开标签设置页但不得替代前三者。
- 容器外空白拖放才可触发解除标签;容器内部空白不算解除标签。
- 拖到解除标签区域的碎纸预览只能是视觉反馈,不能跳过放手后的原确认流程。
src/Apptag/AppGridSupport.swift
@@ -32,6 +32,8 @@
    let isEditing: Bool
    let placement: BubblePlacement
    let arrowOffset: CGFloat
    let noteQuotaText: String?
    let noteQuotaWarning: Bool
    @Binding var draftNote: String
    var noteFocused: FocusState<Bool>.Binding
    let onCommit: () -> Void
@@ -72,6 +74,18 @@
                    Text("\(draftNote.count) / \(TagDatabase.maxAppNoteLength)")
                        .font(.system(size: 10, weight: .medium))
                        .foregroundStyle(.white.opacity(0.35))
                    if let noteQuotaText, !noteQuotaText.isEmpty {
                        Text(noteQuotaText)
                            .font(.system(size: 11, weight: .medium))
                            .foregroundStyle(
                                noteQuotaWarning
                                    ? Color.orange.opacity(0.96)
                                    : Color.white.opacity(0.58)
                            )
                            .multilineTextAlignment(.trailing)
                            .fixedSize(horizontal: false, vertical: true)
                    }
                }
            } else if !displayNote.isEmpty {
                Text(displayNote)
src/Apptag/AppGridTheme.swift
@@ -13,6 +13,13 @@
    static let storageKey = "appGridThemeID"
    static let fallback = AppGridTheme.defaultLight
    static var freeThemes: [AppGridTheme] {
        allCases.filter(\.isFreeTheme)
    }
    static var premiumThemes: [AppGridTheme] {
        allCases.filter(\.requiresPro)
    }
    var id: String { rawValue }
@@ -37,6 +44,18 @@
        self == .defaultLight
    }
    var isFreeTheme: Bool {
        ProEntitlementConfig.freeThemes.contains(self)
    }
    var requiresPro: Bool {
        !isFreeTheme
    }
    var isPremiumTheme: Bool {
        requiresPro
    }
    var usesVisualEffectBackdrop: Bool {
        self != .black
    }
src/Apptag/ApptagApp.swift
@@ -193,6 +193,7 @@
    func applicationDidFinishLaunching(_ notification: Notification) {
        AppDefaults.register()
        L10n.setup()
        ProEntitlementCenter.shared.start()
        migrateDefaultGroupName()
        TagDatabase.seedDefaultTags()
        _ = TagDatabase.relocalizeSystemTagsForCurrentLanguage()
src/Apptag/ContentView.swift
@@ -16,7 +16,9 @@
enum SettingsTabTarget {
    static let userInfoKey = "tab"
    static let theme = "theme"
    static let tags = "tags"
    static let data = "data"
}
// MARK: - Edit Phase
@@ -326,6 +328,7 @@
    var editingBubble: AppBubbleContext? = nil
    var pendingUncategorizedDrop: PendingUncategorizedDrop? = nil
    var pendingTagRemovalDrop: PendingTagRemovalDrop? = nil
    var proPrompt: PendingProPrompt? = nil
    var uncategorizedDropSuppressFuturePrompt = false
    var tagRemovalDropSuppressFuturePrompt = false
    var usageTipsCloseReminderVisible = false
@@ -357,11 +360,18 @@
    let tagName: String
}
private struct PendingProPrompt: Identifiable {
    let id = UUID()
    let feature: ProFeature
    let noteQuotaStatus: ProNoteQuotaStatus?
}
struct ContentView: View {
    let hideOverlay: () -> Void
    private let initialQuickSearchSource: String?
    @Environment(\.colorScheme) private var colorScheme
    @ObservedObject private var proEntitlement = ProEntitlementCenter.shared
    @State private var allApps: [AppInfo] = []
    @State private var displayGroups: [TagGroup] = []
    @State private var tagColors: [String: Int] = [:]
@@ -453,6 +463,7 @@
            || appGridInteraction.pendingUncategorizedDrop != nil
            || appGridInteraction.pendingTagRemovalDrop != nil
            || appGridInteraction.usageTipsCloseReminderVisible
            || appGridInteraction.proPrompt != nil
    }
    private var appGridHighlightedGroupName: String? {
        appGridInteraction.tagNavigationHoveredGroupName
@@ -472,7 +483,9 @@
    }
    private var renderedAppGridTheme: AppGridTheme {
        editPhase == .none ? appGridTheme : .defaultLight
        editPhase == .none
            ? ProEntitlementPolicy.effectiveTheme(for: appGridTheme)
            : .defaultLight
    }
    private var isSideLayout: Bool {
@@ -512,6 +525,65 @@
    }
    var body: some View {
        contentStack
            .onAppear(perform: handleContentAppear)
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidShow), perform: handleOverlayDidShow)
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidHide), perform: handleOverlayDidHide)
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchRequested), perform: handleQuickSearchRequested)
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchDismissRequested), perform: handleQuickSearchDismissRequested)
            .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification), perform: handleApplicationDidBecomeActive)
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherDataDidChange), perform: handleDataDidChange)
            .onReceive(NotificationCenter.default.publisher(for: .appLanguageDidChange), perform: handleAppLanguageDidChange)
            .onChange(of: editPhase) { _, newPhase in
                let active = newPhase != .none
                dismissAppBubble()
                NotificationCenter.default.post(
                    name: .tagLauncherEditModeChanged,
                    object: nil,
                    userInfo: ["active": active]
                )
            }
            .onChange(of: bubbleNoteFocused) { _, focused in
                if appGridInteraction.editingBubble != nil && !focused {
                    commitBubbleNote()
                }
            }
            .onChange(of: draggedTagNames) { _, newOrder in
                rebuildDisplayGroups(apps: allApps, tagOrder: newOrder)
            }
            .onChange(of: defaultGroupName) { _, _ in
                rebuildDisplayGroups(apps: allApps, tagOrder: draggedTagNames)
            }
            .onChange(of: quickSearchQuery) { _, _ in
                quickSearchErrorMessage = nil
                refreshQuickSearchResults()
            }
            .onChange(of: appGridInteraction.pendingUncategorizedDrop != nil) { _, _ in
                publishModalInteractionState()
            }
            .onChange(of: appGridInteraction.pendingTagRemovalDrop != nil) { _, _ in
                publishModalInteractionState()
            }
            .onChange(of: appGridInteraction.proPrompt != nil) { _, _ in
                publishModalInteractionState()
            }
            .onChange(of: smartStartNotice != nil) { _, _ in
                publishModalInteractionState()
            }
            .onChange(of: proEntitlement.accessState) { _, newState in
                handleProAccessStateChange(newState)
            }
            .onDisappear {
                NotificationCenter.default.post(
                    name: .tagLauncherModalInteractionChanged,
                    object: nil,
                    userInfo: ["active": false]
                )
            }
    }
    @ViewBuilder
    private var contentStack: some View {
        ZStack {
            if shouldRenderAppGridBehindQuickSearch {
                appGridBackground
@@ -525,108 +597,6 @@
            dropWarningToastOverlay
            dropRefreshOverlay
        }
        .onAppear {
            refreshNotchHeight()
            refreshAppsForOverlay()
            if let initialQuickSearchSource, !initialQuickSearchConsumed {
                initialQuickSearchConsumed = true
                if initialQuickSearchSource == QuickSearchOpenSource.globalHidden {
                    quickSearchCloseHidesOverlay = true
                    quickSearchOnlySession = true
                }
                if !quickSearchVisible {
                    quickSearchVisible = true
                    quickSearchFocusToken &+= 1
                }
                quickSearchOpenedAt = Date()
                refreshQuickSearchResults()
                refreshAppsForQuickSearch()
                NotificationCenter.default.post(
                    name: .tagLauncherQuickSearchVisibilityChanged,
                    object: nil,
                    userInfo: [
                        "active": true,
                        "hideOverlayOnClose": quickSearchCloseHidesOverlay
                    ]
                )
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidShow)) { _ in
            resetTransientDragState()
            refreshNotchHeight()
            refreshAppsForOverlay()
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidHide)) { _ in
            resetTransientDragState()
            closeQuickSearch(notify: true, hideOverlayIfNeeded: false)
            quickSearchOnlySession = false
            quickSearchCloseHidesOverlay = false
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchRequested)) { notification in
            let source = notification.userInfo?["source"] as? String ?? QuickSearchOpenSource.mainOverlay
            openQuickSearch(source: source)
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchDismissRequested)) { notification in
            let source = notification.userInfo?["source"] as? String
            closeQuickSearch(dismissalSource: source)
        }
        .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
            guard allApps.isEmpty, !refreshInProgress else { return }
            refreshApps()
        }
        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherDataDidChange)) { _ in
            refreshApps(forceLayoutRefresh: true)
        }
        .onReceive(NotificationCenter.default.publisher(for: .appLanguageDidChange)) { _ in
            let appsSnapshot = allApps
            DispatchQueue.global(qos: .userInitiated).async {
                _ = AppleDefaultAppCatalog.relocalizeDefaultNotesForCurrentLanguage(apps: appsSnapshot)
                _ = SmartStartService.relocalizeDefaultNotesForCurrentLanguage(apps: appsSnapshot)
                DispatchQueue.main.async {
                    refreshApps(forceLayoutRefresh: true)
                }
            }
        }
        .onChange(of: editPhase) { _, newPhase in
            let active = newPhase != .none
            dismissAppBubble()
            NotificationCenter.default.post(
                name: .tagLauncherEditModeChanged,
                object: nil,
                userInfo: ["active": active]
            )
        }
        .onChange(of: bubbleNoteFocused) { _, focused in
            if appGridInteraction.editingBubble != nil && !focused {
                commitBubbleNote()
            }
        }
        .onChange(of: draggedTagNames) { _, newOrder in
            rebuildDisplayGroups(apps: allApps, tagOrder: newOrder)
        }
        .onChange(of: defaultGroupName) { _, _ in
            rebuildDisplayGroups(apps: allApps, tagOrder: draggedTagNames)
        }
        .onChange(of: quickSearchQuery) { _, _ in
            quickSearchErrorMessage = nil
            refreshQuickSearchResults()
        }
        .onChange(of: appGridInteraction.pendingUncategorizedDrop != nil) { _, _ in
            publishModalInteractionState()
        }
        .onChange(of: appGridInteraction.pendingTagRemovalDrop != nil) { _, _ in
            publishModalInteractionState()
        }
        .onChange(of: smartStartNotice != nil) { _, _ in
            publishModalInteractionState()
        }
        .onDisappear {
            NotificationCenter.default.post(
                name: .tagLauncherModalInteractionChanged,
                object: nil,
                userInfo: ["active": false]
            )
        }
    }
@@ -657,6 +627,7 @@
        uncategorizedDropConfirmOverlay
        tagRemovalDropConfirmOverlay
        usageTipsCloseReminderOverlay
        proAccessPromptOverlay
    }
    @ViewBuilder
@@ -674,6 +645,31 @@
                )
                .transition(.scale(scale: 0.96).combined(with: .opacity))
                .allowsHitTesting(false)
        }
    }
    @ViewBuilder
    private var proAccessPromptOverlay: some View {
        if let prompt = appGridInteraction.proPrompt {
            ZStack {
                Color.black.opacity(0.16)
                    .ignoresSafeArea()
                ProUpgradePromptView(
                    title: tr(prompt.feature.titleKey),
                    message: proPromptMessage(for: prompt),
                    benefitText: tr(prompt.feature.benefitKey),
                    operationText: proEntitlement.operationState.statusMessageKey.map(tr),
                    unlockTitle: tr("pro.card.unlock"),
                    restoreTitle: tr("pro.card.restore"),
                    isBusy: proEntitlement.operationState.isBusy,
                    onClose: dismissProPrompt,
                    onUnlock: { proEntitlement.purchasePro() },
                    onRestore: { proEntitlement.restorePurchases() }
                )
            }
            .transition(.opacity)
            .zIndex(920)
        }
    }
@@ -709,6 +705,7 @@
                    || appGridInteraction.pendingUncategorizedDrop != nil
                    || appGridInteraction.pendingTagRemovalDrop != nil
                    || appGridInteraction.usageTipsCloseReminderVisible
                    || appGridInteraction.proPrompt != nil
            ]
        )
    }
@@ -1291,6 +1288,8 @@
                    isEditing: editing,
                    placement: placement,
                    arrowOffset: metrics.arrowOffset,
                    noteQuotaText: editing ? noteQuotaHintText(for: context.app) : nil,
                    noteQuotaWarning: editing && isNoteQuotaWarning(for: context.app),
                    draftNote: $appGridInteraction.bubbleDraftNote,
                    noteFocused: $bubbleNoteFocused,
                    onCommit: commitBubbleNote,
@@ -2088,10 +2087,18 @@
    private func commitBubbleNote() {
        guard let context = appGridInteraction.editingBubble else { return }
        guard appGridInteraction.proPrompt == nil else { return }
        let limited = String(appGridInteraction.bubbleDraftNote.prefix(TagDatabase.maxAppNoteLength))
            .trimmingCharacters(in: .whitespacesAndNewlines)
        TagEditor.setAppNote(limited, for: context.app.path.path)
        let decision = TagEditor.setAppNote(limited, for: context.app.path.path)
        appGridInteraction.bubbleDraftNote = limited
        guard case .allow = decision else {
            if case .blocked(let status) = decision {
                presentProPrompt(for: .unlimitedNotes, noteQuotaStatus: status)
            }
            bubbleNoteFocused = false
            return
        }
        appGridInteraction.editingBubble = nil
        bubbleNoteFocused = false
        notifyAppNoteEditing(active: false)
@@ -2135,6 +2142,159 @@
        return app.note ?? ""
    }
    private func noteQuotaStatus(for app: AppInfo) -> ProNoteQuotaStatus? {
        guard !proEntitlement.isUnlocked else { return nil }
        let decision = ProEntitlementPolicy.noteSaveDecision(
            note: appGridInteraction.bubbleDraftNote,
            for: app.path.path
        )
        switch decision {
        case .allow(let status), .blocked(let status):
            return status
        }
    }
    private func noteQuotaHintText(for app: AppInfo) -> String? {
        guard let status = noteQuotaStatus(for: app), !status.isUnlimited else { return nil }
        if status.remaining <= 0 {
            return tr("pro.notes.limitReached")
        }
        return tr(
            "pro.notes.remaining",
            replacements: [
                "%remaining%": "\(status.remaining)",
                "%limit%": "\(status.limit)"
            ]
        )
    }
    private func isNoteQuotaWarning(for app: AppInfo) -> Bool {
        guard let status = noteQuotaStatus(for: app), !status.isUnlimited else { return false }
        return status.remaining <= 0
    }
    private func proPromptMessage(for prompt: PendingProPrompt) -> String {
        if prompt.feature == .unlimitedNotes,
           let status = prompt.noteQuotaStatus,
           !status.isUnlimited,
           status.remaining <= 0 {
            return tr("pro.notes.limitReached")
        }
        return tr(prompt.feature.promptMessageKey)
    }
    private func presentProPrompt(for feature: ProFeature, noteQuotaStatus: ProNoteQuotaStatus? = nil) {
        proEntitlement.clearTransientOperationState()
        withAnimation(.spring(response: 0.22, dampingFraction: 0.84)) {
            appGridInteraction.proPrompt = PendingProPrompt(
                feature: feature,
                noteQuotaStatus: noteQuotaStatus
            )
        }
    }
    private func dismissProPrompt() {
        proEntitlement.clearTransientOperationState()
        withAnimation(.easeOut(duration: 0.18)) {
            appGridInteraction.proPrompt = nil
        }
    }
    private func handleProAccessStateChange(_ newState: ProAccessState) {
        guard newState.isUnlocked else { return }
        if let preview = proEntitlement.themePreviewState {
            appGridThemeID = preview.theme.rawValue
            proEntitlement.stopThemePreview()
        }
        let hadPrompt = appGridInteraction.proPrompt != nil
        if hadPrompt {
            dismissProPrompt()
        }
        if appGridInteraction.editingBubble != nil {
            DispatchQueue.main.async {
                bubbleNoteFocused = true
            }
        }
    }
    private func handleContentAppear() {
        refreshNotchHeight()
        refreshAppsForOverlay()
        guard let initialQuickSearchSource, !initialQuickSearchConsumed else { return }
        initialQuickSearchConsumed = true
        if initialQuickSearchSource == QuickSearchOpenSource.globalHidden {
            quickSearchCloseHidesOverlay = true
            quickSearchOnlySession = true
        }
        if !quickSearchVisible {
            quickSearchVisible = true
            quickSearchFocusToken &+= 1
        }
        quickSearchOpenedAt = Date()
        refreshQuickSearchResults()
        refreshAppsForQuickSearch()
        NotificationCenter.default.post(
            name: .tagLauncherQuickSearchVisibilityChanged,
            object: nil,
            userInfo: [
                "active": true,
                "hideOverlayOnClose": quickSearchCloseHidesOverlay
            ]
        )
    }
    private func handleOverlayDidShow(_ notification: Notification) {
        _ = notification
        resetTransientDragState()
        refreshNotchHeight()
        refreshAppsForOverlay()
    }
    private func handleOverlayDidHide(_ notification: Notification) {
        _ = notification
        resetTransientDragState()
        closeQuickSearch(notify: true, hideOverlayIfNeeded: false)
        quickSearchOnlySession = false
        quickSearchCloseHidesOverlay = false
    }
    private func handleQuickSearchRequested(_ notification: Notification) {
        let source = notification.userInfo?["source"] as? String ?? QuickSearchOpenSource.mainOverlay
        openQuickSearch(source: source)
    }
    private func handleQuickSearchDismissRequested(_ notification: Notification) {
        let source = notification.userInfo?["source"] as? String
        closeQuickSearch(dismissalSource: source)
    }
    private func handleApplicationDidBecomeActive(_ notification: Notification) {
        _ = notification
        guard allApps.isEmpty, !refreshInProgress else { return }
        refreshApps()
    }
    private func handleDataDidChange(_ notification: Notification) {
        _ = notification
        refreshApps(forceLayoutRefresh: true)
    }
    private func handleAppLanguageDidChange(_ notification: Notification) {
        _ = notification
        let appsSnapshot = allApps
        DispatchQueue.global(qos: .userInitiated).async {
            _ = AppleDefaultAppCatalog.relocalizeDefaultNotesForCurrentLanguage(apps: appsSnapshot)
            _ = SmartStartService.relocalizeDefaultNotesForCurrentLanguage(apps: appsSnapshot)
            DispatchQueue.main.async {
                refreshApps(forceLayoutRefresh: true)
            }
        }
    }
    private func bubblePlacement(for frame: CGRect, rootFrame: CGRect) -> BubblePlacement {
        frame.minY < 170 ? .below : .above
    }
@@ -2170,7 +2330,8 @@
    private func estimatedBubbleHeight(for app: AppInfo, width: CGFloat, isEditing: Bool) -> CGFloat {
        if isEditing {
            return 120
            let hint = noteQuotaHintText(for: app)
            return hint == nil ? 124 : 148
        }
        let note = currentNote(for: app).trimmingCharacters(in: .whitespacesAndNewlines)
        guard !note.isEmpty else { return 68 }
@@ -2395,6 +2556,10 @@
        containerAppOrder[key] = paths
        rebuildDisplayGroups(apps: allApps, tagOrder: draggedTagNames)
        guard proEntitlement.isUnlocked else {
            showDropWarning(message: tr("pro.sorting.previewToast"))
            return
        }
        TagEditor.reorderApps(inContainer: key, orderedPaths: paths)
        refreshApps(forceLayoutRefresh: true)
    }
@@ -2615,9 +2780,9 @@
        return !protectedNames.contains(tag) && tagColors[tag] != nil
    }
    private func showDropWarning() {
    private func showDropWarning(message: String = tr("drop.systemDefaultWarning")) {
        withAnimation(.spring(response: 0.24, dampingFraction: 0.82)) {
            appGridInteraction.dropWarningToast = tr("drop.systemDefaultWarning")
            appGridInteraction.dropWarningToast = message
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.6) {
            withAnimation(.easeOut(duration: 0.18)) {
src/Apptag/DataLayer.swift
@@ -1551,12 +1551,14 @@
    // MARK: Export / Import
    static func exportTo(_ url: URL) throws {
        try ProEntitlementPolicy.requireUnlocked(.layoutExport)
        let store = loadWithEnsuredCategoryScheme()
        let data = try JSONEncoder().encode(store)
        try data.write(to: url, options: .atomic)
    }
    static func importFrom(_ url: URL) throws -> Store {
        try ProEntitlementPolicy.requireUnlocked(.layoutImport)
        let previousStore = loadWithEnsuredCategoryScheme()
        let data = try Data(contentsOf: url)
        var store = try JSONDecoder().decode(Store.self, from: data)
@@ -1808,6 +1810,8 @@
    /// Persist the visible app order for one stable container.
    static func reorderApps(inContainer containerID: String, orderedPaths: [String]) {
        guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return }
        var store = TagDatabase.load()
        _ = TagDatabase.normalizeContainerAppOrder(in: &store)
        let previousStore = store
@@ -2137,11 +2141,22 @@
        TagDatabase.save(store)
    }
    static func setAppNote(_ note: String, for path: String) {
    @discardableResult
    static func setAppNote(_ note: String, for path: String) -> ProNoteSaveDecision {
        var store = TagDatabase.load()
        let previousStore = store
        let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)
        let limited = String(trimmed.prefix(TagDatabase.maxAppNoteLength))
        let decision = ProEntitlementPolicy.noteSaveDecision(
            note: limited,
            for: path,
            in: store
        )
        guard case .allow = decision else {
            return decision
        }
        let previousStore = store
        if limited.isEmpty {
            store.appNotes.removeValue(forKey: path)
            store.appNoteMetadata[path] = TagDatabase.AppNoteMetadata(
@@ -2167,6 +2182,7 @@
            previous: previousStore,
            reason: "edit-app-note"
        )
        return decision
    }
    static func moveApp(path: String, from sourceTag: String, to targetTag: String, color: Int, copy: Bool) {
src/Apptag/Info.plist
@@ -19,9 +19,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>8.0.6</string>
    <string>8.1.0</string>
    <key>CFBundleVersion</key>
    <string>20260625.1942</string>
    <string>20260626.1726</string>
    <key>LSApplicationCategoryType</key>
    <string>public.app-category.utilities</string>
    <key>LSMinimumSystemVersion</key>
src/Apptag/L10n.swift
@@ -165,6 +165,14 @@
    L10n.current[key] ?? key
}
func tr(_ key: String, replacements: [String: String]) -> String {
    var value = tr(key)
    for (token, replacement) in replacements {
        value = value.replacingOccurrences(of: token, with: replacement)
    }
    return value
}
enum HelpDocument {
    private static let baseURL = "https://github.com/shanghai3168/taglauncher/releases/download/v7.6.0"
src/Apptag/Localization/ar-Najdi.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "إظهار التلميحات بعدين",
  "usageTips.closeReminder.message": "تقدر ترجع تعرض التلميحات من الإعدادات > عام بإلغاء تحديد “إخفاء نصائح الاستخدام”.",
  "usageTips.closeReminder.doNotRemind": "تم. لا تذكرني مرة ثانية إذا سكرت التلميحات.",
  "usageTips.closeReminder.previewCallout": "شِلّ التحديد هنا عشان ترجع التلميحات"
  "usageTips.closeReminder.previewCallout": "شِلّ التحديد هنا عشان ترجع التلميحات",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "معاينة",
  "pro.card.restore": "استعادة المشتريات",
  "pro.card.summary.free": "افتح السمات المميزة، واستيراد التخطيط وتصديره، وملاحظات غير محدودة، وفرز التطبيقات اليدوية المحفوظة.",
  "pro.card.summary.legacy": "شكرًا لدعم TagLauncher مبكرًا. وصول Pro لديك نشط بالفعل.",
  "pro.card.summary.unlocked": "Pro نشط على جهاز Mac هذا. السمات المميزة وأدوات التخطيط المتقدمة جاهزة.",
  "pro.card.title.free": "الترقية إلى Pro",
  "pro.card.title.legacy": "مقفلة للمحترفين",
  "pro.card.title.unlocked": "مقفلة للمحترفين",
  "pro.card.unlock": "فتح برو",
  "pro.feature.export": "تصدير التخطيط",
  "pro.feature.export.benefit": "احفظ علاماتك الحالية وترتيبك وتخطيطك كنسخة احتياطية قابلة لإعادة الاستخدام.",
  "pro.feature.import": "استيراد التخطيط",
  "pro.feature.import.benefit": "قم باستعادة تخطيط TagLauncher المحفوظ على جهاز Mac هذا في خطوة واحدة.",
  "pro.feature.notes": "ملاحظات غير محدودة",
  "pro.feature.notes.benefit": "احفظ الملاحظات لكل تطبيق دون الحد المجاني المكون من 5 ملاحظات.",
  "pro.feature.sorting": "فرز التطبيقات المحفوظة",
  "pro.feature.sorting.benefit": "احتفظ بترتيب التطبيق اليدوي بعد مغادرة وضع التحرير.",
  "pro.feature.themes": "مواضيع متميزة",
  "pro.feature.themes.benefit": "افتح جميع سمات App Grid المتقدمة واحتفظ بها بشكل دائم.",
  "pro.notes.limitReached": "يمكن للإصدار المجاني حفظ الملاحظات لما يصل إلى 5 تطبيقات. فتح Pro لحفظ الملاحظات غير المحدودة.",
  "pro.notes.remaining": "يتضمن الإصدار المجاني %limit% ملاحظات للتطبيق. %remaining% المتبقية.",
  "pro.operation.cancelled": "تم إلغاء الشراء.",
  "pro.operation.loading": "جارٍ التحقق من الوصول الاحترافي…",
  "pro.operation.pending": "في انتظار تأكيد متجر التطبيقات...",
  "pro.operation.purchasing": "شراء برو…",
  "pro.operation.restoreNotFound": "لم يتم العثور على عملية شراء Pro سابقة لمعرف Apple هذا.",
  "pro.operation.restoring": "جارٍ استعادة عملية الشراء...",
  "pro.prompt.export": "تصدير التخطيط هو ميزة احترافية. قم بإلغاء تأمين Pro لعمل نسخة احتياطية من العلامات والطلب والتخطيط الحالي.",
  "pro.prompt.import": "يعد استيراد التخطيط ميزة احترافية. افتح Pro لاستعادة التخطيط المحفوظ على جهاز Mac هذا.",
  "pro.prompt.notes": "تعد ملاحظات التطبيق غير المحدودة جزءًا من Pro. فتح Pro لحفظ الملاحظات لكل تطبيق.",
  "pro.prompt.sorting": "يعد الفرز اليدوي للتطبيقات المحفوظة جزءًا من Pro. فتح Pro للحفاظ على طلبك المخصص.",
  "pro.prompt.themes": "هذا الموضوع هو جزء من Pro. افتح قفل Pro للاستمرار في استخدام سمات App Grid المتقدمة.",
  "pro.purchase.failed": "غير قادر على إكمال عملية الشراء الآن. يرجى المحاولة مرة أخرى.",
  "pro.purchase.unavailable": "الشراء الاحترافي غير متوفر الآن. يرجى المحاولة مرة أخرى لاحقا.",
  "pro.purchase.verificationFailed": "فشل التحقق من متجر التطبيقات. يرجى المحاولة مرة أخرى.",
  "pro.restore.failed": "غير قادر على استعادة المشتريات الآن. يرجى المحاولة مرة أخرى.",
  "pro.sorting.previewToast": "معاينة فقط. فتح Pro لحفظ الفرز اليدوي للتطبيقات.",
  "pro.status.free": "حر",
  "pro.status.legacyUnlocked": "برو · مؤيد في وقت مبكر",
  "pro.status.unlocked": "مقفلة للمحترفين",
  "pro.theme.preview.status": "معاينة موضوع برو",
  "pro.theme.status.free": "المواضيع المجانية جاهزة الآن. فتح Pro للحفاظ على كل موضوع متقدم.",
  "pro.theme.status.preview": "معاينة موضوع برو. قم بإلغاء تأمين Pro للاحتفاظ به بعد انتهاء المعاينة.",
  "pro.theme.status.unlocked": "تم فتح جميع السمات على جهاز Mac هذا."
}
src/Apptag/Localization/ar.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "إظهار التلميحات لاحقًا",
  "usageTips.closeReminder.message": "يمكنك إظهار التلميحات مرة أخرى من الإعدادات > عام عبر إلغاء تحديد “إخفاء نصائح الاستخدام”.",
  "usageTips.closeReminder.doNotRemind": "فهمت. لا تذكرني مرة أخرى عند إغلاق التلميحات.",
  "usageTips.closeReminder.previewCallout": "ألغِ تحديد هذا الخيار لإظهار التلميحات مجددًا"
  "usageTips.closeReminder.previewCallout": "ألغِ تحديد هذا الخيار لإظهار التلميحات مجددًا",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "معاينة",
  "pro.card.restore": "استعادة المشتريات",
  "pro.card.summary.free": "افتح السمات المميزة، واستيراد التخطيط وتصديره، وملاحظات غير محدودة، وفرز التطبيقات اليدوية المحفوظة.",
  "pro.card.summary.legacy": "شكرًا لدعم TagLauncher مبكرًا. وصول Pro لديك نشط بالفعل.",
  "pro.card.summary.unlocked": "Pro نشط على جهاز Mac هذا. السمات المميزة وأدوات التخطيط المتقدمة جاهزة.",
  "pro.card.title.free": "الترقية إلى Pro",
  "pro.card.title.legacy": "مقفلة للمحترفين",
  "pro.card.title.unlocked": "مقفلة للمحترفين",
  "pro.card.unlock": "فتح برو",
  "pro.feature.export": "تصدير التخطيط",
  "pro.feature.export.benefit": "احفظ علاماتك الحالية وترتيبك وتخطيطك كنسخة احتياطية قابلة لإعادة الاستخدام.",
  "pro.feature.import": "استيراد التخطيط",
  "pro.feature.import.benefit": "قم باستعادة تخطيط TagLauncher المحفوظ على جهاز Mac هذا في خطوة واحدة.",
  "pro.feature.notes": "ملاحظات غير محدودة",
  "pro.feature.notes.benefit": "احفظ الملاحظات لكل تطبيق دون الحد المجاني المكون من 5 ملاحظات.",
  "pro.feature.sorting": "فرز التطبيقات المحفوظة",
  "pro.feature.sorting.benefit": "احتفظ بترتيب التطبيق اليدوي بعد مغادرة وضع التحرير.",
  "pro.feature.themes": "مواضيع متميزة",
  "pro.feature.themes.benefit": "افتح جميع سمات App Grid المتقدمة واحتفظ بها بشكل دائم.",
  "pro.notes.limitReached": "يمكن للإصدار المجاني حفظ الملاحظات لما يصل إلى 5 تطبيقات. فتح Pro لحفظ الملاحظات غير المحدودة.",
  "pro.notes.remaining": "يتضمن الإصدار المجاني %limit% ملاحظات للتطبيق. %remaining% المتبقية.",
  "pro.operation.cancelled": "تم إلغاء الشراء.",
  "pro.operation.loading": "جارٍ التحقق من الوصول الاحترافي…",
  "pro.operation.pending": "في انتظار تأكيد متجر التطبيقات...",
  "pro.operation.purchasing": "شراء برو…",
  "pro.operation.restoreNotFound": "لم يتم العثور على عملية شراء Pro سابقة لمعرف Apple هذا.",
  "pro.operation.restoring": "جارٍ استعادة عملية الشراء...",
  "pro.prompt.export": "تصدير التخطيط هو ميزة احترافية. قم بإلغاء تأمين Pro لعمل نسخة احتياطية من العلامات والطلب والتخطيط الحالي.",
  "pro.prompt.import": "يعد استيراد التخطيط ميزة احترافية. افتح Pro لاستعادة التخطيط المحفوظ على جهاز Mac هذا.",
  "pro.prompt.notes": "تعد ملاحظات التطبيق غير المحدودة جزءًا من Pro. فتح Pro لحفظ الملاحظات لكل تطبيق.",
  "pro.prompt.sorting": "يعد الفرز اليدوي للتطبيقات المحفوظة جزءًا من Pro. فتح Pro للحفاظ على طلبك المخصص.",
  "pro.prompt.themes": "هذا الموضوع هو جزء من Pro. افتح قفل Pro للاستمرار في استخدام سمات App Grid المتقدمة.",
  "pro.purchase.failed": "غير قادر على إكمال عملية الشراء الآن. يرجى المحاولة مرة أخرى.",
  "pro.purchase.unavailable": "الشراء الاحترافي غير متوفر الآن. يرجى المحاولة مرة أخرى لاحقا.",
  "pro.purchase.verificationFailed": "فشل التحقق من متجر التطبيقات. يرجى المحاولة مرة أخرى.",
  "pro.restore.failed": "غير قادر على استعادة المشتريات الآن. يرجى المحاولة مرة أخرى.",
  "pro.sorting.previewToast": "معاينة فقط. فتح Pro لحفظ الفرز اليدوي للتطبيقات.",
  "pro.status.free": "حر",
  "pro.status.legacyUnlocked": "برو · مؤيد في وقت مبكر",
  "pro.status.unlocked": "مقفلة للمحترفين",
  "pro.theme.preview.status": "معاينة موضوع برو",
  "pro.theme.status.free": "المواضيع المجانية جاهزة الآن. فتح Pro للحفاظ على كل موضوع متقدم.",
  "pro.theme.status.preview": "معاينة موضوع برو. قم بإلغاء تأمين Pro للاحتفاظ به بعد انتهاء المعاينة.",
  "pro.theme.status.unlocked": "تم فتح جميع السمات على جهاز Mac هذا."
}
src/Apptag/Localization/cs.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Zobrazit tipy později znovu",
  "usageTips.closeReminder.message": "Tipy můžete znovu zobrazit v Nastavení > Obecné zrušením volby „Skrýt tipy k používání“.",
  "usageTips.closeReminder.doNotRemind": "Rozumím. Při příštím zavření tipů už nepřipomínat.",
  "usageTips.closeReminder.previewCallout": "Zrušte tuto volbu a tipy se znovu zobrazí"
  "usageTips.closeReminder.previewCallout": "Zrušte tuto volbu a tipy se znovu zobrazí",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Náhled",
  "pro.card.restore": "Obnovit nákupy",
  "pro.card.summary.free": "Odemkněte prémiová témata, import a export rozložení, neomezený počet poznámek a uložené ruční řazení aplikací.",
  "pro.card.summary.legacy": "Děkujeme za včasnou podporu TagLauncher. Váš přístup Pro je již aktivní.",
  "pro.card.summary.unlocked": "Pro je na tomto Macu aktivní. Prémiové motivy a pokročilé nástroje pro rozvržení jsou připraveny.",
  "pro.card.title.free": "Upgradujte na Pro",
  "pro.card.title.legacy": "Pro odemčeno",
  "pro.card.title.unlocked": "Pro odemčeno",
  "pro.card.unlock": "Odemknout Pro",
  "pro.feature.export": "Export rozložení",
  "pro.feature.export.benefit": "Uložte své aktuální značky, pořadí a rozvržení jako opakovaně použitelnou zálohu.",
  "pro.feature.import": "Import rozložení",
  "pro.feature.import.benefit": "Obnovte uložené rozložení TagLauncher na tomto Macu v jednom kroku.",
  "pro.feature.notes": "Neomezené poznámky",
  "pro.feature.notes.benefit": "Ukládejte poznámky pro každou aplikaci bez bezplatného limitu 5 poznámek.",
  "pro.feature.sorting": "Uložené řazení aplikací",
  "pro.feature.sorting.benefit": "Po opuštění režimu úprav si ponechte ruční objednávku aplikací.",
  "pro.feature.themes": "Prémiová témata",
  "pro.feature.themes.benefit": "Odemkněte všechna pokročilá témata App Grid a ponechte si je trvale.",
  "pro.notes.limitReached": "Bezplatná verze dokáže uložit poznámky až pro 5 aplikací. Odemkněte Pro a uložte neomezené poznámky.",
  "pro.notes.remaining": "Bezplatná verze obsahuje %limit% poznámek k aplikaci. %remaining% zbývající.",
  "pro.operation.cancelled": "Nákup zrušen.",
  "pro.operation.loading": "Kontrola přístupu Pro…",
  "pro.operation.pending": "Čekání na potvrzení App Store…",
  "pro.operation.purchasing": "Nákup Pro…",
  "pro.operation.restoreNotFound": "Pro toto Apple ID nebyl nalezen žádný předchozí nákup Pro.",
  "pro.operation.restoring": "Obnovování nákupu…",
  "pro.prompt.export": "Export rozvržení je funkce Pro. Odemkněte Pro, abyste mohli zálohovat své aktuální značky, pořadí a rozvržení.",
  "pro.prompt.import": "Import rozvržení je funkce Pro. Chcete-li obnovit uložené rozvržení na tomto Macu, odemkněte Pro.",
  "pro.prompt.notes": "Neomezené poznámky k aplikaci jsou součástí Pro. Odemkněte Pro a uložte si poznámky pro každou aplikaci.",
  "pro.prompt.sorting": "Uložené ruční řazení aplikací je součástí Pro. Odemkněte Pro, abyste si zachovali vlastní pořadí.",
  "pro.prompt.themes": "Toto téma je součástí Pro. Odemkněte Pro, abyste mohli nadále používat pokročilé motivy App Grid.",
  "pro.purchase.failed": "Nákup nyní nelze dokončit. Zkuste to prosím znovu.",
  "pro.purchase.unavailable": "Pro nákup je momentálně nedostupný. Zkuste to znovu později.",
  "pro.purchase.verificationFailed": "Ověření App Store se nezdařilo. Zkuste to prosím znovu.",
  "pro.restore.failed": "Nákupy nyní nelze obnovit. Zkuste to prosím znovu.",
  "pro.sorting.previewToast": "Pouze náhled. Odemkněte Pro, abyste si uložili ruční řazení aplikací.",
  "pro.status.free": "Uvolnit",
  "pro.status.legacyUnlocked": "Pro · První podporovatel",
  "pro.status.unlocked": "Pro odemčeno",
  "pro.theme.preview.status": "Náhled Pro téma",
  "pro.theme.status.free": "Volná témata jsou nyní připravena. Odemkněte Pro, abyste zachovali každé pokročilé téma.",
  "pro.theme.status.preview": "Zobrazení náhledu motivu Pro. Odemkněte Pro, abyste si jej ponechali i po skončení náhledu.",
  "pro.theme.status.unlocked": "Všechna témata jsou na tomto Macu odemčená."
}
src/Apptag/Localization/da.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Vis tips igen senere",
  "usageTips.closeReminder.message": "Du kan vise tips igen i Indstillinger > Generelt ved at fjerne markeringen af “Skjul brugstips”.",
  "usageTips.closeReminder.doNotRemind": "Forstået. Mind mig ikke om det igen, når jeg lukker tips.",
  "usageTips.closeReminder.previewCallout": "Fjern markeringen for at vise tips igen"
  "usageTips.closeReminder.previewCallout": "Fjern markeringen for at vise tips igen",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Forhåndsvisning",
  "pro.card.restore": "Gendan køb",
  "pro.card.summary.free": "Lås op for premium-temaer, layout-import og -eksport, ubegrænset antal noter og gemt manuel app-sortering.",
  "pro.card.summary.legacy": "Tak for at støtte TagLauncher tidligt. Din Pro-adgang er allerede aktiv.",
  "pro.card.summary.unlocked": "Pro er aktiv på denne Mac. Premium-temaer og avancerede layoutværktøjer er klar.",
  "pro.card.title.free": "Opgrader til Pro",
  "pro.card.title.legacy": "Pro låst op",
  "pro.card.title.unlocked": "Pro låst op",
  "pro.card.unlock": "Lås Pro op",
  "pro.feature.export": "Layout eksport",
  "pro.feature.export.benefit": "Gem dine nuværende tags, ordre og layout som en genbrugelig backup.",
  "pro.feature.import": "Import af layout",
  "pro.feature.import.benefit": "Gendan et gemt TagLauncher-layout på denne Mac i ét trin.",
  "pro.feature.notes": "Ubegrænsede noter",
  "pro.feature.notes.benefit": "Gem noter til hver app uden den gratis grænse på 5 noter.",
  "pro.feature.sorting": "Gemt app-sortering",
  "pro.feature.sorting.benefit": "Behold din manuelle app-rækkefølge, når du forlader redigeringstilstand.",
  "pro.feature.themes": "Premium temaer",
  "pro.feature.themes.benefit": "Lås op for alle avancerede App Grid-temaer, og behold dem permanent.",
  "pro.notes.limitReached": "Gratis version kan gemme noter til op til 5 apps. Lås Pro op for at gemme ubegrænsede noter.",
  "pro.notes.remaining": "Gratis version inkluderer %limit% app noter. %remaining% tilbage.",
  "pro.operation.cancelled": "Køb annulleret.",
  "pro.operation.loading": "Kontrollerer Pro-adgang...",
  "pro.operation.pending": "Venter på App Store-bekræftelse...",
  "pro.operation.purchasing": "Køber Pro...",
  "pro.operation.restoreNotFound": "Der blev ikke fundet noget tidligere Pro-køb for dette Apple ID.",
  "pro.operation.restoring": "Gendanner køb...",
  "pro.prompt.export": "Layouteksport er en Pro-funktion. Lås Pro op for at sikkerhedskopiere dine nuværende tags, ordre og layout.",
  "pro.prompt.import": "Layoutimport er en Pro-funktion. Lås Pro op for at gendanne et gemt layout på denne Mac.",
  "pro.prompt.notes": "Ubegrænsede app-noter er en del af Pro. Lås Pro op for at gemme noter til hver app.",
  "pro.prompt.sorting": "Gemt manuel app-sortering er en del af Pro. Lås Pro op for at bevare din tilpassede rækkefølge.",
  "pro.prompt.themes": "Dette tema er en del af Pro. Lås Pro op for at fortsætte med at bruge avancerede App Grid-temaer.",
  "pro.purchase.failed": "Kan ikke gennemføre købet lige nu. Prøv venligst igen.",
  "pro.purchase.unavailable": "Pro-køb er ikke tilgængelig lige nu. Prøv venligst igen senere.",
  "pro.purchase.verificationFailed": "App Store-bekræftelse mislykkedes. Prøv venligst igen.",
  "pro.restore.failed": "Kan ikke gendanne køb lige nu. Prøv venligst igen.",
  "pro.sorting.previewToast": "Kun forhåndsvisning. Lås Pro op for at gemme manuel app-sortering.",
  "pro.status.free": "Gratis",
  "pro.status.legacyUnlocked": "Pro · Tidlig supporter",
  "pro.status.unlocked": "Pro låst op",
  "pro.theme.preview.status": "Forhåndsvisning af Pro-tema",
  "pro.theme.status.free": "Gratis temaer er klar nu. Lås Pro op for at beholde alle avancerede temaer.",
  "pro.theme.status.preview": "Forhåndsvisning af et Pro-tema. Lås Pro op for at beholde den, når forhåndsvisningen slutter.",
  "pro.theme.status.unlocked": "Alle temaer er låst op på denne Mac."
}
src/Apptag/Localization/de.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Tipps später wieder anzeigen",
  "usageTips.closeReminder.message": "Du kannst die Tipps später unter Einstellungen > Allgemein wieder anzeigen, indem du „Nutzungstipps ausblenden“ deaktivierst.",
  "usageTips.closeReminder.doNotRemind": "Verstanden. Beim Schließen der Tipps nicht mehr erinnern.",
  "usageTips.closeReminder.previewCallout": "Deaktiviere diese Option, um Tipps wieder anzuzeigen"
  "usageTips.closeReminder.previewCallout": "Deaktiviere diese Option, um Tipps wieder anzuzeigen",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Vorschau",
  "pro.card.restore": "Einkäufe wiederherstellen",
  "pro.card.summary.free": "Schalten Sie Premium-Themen, Layout-Import und -Export, unbegrenzte Notizen und gespeicherte manuelle App-Sortierung frei.",
  "pro.card.summary.legacy": "Vielen Dank, dass Sie TagLauncher frühzeitig unterstützt haben. Ihr Pro-Zugang ist bereits aktiv.",
  "pro.card.summary.unlocked": "Pro ist auf diesem Mac aktiv. Premium-Themen und erweiterte Layout-Tools stehen bereit.",
  "pro.card.title.free": "Auf Pro upgraden",
  "pro.card.title.legacy": "Pro freigeschaltet",
  "pro.card.title.unlocked": "Pro freigeschaltet",
  "pro.card.unlock": "Schalte Pro frei",
  "pro.feature.export": "Layout-Export",
  "pro.feature.export.benefit": "Speichern Sie Ihre aktuellen Tags, Reihenfolge und Layout als wiederverwendbares Backup.",
  "pro.feature.import": "Layout-Import",
  "pro.feature.import.benefit": "Stellen Sie in einem Schritt ein gespeichertes TagLauncher-Layout auf diesem Mac wieder her.",
  "pro.feature.notes": "Unbegrenzte Notizen",
  "pro.feature.notes.benefit": "Speichern Sie Notizen für jede App ohne die kostenlose Beschränkung auf 5 Notizen.",
  "pro.feature.sorting": "Gespeicherte App-Sortierung",
  "pro.feature.sorting.benefit": "Behalten Sie Ihre manuelle App-Reihenfolge bei, nachdem Sie den Bearbeitungsmodus verlassen haben.",
  "pro.feature.themes": "Premium-Themen",
  "pro.feature.themes.benefit": "Schalten Sie alle erweiterten App Grid-Designs frei und behalten Sie sie dauerhaft.",
  "pro.notes.limitReached": "In der kostenlosen Version können Notizen für bis zu 5 Apps gespeichert werden. Schalten Sie Pro frei, um unbegrenzt Notizen zu speichern.",
  "pro.notes.remaining": "Die kostenlose Version enthält %limit% App-Notizen. %remaining% übrig.",
  "pro.operation.cancelled": "Kauf storniert.",
  "pro.operation.loading": "Pro-Zugriff wird überprüft…",
  "pro.operation.pending": "Warten auf Bestätigung aus dem App Store…",
  "pro.operation.purchasing": "Einkaufspro…",
  "pro.operation.restoreNotFound": "Für diese Apple-ID wurde kein früherer Pro-Kauf gefunden.",
  "pro.operation.restoring": "Kauf wird wiederhergestellt…",
  "pro.prompt.export": "Der Layout-Export ist eine Pro-Funktion. Schalten Sie Pro frei, um Ihre aktuellen Tags, Reihenfolge und Layout zu sichern.",
  "pro.prompt.import": "Der Layoutimport ist eine Pro-Funktion. Entsperren Sie Pro, um ein gespeichertes Layout auf diesem Mac wiederherzustellen.",
  "pro.prompt.notes": "Unbegrenzte App-Notizen sind Teil von Pro. Schalten Sie Pro frei, um Notizen für jede App zu speichern.",
  "pro.prompt.sorting": "Die gespeicherte manuelle App-Sortierung ist Teil von Pro. Schalten Sie Pro frei, um Ihre individuelle Bestellung beizubehalten.",
  "pro.prompt.themes": "Dieses Thema ist Teil von Pro. Schalten Sie Pro frei, um weiterhin erweiterte App Grid-Designs zu verwenden.",
  "pro.purchase.failed": "Der Kauf kann derzeit nicht abgeschlossen werden. Bitte versuchen Sie es erneut.",
  "pro.purchase.unavailable": "Der Pro-Kauf ist derzeit nicht verfügbar. Bitte versuchen Sie es später noch einmal.",
  "pro.purchase.verificationFailed": "Die App Store-Überprüfung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
  "pro.restore.failed": "Einkäufe können derzeit nicht wiederhergestellt werden. Bitte versuchen Sie es erneut.",
  "pro.sorting.previewToast": "Nur Vorschau. Entsperren Sie Pro, um die manuelle App-Sortierung zu sparen.",
  "pro.status.free": "Frei",
  "pro.status.legacyUnlocked": "Pro · Früher Unterstützer",
  "pro.status.unlocked": "Pro freigeschaltet",
  "pro.theme.preview.status": "Vorschau des Pro-Themes",
  "pro.theme.status.free": "Kostenlose Themes sind jetzt fertig. Schalten Sie Pro frei, um alle erweiterten Themes beizubehalten.",
  "pro.theme.status.preview": "Vorschau eines Pro-Themes. Schalten Sie Pro frei, um es nach Ende der Vorschau zu behalten.",
  "pro.theme.status.unlocked": "Alle Themes sind auf diesem Mac freigeschaltet."
}
src/Apptag/Localization/en.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Show tips again later",
  "usageTips.closeReminder.message": "You can show tips again later in Settings > General by clearing “Hide usage tips”.",
  "usageTips.closeReminder.doNotRemind": "Got it. Don’t remind me next time I close tips.",
  "usageTips.closeReminder.previewCallout": "Clear this checkbox to show tips again"
  "usageTips.closeReminder.previewCallout": "Clear this checkbox to show tips again",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Preview",
  "pro.card.restore": "Restore Purchases",
  "pro.card.summary.free": "Unlock premium themes, layout import and export, unlimited notes, and saved manual app sorting.",
  "pro.card.summary.legacy": "Thanks for supporting TagLauncher early. Your Pro access is already active.",
  "pro.card.summary.unlocked": "Pro is active on this Mac. Premium themes and advanced layout tools are ready.",
  "pro.card.title.free": "Upgrade to Pro",
  "pro.card.title.legacy": "Pro unlocked",
  "pro.card.title.unlocked": "Pro unlocked",
  "pro.card.unlock": "Unlock Pro",
  "pro.feature.export": "Layout export",
  "pro.feature.export.benefit": "Save your current tags, order, and layout as a reusable backup.",
  "pro.feature.import": "Layout import",
  "pro.feature.import.benefit": "Restore a saved TagLauncher layout on this Mac in one step.",
  "pro.feature.notes": "Unlimited notes",
  "pro.feature.notes.benefit": "Save notes for every app without the free 5-note limit.",
  "pro.feature.sorting": "Saved app sorting",
  "pro.feature.sorting.benefit": "Keep your manual app order after you leave editing mode.",
  "pro.feature.themes": "Premium themes",
  "pro.feature.themes.benefit": "Unlock every advanced App Grid theme and keep it permanently.",
  "pro.notes.limitReached": "Free version can save notes for up to 5 apps. Unlock Pro to save unlimited notes.",
  "pro.notes.remaining": "Free version includes %limit% app notes. %remaining% remaining.",
  "pro.operation.cancelled": "Purchase cancelled.",
  "pro.operation.loading": "Checking Pro access…",
  "pro.operation.pending": "Waiting for App Store confirmation…",
  "pro.operation.purchasing": "Purchasing Pro…",
  "pro.operation.restoreNotFound": "No previous Pro purchase was found for this Apple ID.",
  "pro.operation.restoring": "Restoring purchase…",
  "pro.prompt.export": "Layout export is a Pro feature. Unlock Pro to back up your current tags, order, and layout.",
  "pro.prompt.import": "Layout import is a Pro feature. Unlock Pro to restore a saved layout on this Mac.",
  "pro.prompt.notes": "Unlimited app notes are part of Pro. Unlock Pro to save notes for every app.",
  "pro.prompt.sorting": "Saved manual app sorting is part of Pro. Unlock Pro to keep your custom order.",
  "pro.prompt.themes": "This theme is part of Pro. Unlock Pro to keep using advanced App Grid themes.",
  "pro.purchase.failed": "Unable to complete the purchase right now. Please try again.",
  "pro.purchase.unavailable": "Pro purchase is unavailable right now. Please try again later.",
  "pro.purchase.verificationFailed": "App Store verification failed. Please try again.",
  "pro.restore.failed": "Unable to restore purchases right now. Please try again.",
  "pro.sorting.previewToast": "Preview only. Unlock Pro to save manual app sorting.",
  "pro.status.free": "Free",
  "pro.status.legacyUnlocked": "Pro · Early supporter",
  "pro.status.unlocked": "Pro unlocked",
  "pro.theme.preview.status": "Previewing Pro theme",
  "pro.theme.status.free": "Free themes are ready now. Unlock Pro to keep every advanced theme.",
  "pro.theme.status.preview": "Previewing a Pro theme. Unlock Pro to keep it after the preview ends.",
  "pro.theme.status.unlocked": "All themes are unlocked on this Mac."
}
src/Apptag/Localization/es.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Volver a ver los consejos más tarde",
  "usageTips.closeReminder.message": "Puedes volver a ver los consejos en Ajustes > General desmarcando “Ocultar consejos de uso”.",
  "usageTips.closeReminder.doNotRemind": "Entendido. No volver a recordármelo al cerrar los consejos.",
  "usageTips.closeReminder.previewCallout": "Desmarca esta opción para volver a verlos"
  "usageTips.closeReminder.previewCallout": "Desmarca esta opción para volver a verlos",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Avance",
  "pro.card.restore": "Restaurar compras",
  "pro.card.summary.free": "Desbloquee temas premium, importación y exportación de diseños, notas ilimitadas y clasificación manual de aplicaciones guardadas.",
  "pro.card.summary.legacy": "Gracias por apoyar TagLauncher desde el principio. Tu acceso Pro ya está activo.",
  "pro.card.summary.unlocked": "Pro está activo en esta Mac. Los temas premium y las herramientas de diseño avanzadas están listos.",
  "pro.card.title.free": "Actualízate a Pro",
  "pro.card.title.legacy": "Pro desbloqueado",
  "pro.card.title.unlocked": "Pro desbloqueado",
  "pro.card.unlock": "Desbloquear Pro",
  "pro.feature.export": "Exportación de diseño",
  "pro.feature.export.benefit": "Guarde sus etiquetas, orden y diseño actuales como una copia de seguridad reutilizable.",
  "pro.feature.import": "Importación de diseño",
  "pro.feature.import.benefit": "Restaure un diseño de TagLauncher guardado en esta Mac en un solo paso.",
  "pro.feature.notes": "Notas ilimitadas",
  "pro.feature.notes.benefit": "Guarde notas para cada aplicación sin el límite gratuito de 5 notas.",
  "pro.feature.sorting": "Clasificación de aplicaciones guardadas",
  "pro.feature.sorting.benefit": "Mantenga el orden manual de sus aplicaciones después de salir del modo de edición.",
  "pro.feature.themes": "Temas premium",
  "pro.feature.themes.benefit": "Desbloquea todos los temas avanzados de App Grid y consérvalos para siempre.",
  "pro.notes.limitReached": "La versión gratuita puede guardar notas de hasta 5 aplicaciones. Desbloquea Pro para guardar notas ilimitadas.",
  "pro.notes.remaining": "La versión gratuita incluye %limit% notas de la aplicación. Quedan %remaining%.",
  "pro.operation.cancelled": "Compra cancelada.",
  "pro.operation.loading": "Comprobando acceso Pro...",
  "pro.operation.pending": "Esperando la confirmación de la App Store...",
  "pro.operation.purchasing": "Comprando Pro…",
  "pro.operation.restoreNotFound": "No se encontró ninguna compra Pro anterior para este ID de Apple.",
  "pro.operation.restoring": "Restaurando compra...",
  "pro.prompt.export": "La exportación de diseño es una característica Pro. Desbloquee Pro para hacer una copia de seguridad de sus etiquetas, orden y diseño actuales.",
  "pro.prompt.import": "La importación de diseños es una función Pro. Desbloquea Pro para restaurar un diseño guardado en esta Mac.",
  "pro.prompt.notes": "Las notas de aplicaciones ilimitadas son parte de Pro. Desbloquea Pro para guardar notas para cada aplicación.",
  "pro.prompt.sorting": "La clasificación manual de aplicaciones guardadas es parte de Pro. Desbloquea Pro para mantener tu pedido personalizado.",
  "pro.prompt.themes": "Este tema es parte de Pro. Desbloquea Pro para seguir usando temas avanzados de App Grid.",
  "pro.purchase.failed": "No se puede completar la compra en este momento. Por favor inténtalo de nuevo.",
  "pro.purchase.unavailable": "La compra Pro no está disponible en este momento. Inténtelo de nuevo más tarde.",
  "pro.purchase.verificationFailed": "La verificación de la App Store falló. Por favor inténtalo de nuevo.",
  "pro.restore.failed": "No se pueden restaurar las compras en este momento. Por favor inténtalo de nuevo.",
  "pro.sorting.previewToast": "Solo vista previa. Desbloquee Pro para guardar la clasificación manual de aplicaciones.",
  "pro.status.free": "Gratis",
  "pro.status.legacyUnlocked": "Pro · Partidario temprano",
  "pro.status.unlocked": "Pro desbloqueado",
  "pro.theme.preview.status": "Vista previa del tema Pro",
  "pro.theme.status.free": "Los temas gratuitos ya están listos. Desbloquea Pro para conservar todos los temas avanzados.",
  "pro.theme.status.preview": "Vista previa de un tema Pro. Desbloquea Pro para conservarlo después de que finalice la vista previa.",
  "pro.theme.status.unlocked": "Todos los temas están desbloqueados en esta Mac."
}
src/Apptag/Localization/fr.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Retrouver les astuces plus tard",
  "usageTips.closeReminder.message": "Vous pourrez revoir les astuces dans Réglages > Général en décochant « Masquer les astuces d’utilisation ».",
  "usageTips.closeReminder.doNotRemind": "Compris. Ne plus me le rappeler quand je ferme les astuces.",
  "usageTips.closeReminder.previewCallout": "Décochez cette option pour revoir les astuces"
  "usageTips.closeReminder.previewCallout": "Décochez cette option pour revoir les astuces",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Aperçu",
  "pro.card.restore": "Restaurer les achats",
  "pro.card.summary.free": "Débloquez des thèmes premium, l'importation et l'exportation de mises en page, des notes illimitées et le tri manuel des applications enregistrées.",
  "pro.card.summary.legacy": "Merci d'avoir soutenu TagLauncher dès le début. Votre accès Pro est déjà actif.",
  "pro.card.summary.unlocked": "Pro est actif sur ce Mac. Les thèmes premium et les outils de mise en page avancés sont prêts.",
  "pro.card.title.free": "Passer à Pro",
  "pro.card.title.legacy": "Pro débloqué",
  "pro.card.title.unlocked": "Pro débloqué",
  "pro.card.unlock": "Débloquez Pro",
  "pro.feature.export": "Exportation de mise en page",
  "pro.feature.export.benefit": "Enregistrez vos balises, votre commande et votre mise en page actuelles en tant que sauvegarde réutilisable.",
  "pro.feature.import": "Importation de mise en page",
  "pro.feature.import.benefit": "Restaurez une mise en page TagLauncher enregistrée sur ce Mac en une seule étape.",
  "pro.feature.notes": "Notes illimitées",
  "pro.feature.notes.benefit": "Enregistrez des notes pour chaque application sans la limite gratuite de 5 notes.",
  "pro.feature.sorting": "Tri des applications enregistrées",
  "pro.feature.sorting.benefit": "Conservez l’ordre manuel de vos applications après avoir quitté le mode édition.",
  "pro.feature.themes": "Thèmes premium",
  "pro.feature.themes.benefit": "Débloquez tous les thèmes App Grid avancés et conservez-les en permanence.",
  "pro.notes.limitReached": "La version gratuite peut enregistrer des notes pour jusqu'à 5 applications. Débloquez Pro pour enregistrer un nombre illimité de notes.",
  "pro.notes.remaining": "La version gratuite comprend %limit% notes d'application. %remaining% restant.",
  "pro.operation.cancelled": "Achat annulé.",
  "pro.operation.loading": "Vérification de l'accès Pro…",
  "pro.operation.pending": "En attente de confirmation App Store…",
  "pro.operation.purchasing": "Achat de Pro…",
  "pro.operation.restoreNotFound": "Aucun achat Pro précédent n'a été trouvé pour ce Apple ID.",
  "pro.operation.restoring": "Restauration de l'achat…",
  "pro.prompt.export": "L'exportation de mise en page est une fonctionnalité Pro. Débloquez Pro pour sauvegarder vos balises, votre commande et votre mise en page actuelles.",
  "pro.prompt.import": "L'importation de mise en page est une fonctionnalité Pro. Déverrouillez Pro pour restaurer une mise en page enregistrée sur ce Mac.",
  "pro.prompt.notes": "Les notes d'application illimitées font partie de Pro. Débloquez Pro pour enregistrer des notes pour chaque application.",
  "pro.prompt.sorting": "Le tri manuel des applications enregistrées fait partie de Pro. Débloquez Pro pour conserver votre commande personnalisée.",
  "pro.prompt.themes": "Ce thème fait partie de Pro. Débloquez Pro pour continuer à utiliser les thèmes App Grid avancés.",
  "pro.purchase.failed": "Impossible de finaliser l'achat pour le moment. Veuillez réessayer.",
  "pro.purchase.unavailable": "L'achat Pro n'est pas disponible pour le moment. Veuillez réessayer plus tard.",
  "pro.purchase.verificationFailed": "La vérification App Store a échoué. Veuillez réessayer.",
  "pro.restore.failed": "Impossible de restaurer les achats pour le moment. Veuillez réessayer.",
  "pro.sorting.previewToast": "Aperçu uniquement. Déverrouillez Pro pour enregistrer le tri manuel des applications.",
  "pro.status.free": "Gratuit",
  "pro.status.legacyUnlocked": "Pro · Premier partisan",
  "pro.status.unlocked": "Pro débloqué",
  "pro.theme.preview.status": "Aperçu du thème Pro",
  "pro.theme.status.free": "Les thèmes gratuits sont prêts maintenant. Débloquez Pro pour conserver tous les thèmes avancés.",
  "pro.theme.status.preview": "Prévisualisation d'un thème Pro. Déverrouillez Pro pour le conserver une fois l'aperçu terminé.",
  "pro.theme.status.unlocked": "Tous les thèmes sont débloqués sur ce Mac."
}
src/Apptag/Localization/id.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Tampilkan tips lagi nanti",
  "usageTips.closeReminder.message": "Kamu dapat menampilkan tips lagi di Pengaturan > Umum dengan menghapus centang “Sembunyikan tips penggunaan”.",
  "usageTips.closeReminder.doNotRemind": "Mengerti. Jangan ingatkan lagi saat saya menutup tips.",
  "usageTips.closeReminder.previewCallout": "Hapus centang ini untuk menampilkan tips lagi"
  "usageTips.closeReminder.previewCallout": "Hapus centang ini untuk menampilkan tips lagi",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Pratinjau",
  "pro.card.restore": "Pulihkan Pembelian",
  "pro.card.summary.free": "Buka kunci tema premium, impor dan ekspor tata letak, catatan tak terbatas, dan penyortiran aplikasi manual yang tersimpan.",
  "pro.card.summary.legacy": "Terima kasih telah mendukung TagLauncher lebih awal. Akses Pro Anda sudah aktif.",
  "pro.card.summary.unlocked": "Pro aktif di Mac ini. Tema premium dan alat tata letak tingkat lanjut sudah siap.",
  "pro.card.title.free": "Tingkatkan ke Pro",
  "pro.card.title.legacy": "Pro tidak terkunci",
  "pro.card.title.unlocked": "Pro tidak terkunci",
  "pro.card.unlock": "Buka kunci Pro",
  "pro.feature.export": "Ekspor tata letak",
  "pro.feature.export.benefit": "Simpan tag, urutan, dan tata letak Anda saat ini sebagai cadangan yang dapat digunakan kembali.",
  "pro.feature.import": "Impor tata letak",
  "pro.feature.import.benefit": "Pulihkan tata letak TagLauncher yang disimpan di Mac ini dalam satu langkah.",
  "pro.feature.notes": "Catatan tanpa batas",
  "pro.feature.notes.benefit": "Simpan catatan untuk setiap aplikasi tanpa batas 5 nada gratis.",
  "pro.feature.sorting": "Penyortiran aplikasi tersimpan",
  "pro.feature.sorting.benefit": "Simpan urutan aplikasi manual Anda setelah Anda keluar dari mode pengeditan.",
  "pro.feature.themes": "Tema premium",
  "pro.feature.themes.benefit": "Buka kunci setiap tema App Grid tingkat lanjut dan simpan secara permanen.",
  "pro.notes.limitReached": "Versi gratis dapat menyimpan catatan hingga 5 aplikasi. Buka kunci Pro untuk menyimpan catatan tanpa batas.",
  "pro.notes.remaining": "Versi gratis mencakup %limit% catatan aplikasi. %remaining% tersisa.",
  "pro.operation.cancelled": "Pembelian dibatalkan.",
  "pro.operation.loading": "Memeriksa akses Pro…",
  "pro.operation.pending": "Menunggu konfirmasi App Store…",
  "pro.operation.purchasing": "Membeli Pro…",
  "pro.operation.restoreNotFound": "Tidak ditemukan pembelian Pro sebelumnya untuk Apple ID ini.",
  "pro.operation.restoring": "Memulihkan pembelian…",
  "pro.prompt.export": "Ekspor tata letak adalah fitur Pro. Buka kunci Pro untuk mencadangkan tag, urutan, dan tata letak Anda saat ini.",
  "pro.prompt.import": "Impor tata letak adalah fitur Pro. Buka kunci Pro untuk memulihkan tata letak yang disimpan di Mac ini.",
  "pro.prompt.notes": "Catatan aplikasi tanpa batas adalah bagian dari Pro. Buka kunci Pro untuk menyimpan catatan untuk setiap aplikasi.",
  "pro.prompt.sorting": "Penyortiran aplikasi manual yang disimpan adalah bagian dari Pro. Buka kunci Pro untuk menyimpan pesanan khusus Anda.",
  "pro.prompt.themes": "Tema ini adalah bagian dari Pro. Buka kunci Pro untuk terus menggunakan tema App Grid tingkat lanjut.",
  "pro.purchase.failed": "Tidak dapat menyelesaikan pembelian saat ini. Silakan coba lagi.",
  "pro.purchase.unavailable": "Pembelian Pro tidak tersedia saat ini. Silakan coba lagi nanti.",
  "pro.purchase.verificationFailed": "Verifikasi App Store gagal. Silakan coba lagi.",
  "pro.restore.failed": "Tidak dapat memulihkan pembelian saat ini. Silakan coba lagi.",
  "pro.sorting.previewToast": "Hanya pratinjau. Buka kunci Pro untuk menyimpan penyortiran aplikasi manual.",
  "pro.status.free": "Gratis",
  "pro.status.legacyUnlocked": "Pro · Pendukung awal",
  "pro.status.unlocked": "Pro tidak terkunci",
  "pro.theme.preview.status": "Pratinjau tema Pro",
  "pro.theme.status.free": "Tema gratis sudah siap sekarang. Buka kunci Pro untuk mempertahankan setiap tema lanjutan.",
  "pro.theme.status.preview": "Pratinjau tema Pro. Buka kunci Pro untuk menyimpannya setelah pratinjau berakhir.",
  "pro.theme.status.unlocked": "Semua tema tidak terkunci di Mac ini."
}
src/Apptag/Localization/it.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Rivedere i suggerimenti più tardi",
  "usageTips.closeReminder.message": "Puoi mostrarli di nuovo in Impostazioni > Generali disattivando “Nascondi suggerimenti d’uso”.",
  "usageTips.closeReminder.doNotRemind": "Ho capito. Non ricordarmelo più quando chiudo i suggerimenti.",
  "usageTips.closeReminder.previewCallout": "Disattiva questa opzione per rivedere i suggerimenti"
  "usageTips.closeReminder.previewCallout": "Disattiva questa opzione per rivedere i suggerimenti",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Anteprima",
  "pro.card.restore": "Ripristina gli acquisti",
  "pro.card.summary.free": "Sblocca temi premium, importazione ed esportazione di layout, note illimitate e ordinamento manuale delle app salvate.",
  "pro.card.summary.legacy": "Grazie per aver supportato subito TagLauncher. Il tuo accesso Pro è già attivo.",
  "pro.card.summary.unlocked": "Pro è attivo su questo Mac. Temi premium e strumenti di layout avanzati sono pronti.",
  "pro.card.title.free": "Aggiorna a Pro",
  "pro.card.title.legacy": "Pro sbloccato",
  "pro.card.title.unlocked": "Pro sbloccato",
  "pro.card.unlock": "Sblocca Pro",
  "pro.feature.export": "Esportazione del layout",
  "pro.feature.export.benefit": "Salva i tag, l'ordine e il layout correnti come backup riutilizzabile.",
  "pro.feature.import": "Importazione del layout",
  "pro.feature.import.benefit": "Ripristina un layout TagLauncher salvato su questo Mac in un solo passaggio.",
  "pro.feature.notes": "Note illimitate",
  "pro.feature.notes.benefit": "Salva note per ogni app senza il limite gratuito di 5 note.",
  "pro.feature.sorting": "Ordinamento delle app salvate",
  "pro.feature.sorting.benefit": "Mantieni l'ordine manuale delle app dopo aver abbandonato la modalità di modifica.",
  "pro.feature.themes": "Temi premium",
  "pro.feature.themes.benefit": "Sblocca tutti i temi App Grid avanzati e mantienili in modo permanente.",
  "pro.notes.limitReached": "La versione gratuita può salvare note per un massimo di 5 app. Sblocca Pro per salvare note illimitate.",
  "pro.notes.remaining": "La versione gratuita include %limit% note sull'app. %remaining% rimanenti.",
  "pro.operation.cancelled": "Acquisto annullato.",
  "pro.operation.loading": "Verifica dell'accesso a Pro…",
  "pro.operation.pending": "In attesa della conferma dell'App Store...",
  "pro.operation.purchasing": "Acquistando Pro…",
  "pro.operation.restoreNotFound": "Nessun acquisto Pro precedente trovato per questo Apple ID.",
  "pro.operation.restoring": "Ripristino dell'acquisto…",
  "pro.prompt.export": "L'esportazione del layout è una funzionalità di Pro. Sblocca Pro per eseguire il backup dei tag, dell'ordine e del layout attuali.",
  "pro.prompt.import": "L'importazione del layout è una funzionalità Pro. Sblocca Pro per ripristinare un layout salvato su questo Mac.",
  "pro.prompt.notes": "Le note sull'app illimitate fanno parte di Pro. Sblocca Pro per salvare le note per ogni app.",
  "pro.prompt.sorting": "L'ordinamento manuale delle app salvate fa parte di Pro. Sblocca Pro per mantenere il tuo ordine personalizzato.",
  "pro.prompt.themes": "Questo tema fa parte di Pro. Sblocca Pro per continuare a utilizzare i temi App Grid avanzati.",
  "pro.purchase.failed": "Impossibile completare l'acquisto in questo momento. Per favore riprova.",
  "pro.purchase.unavailable": "L'acquisto di Pro non è al momento disponibile. Per favore riprova più tardi.",
  "pro.purchase.verificationFailed": "Verifica App Store non riuscita. Per favore riprova.",
  "pro.restore.failed": "Impossibile ripristinare gli acquisti in questo momento. Per favore riprova.",
  "pro.sorting.previewToast": "Solo anteprima. Sblocca Pro per salvare l'ordinamento manuale delle app.",
  "pro.status.free": "Gratuito",
  "pro.status.legacyUnlocked": "Pro · Primo sostenitore",
  "pro.status.unlocked": "Pro sbloccato",
  "pro.theme.preview.status": "Anteprima del tema Pro",
  "pro.theme.status.free": "I temi gratuiti sono pronti ora. Sblocca Pro per mantenere tutti i temi avanzati.",
  "pro.theme.status.preview": "Anteprima di un tema Pro. Sblocca Pro per conservarlo al termine dell'anteprima.",
  "pro.theme.status.unlocked": "Tutti i temi sono sbloccati su questo Mac."
}
src/Apptag/Localization/ja.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "あとでヒントをもう一度見る",
  "usageTips.closeReminder.message": "設定 > 一般で「使用ヒントを隠す」のチェックを外すと、ヒントを再表示できます。",
  "usageTips.closeReminder.doNotRemind": "了解しました。次回ヒントを閉じるときは表示しません。",
  "usageTips.closeReminder.previewCallout": "再表示するにはこのチェックを外します"
  "usageTips.closeReminder.previewCallout": "再表示するにはこのチェックを外します",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "プレビュー",
  "pro.card.restore": "購入を復元",
  "pro.card.summary.free": "プレミアム テーマ、レイアウトのインポートとエクスポート、無制限のメモ、および保存された手動アプリの並べ替えのロックを解除します。",
  "pro.card.summary.legacy": "早期に TagLauncher をサポートしていただきありがとうございます。 Pro アクセスはすでにアクティブです。",
  "pro.card.summary.unlocked": "Pro はこの Mac 上でアクティブです。プレミアム テーマと高度なレイアウト ツールが用意されています。",
  "pro.card.title.free": "Pro にアップグレード",
  "pro.card.title.legacy": "Pro ロック解除",
  "pro.card.title.unlocked": "Pro ロック解除",
  "pro.card.unlock": "Pro のロック解除",
  "pro.feature.export": "レイアウトエクスポート",
  "pro.feature.export.benefit": "現在のタグ、順序、レイアウトを再利用可能なバックアップとして保存します。",
  "pro.feature.import": "レイアウト インポート",
  "pro.feature.import.benefit": "保存されている TagLauncher レイアウトをこの Mac にワンステップで復元します。",
  "pro.feature.notes": "無制限メモ",
  "pro.feature.notes.benefit": "無料の 5 メモ制限なしで、すべてのアプリのメモを保存します。",
  "pro.feature.sorting": "保存したアプリの並べ替え",
  "pro.feature.sorting.benefit": "編集モードを終了した後も、手動でアプリの順序を維持します。",
  "pro.feature.themes": "プレミアム テーマ",
  "pro.feature.themes.benefit": "すべての高度な App Grid のロックを解除します",
  "pro.notes.limitReached": "無料版では、最大 5 つのアプリのメモを保存できます。 Pro のロックを解除すると、メモを無制限に保存できます。",
  "pro.notes.remaining": "無料版には %limit% 個のアプリ メモが含まれています。残り %remaining% が残っています。",
  "pro.operation.cancelled": "購入はキャンセルされました。",
  "pro.operation.loading": "Pro アクセスを確認中…",
  "pro.operation.pending": "App Store の確認を待っています…",
  "pro.operation.purchasing": "Pro を購入中…",
  "pro.operation.restoreNotFound": "以前はありませんこの Apple ID に対して Pro の購入が見つかりました。",
  "pro.operation.restoring": "購入を復元中…",
  "pro.prompt.export": "レイアウトのエクスポートは Pro 機能です。 Pro のロックを解除して、現在のタグ、順序、レイアウトをバックアップします。",
  "pro.prompt.import": "レイアウトのインポートは Pro 機能です。 Pro のロックを解除して、この Mac に保存されたレイアウトを復元します。",
  "pro.prompt.notes": "無制限のアプリ ノートは Pro の一部です。 Pro のロックを解除して、すべてのアプリのメモを保存します。",
  "pro.prompt.sorting": "保存されたアプリの手動並べ替えは Pro の一部です。カスタム オーダーを維持するには、Pro のロックを解除してください。",
  "pro.prompt.themes": "このテーマは Pro の一部です。高度な App Grid テーマを引き続き使用するには、Pro のロックを解除してください。",
  "pro.purchase.failed": "現在購入を完了できません。もう一度お試しください。",
  "pro.purchase.unavailable": "Pro は現在購入できません。後でもう一度お試しください。",
  "pro.purchase.verificationFailed": "App Store の検証に失敗しました。もう一度お試しください。",
  "pro.restore.failed": "現在、購入を復元できません。もう一度お試しください。",
  "pro.sorting.previewToast": "プレビューのみ。 Pro のロックを解除して、アプリの手動並べ替えを保存します。",
  "pro.status.free": "無料",
  "pro.status.legacyUnlocked": "Pro · 初期サポーター",
  "pro.status.unlocked": "Pro ロック解除",
  "pro.theme.preview.status": "Pro テーマのプレビュー",
  "pro.theme.status.free": "無料のテーマが準備完了です。 Pro のロックを解除して、すべての高度なテーマを保持します。",
  "pro.theme.status.preview": "Pro テーマをプレビューしています。プレビュー終了後も保持するには、Pro のロックを解除してください。",
  "pro.theme.status.unlocked": "この Mac ではすべてのテーマのロックが解除されています。"
}
src/Apptag/Localization/ko.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "나중에 팁 다시 보기",
  "usageTips.closeReminder.message": "설정 > 일반에서 “사용 팁 숨기기”를 해제하면 팁을 다시 볼 수 있습니다.",
  "usageTips.closeReminder.doNotRemind": "알겠습니다. 다음에 팁을 닫을 때 다시 알리지 마세요.",
  "usageTips.closeReminder.previewCallout": "팁을 다시 보려면 이 선택을 해제하세요"
  "usageTips.closeReminder.previewCallout": "팁을 다시 보려면 이 선택을 해제하세요",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "미리보기",
  "pro.card.restore": "구매 복원",
  "pro.card.summary.free": "프리미엄 테마, 레이아웃 가져오기 및 내보내기, 무제한 메모, 저장된 수동 앱 정렬 기능을 잠금 해제하세요.",
  "pro.card.summary.legacy": "TagLauncher을 조기에 지원해 주셔서 감사합니다. 귀하의 Pro 액세스는 이미 활성화되어 있습니다.",
  "pro.card.summary.unlocked": "Pro은(는) 이 Mac에서 활성화되어 있습니다. 프리미엄 테마 및 고급 레이아웃 도구가 준비되었습니다.",
  "pro.card.title.free": "Pro로 업그레이드",
  "pro.card.title.legacy": "Pro 잠금 해제됨",
  "pro.card.title.unlocked": "Pro 잠금 해제됨",
  "pro.card.unlock": "Pro 잠금 해제",
  "pro.feature.export": "레이아웃 내보내기",
  "pro.feature.export.benefit": "현재 저장 태그, 순서 및 레이아웃을 재사용 가능한 백업으로 저장하세요.",
  "pro.feature.import": "레이아웃 가져오기",
  "pro.feature.import.benefit": "이 Mac에 저장된 TagLauncher 레이아웃을 한 번에 복원하세요.",
  "pro.feature.notes": "무제한 메모",
  "pro.feature.notes.benefit": "무료 5노트 없이 모든 앱에 대한 메모를 저장하세요 제한.",
  "pro.feature.sorting": "저장된 앱 정렬",
  "pro.feature.sorting.benefit": "편집 모드를 종료한 후에도 수동 앱 순서를 유지하세요.",
  "pro.feature.themes": "프리미엄 테마",
  "pro.feature.themes.benefit": "모든 고급 App Grid 테마를 잠금 해제하고 영구적으로 유지하세요.",
  "pro.notes.limitReached": "무료 버전에서는 최대 5개의 앱에 대한 메모를 저장할 수 있습니다. 메모를 무제한으로 저장하려면 Pro을(를) 잠금 해제하세요.",
  "pro.notes.remaining": "무료 버전에는 %limit%개의 앱 메모가 포함되어 있습니다. %remaining%개 남음.",
  "pro.operation.cancelled": "구매가 취소되었습니다.",
  "pro.operation.loading": "Pro 액세스 확인 중…",
  "pro.operation.pending": "App Store 확인을 기다리는 중…",
  "pro.operation.purchasing": "Pro 구매 중…",
  "pro.operation.restoreNotFound": "이전 항목 없음 Pro 이 Apple ID에 대한 구매가 발견되었습니다.",
  "pro.operation.restoring": "구매를 복원하는 중...",
  "pro.prompt.export": "레이아웃 내보내기는 Pro 기능입니다. 현재 태그, 주문 및 레이아웃을 백업하려면 Pro을(를) 잠금 해제하세요.",
  "pro.prompt.import": "레이아웃 가져오기는 Pro 기능입니다. 이 Mac에 저장된 레이아웃을 복원하려면 Pro을(를) 잠금 해제하세요.",
  "pro.prompt.notes": "무제한 앱 노트는 Pro의 일부입니다. Pro을(를) 잠금 해제하여 모든 앱에 대한 메모를 저장하세요.",
  "pro.prompt.sorting": "저장된 수동 앱 정렬은 Pro의 일부입니다. 맞춤 순서를 유지하려면 Pro을(를) 잠금 해제하세요.",
  "pro.prompt.themes": "이 테마는 Pro의 일부입니다. 고급 App Grid 테마를 계속 사용하려면 Pro을(를) 잠금 해제하세요.",
  "pro.purchase.failed": "지금은 구매를 완료할 수 없습니다. 다시 시도해 주세요.",
  "pro.purchase.unavailable": "Pro 지금은 구매할 수 없습니다. 나중에 다시 시도해 주세요.",
  "pro.purchase.verificationFailed": "App Store 확인에 실패했습니다. 다시 시도해 주세요.",
  "pro.restore.failed": "지금은 구매를 복원할 수 없습니다. 다시 시도해 주세요.",
  "pro.sorting.previewToast": "미리보기 전용입니다. 수동 앱 정렬을 저장하려면 Pro을(를) 잠금 해제하세요.",
  "pro.status.free": "무료",
  "pro.status.legacyUnlocked": "Pro · 초기 후원자",
  "pro.status.unlocked": "Pro 잠금 해제됨",
  "pro.theme.preview.status": "Pro 테마 미리보기",
  "pro.theme.status.free": "무료 테마가 이제 준비되었습니다. 모든 고급 테마를 유지하려면 Pro을(를) 잠금 해제하세요.",
  "pro.theme.status.preview": "Pro 테마를 미리 보는 중입니다. 미리보기가 끝난 후에도 유지하려면 Pro을(를) 잠금 해제하세요.",
  "pro.theme.status.unlocked": "모든 테마는 이 Mac에서 잠금 해제됩니다."
}
src/Apptag/Localization/ms.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Tunjukkan petua lagi kemudian",
  "usageTips.closeReminder.message": "Anda boleh menunjukkan petua semula dalam Tetapan > Umum dengan menyahtanda “Sembunyikan petua penggunaan”.",
  "usageTips.closeReminder.doNotRemind": "Faham. Jangan ingatkan lagi apabila saya menutup petua.",
  "usageTips.closeReminder.previewCallout": "Nyahtanda pilihan ini untuk menunjukkan petua semula"
  "usageTips.closeReminder.previewCallout": "Nyahtanda pilihan ini untuk menunjukkan petua semula",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Pratonton",
  "pro.card.restore": "Pulihkan Pembelian",
  "pro.card.summary.free": "Buka kunci tema premium, import dan eksport reka letak, nota tanpa had dan pengisihan apl manual yang disimpan.",
  "pro.card.summary.legacy": "Terima kasih kerana menyokong TagLauncher lebih awal. Akses Pro anda sudah aktif.",
  "pro.card.summary.unlocked": "Pro aktif pada Mac ini. Tema premium dan alatan susun atur lanjutan sudah sedia.",
  "pro.card.title.free": "Tingkatkan ke Pro",
  "pro.card.title.legacy": "Pro dibuka kunci",
  "pro.card.title.unlocked": "Pro dibuka kunci",
  "pro.card.unlock": "Buka kunci Pro",
  "pro.feature.export": "Eksport reka letak",
  "pro.feature.export.benefit": "Simpan teg, pesanan dan reka letak semasa anda sebagai sandaran boleh guna semula.",
  "pro.feature.import": "Import reka letak",
  "pro.feature.import.benefit": "Pulihkan reka letak TagLauncher yang disimpan pada Mac ini dalam satu langkah.",
  "pro.feature.notes": "Nota tanpa had",
  "pro.feature.notes.benefit": "Simpan nota untuk setiap apl tanpa had 5 nota percuma.",
  "pro.feature.sorting": "Pengisihan apl yang disimpan",
  "pro.feature.sorting.benefit": "Simpan pesanan apl manual anda selepas anda meninggalkan mod pengeditan.",
  "pro.feature.themes": "Tema premium",
  "pro.feature.themes.benefit": "Buka kunci semua tema Grid Apl lanjutan dan simpannya secara kekal.",
  "pro.notes.limitReached": "Versi percuma boleh menyimpan nota untuk sehingga 5 apl. Buka kunci Pro untuk menyimpan nota tanpa had.",
  "pro.notes.remaining": "Versi percuma termasuk %limit% nota aplikasi. %remaining% lagi.",
  "pro.operation.cancelled": "Pembelian dibatalkan.",
  "pro.operation.loading": "Menyemak akses Pro…",
  "pro.operation.pending": "Menunggu pengesahan App Store…",
  "pro.operation.purchasing": "Membeli Pro…",
  "pro.operation.restoreNotFound": "Tiada pembelian Pro sebelumnya ditemui untuk Apple ID ini.",
  "pro.operation.restoring": "Memulihkan pembelian…",
  "pro.prompt.export": "Eksport reka letak ialah ciri Pro. Buka kunci Pro untuk menyandarkan teg, pesanan dan reka letak semasa anda.",
  "pro.prompt.import": "Import reka letak ialah ciri Pro. Buka kunci Pro untuk memulihkan reka letak yang disimpan pada Mac ini.",
  "pro.prompt.notes": "Nota apl tanpa had adalah sebahagian daripada Pro. Buka kunci Pro untuk menyimpan nota bagi setiap apl.",
  "pro.prompt.sorting": "Pengisihan apl manual yang disimpan adalah sebahagian daripada Pro. Buka kunci Pro untuk mengekalkan pesanan tersuai anda.",
  "pro.prompt.themes": "Tema ini adalah sebahagian daripada Pro. Buka kunci Pro untuk terus menggunakan tema Grid Apl lanjutan.",
  "pro.purchase.failed": "Tidak dapat menyelesaikan pembelian sekarang. Sila cuba lagi.",
  "pro.purchase.unavailable": "Pembelian Pro tidak tersedia sekarang. Sila cuba lagi kemudian.",
  "pro.purchase.verificationFailed": "App Store pengesahan gagal. Sila cuba lagi.",
  "pro.restore.failed": "Tidak dapat memulihkan pembelian sekarang. Sila cuba lagi.",
  "pro.sorting.previewToast": "Pratonton sahaja. Buka kunci Pro untuk menyimpan pengisihan apl manual.",
  "pro.status.free": "Percuma",
  "pro.status.legacyUnlocked": "Pro · Penyokong awal",
  "pro.status.unlocked": "Pro dibuka kunci",
  "pro.theme.preview.status": "Mempratonton tema Pro",
  "pro.theme.status.free": "Tema percuma sudah sedia sekarang. Buka kunci Pro untuk mengekalkan setiap tema lanjutan.",
  "pro.theme.status.preview": "Mempratonton tema Pro. Buka kunci Pro untuk menyimpannya selepas pratonton tamat.",
  "pro.theme.status.unlocked": "Semua tema dibuka kunci pada Mac ini."
}
src/Apptag/Localization/nb.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Vis tips igjen senere",
  "usageTips.closeReminder.message": "Du kan vise tips igjen i Innstillinger > Generelt ved å fjerne avmerkingen for «Skjul brukstips».",
  "usageTips.closeReminder.doNotRemind": "Skjønner. Ikke minn meg på dette neste gang jeg lukker tips.",
  "usageTips.closeReminder.previewCallout": "Fjern denne avmerkingen for å vise tips igjen"
  "usageTips.closeReminder.previewCallout": "Fjern denne avmerkingen for å vise tips igjen",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Forhåndsvisning",
  "pro.card.restore": "Gjenopprett kjøp",
  "pro.card.summary.free": "Lås opp premium-temaer, import og eksport av layout, ubegrensede notater og lagret manuell appsortering.",
  "pro.card.summary.legacy": "Takk for at du støttet TagLauncher tidlig. Din Pro-tilgang er allerede aktiv.",
  "pro.card.summary.unlocked": "Pro er aktiv på denne Mac. Premium-temaer og avanserte layoutverktøy er klare.",
  "pro.card.title.free": "Oppgrader til Pro",
  "pro.card.title.legacy": "Pro låst opp",
  "pro.card.title.unlocked": "Pro låst opp",
  "pro.card.unlock": "Lås opp Pro",
  "pro.feature.export": "Eksport av layout",
  "pro.feature.export.benefit": "Lagre gjeldende tagger, bestilling og layout som en gjenbrukbar sikkerhetskopi.",
  "pro.feature.import": "Import av layout",
  "pro.feature.import.benefit": "Gjenopprett et lagret TagLauncher-oppsett på denne Mac i ett trinn.",
  "pro.feature.notes": "Ubegrensede notater",
  "pro.feature.notes.benefit": "Lagre notater for hver app uten den gratis grensen på 5 noter.",
  "pro.feature.sorting": "Lagret appsortering",
  "pro.feature.sorting.benefit": "Behold den manuelle appbestillingen din etter at du forlater redigeringsmodus.",
  "pro.feature.themes": "Premium-temaer",
  "pro.feature.themes.benefit": "Lås opp hvert avansert App Grid-tema og behold det permanent.",
  "pro.notes.limitReached": "Gratisversjonen kan lagre notater for opptil 5 apper. Lås opp Pro for å lagre ubegrensede notater.",
  "pro.notes.remaining": "Gratisversjonen inkluderer %limit% appnotater. %remaining% gjenstår.",
  "pro.operation.cancelled": "Kjøp kansellert.",
  "pro.operation.loading": "Sjekker Pro-tilgang …",
  "pro.operation.pending": "Venter på App Store bekreftelse...",
  "pro.operation.purchasing": "Kjøper Pro...",
  "pro.operation.restoreNotFound": "Ingen tidligere Pro-kjøp ble funnet for denne Apple ID.",
  "pro.operation.restoring": "Gjenoppretter kjøpet …",
  "pro.prompt.export": "Layouteksport er en Pro-funksjon. Lås opp Pro for å sikkerhetskopiere gjeldende tagger, rekkefølge og layout.",
  "pro.prompt.import": "Layoutimport er en Pro-funksjon. Lås opp Pro for å gjenopprette et lagret oppsett på denne Mac.",
  "pro.prompt.notes": "Ubegrensede appnotater er en del av Pro. Lås opp Pro for å lagre notater for hver app.",
  "pro.prompt.sorting": "Lagret manuell appsortering er en del av Pro. Lås opp Pro for å beholde din egendefinerte rekkefølge.",
  "pro.prompt.themes": "Dette temaet er en del av Pro. Lås opp Pro for å fortsette å bruke avanserte App Grid-temaer.",
  "pro.purchase.failed": "Kan ikke fullføre kjøpet akkurat nå. Vennligst prøv igjen.",
  "pro.purchase.unavailable": "Pro kjøp er utilgjengelig akkurat nå. Vennligst prøv igjen senere.",
  "pro.purchase.verificationFailed": "App Store bekreftelse mislyktes. Vennligst prøv igjen.",
  "pro.restore.failed": "Kan ikke gjenopprette kjøp akkurat nå. Vennligst prøv igjen.",
  "pro.sorting.previewToast": "Kun forhåndsvisning. Lås opp Pro for å lagre manuell appsortering.",
  "pro.status.free": "Gratis",
  "pro.status.legacyUnlocked": "Pro · Tidlig støttespiller",
  "pro.status.unlocked": "Pro låst opp",
  "pro.theme.preview.status": "Forhåndsviser Pro-tema",
  "pro.theme.status.free": "Gratis temaer er klare nå. Lås opp Pro for å beholde alle avanserte temaer.",
  "pro.theme.status.preview": "Forhåndsvisning av et Pro-tema. Lås opp Pro for å beholde den etter at forhåndsvisningen er ferdig.",
  "pro.theme.status.unlocked": "Alle temaer er låst opp på denne Mac."
}
src/Apptag/Localization/nl.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Tips later opnieuw tonen",
  "usageTips.closeReminder.message": "Je kunt tips opnieuw tonen via Instellingen > Algemeen door “Gebruikstips verbergen” uit te schakelen.",
  "usageTips.closeReminder.doNotRemind": "Begrepen. Niet meer herinneren wanneer ik tips sluit.",
  "usageTips.closeReminder.previewCallout": "Schakel dit uit om tips opnieuw te tonen"
  "usageTips.closeReminder.previewCallout": "Schakel dit uit om tips opnieuw te tonen",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Voorbeeld",
  "pro.card.restore": "Aankopen herstellen",
  "pro.card.summary.free": "Ontgrendel premiumthema's, import en export van lay-outs, onbeperkte notities en opgeslagen handmatige app-sortering.",
  "pro.card.summary.legacy": "Bedankt voor het vroegtijdig steunen van TagLauncher. Uw Pro-toegang is al actief.",
  "pro.card.summary.unlocked": "Pro is actief op deze Mac. Premiumthema's en geavanceerde lay-outtools zijn klaar.",
  "pro.card.title.free": "Upgrade naar Pro",
  "pro.card.title.legacy": "Pro ontgrendeld",
  "pro.card.title.unlocked": "Pro ontgrendeld",
  "pro.card.unlock": "Ontgrendel Pro",
  "pro.feature.export": "Lay-out exporteren",
  "pro.feature.export.benefit": "Bewaar uw huidige tags, bestelling en lay-out als herbruikbare back-up.",
  "pro.feature.import": "Lay-out importeren",
  "pro.feature.import.benefit": "Herstel een opgeslagen TagLauncher-indeling op deze Mac in één stap.",
  "pro.feature.notes": "Onbeperkt notities",
  "pro.feature.notes.benefit": "Bewaar notities voor elke app zonder de gratis limiet van vijf notities.",
  "pro.feature.sorting": "Opgeslagen app-sortering",
  "pro.feature.sorting.benefit": "Bewaar uw handmatige app-volgorde nadat u de bewerkingsmodus heeft verlaten.",
  "pro.feature.themes": "Premium-thema's",
  "pro.feature.themes.benefit": "Ontgrendel elk geavanceerd App Grid-thema en bewaar het permanent.",
  "pro.notes.limitReached": "De gratis versie kan notities opslaan voor maximaal 5 apps. Ontgrendel Pro om onbeperkt notities op te slaan.",
  "pro.notes.remaining": "De gratis versie bevat %limit% app-notities. %remaining% resterend.",
  "pro.operation.cancelled": "Aankoop geannuleerd.",
  "pro.operation.loading": "Toegang tot Pro controleren…",
  "pro.operation.pending": "Wachten op App Store bevestiging…",
  "pro.operation.purchasing": "Pro kopen…",
  "pro.operation.restoreNotFound": "Er is geen eerdere Pro-aankoop gevonden voor deze Apple ID.",
  "pro.operation.restoring": "Aankoop herstellen…",
  "pro.prompt.export": "Lay-outexport is een Pro-functie. Ontgrendel Pro om een ​​back-up te maken van uw huidige tags, volgorde en lay-out.",
  "pro.prompt.import": "Het importeren van lay-outs is een functie van Pro. Ontgrendel Pro om een ​​opgeslagen lay-out op deze Mac te herstellen.",
  "pro.prompt.notes": "Onbeperkte app-notities zijn onderdeel van Pro. Ontgrendel Pro om notities voor elke app op te slaan.",
  "pro.prompt.sorting": "Opgeslagen handmatige app-sortering is onderdeel van Pro. Ontgrendel Pro om uw aangepaste volgorde te behouden.",
  "pro.prompt.themes": "Dit thema is onderdeel van Pro. Ontgrendel Pro om geavanceerde App Grid-thema's te blijven gebruiken.",
  "pro.purchase.failed": "Kan de aankoop momenteel niet voltooien. Probeer het opnieuw.",
  "pro.purchase.unavailable": "Pro aankoop is momenteel niet beschikbaar. Probeer het later opnieuw.",
  "pro.purchase.verificationFailed": "App Store verificatie mislukt. Probeer het opnieuw.",
  "pro.restore.failed": "Kan aankopen momenteel niet herstellen. Probeer het opnieuw.",
  "pro.sorting.previewToast": "Alleen voorbeeld. Ontgrendel Pro om het handmatig sorteren van apps op te slaan.",
  "pro.status.free": "Vrij",
  "pro.status.legacyUnlocked": "Pro · Vroege supporter",
  "pro.status.unlocked": "Pro ontgrendeld",
  "pro.theme.preview.status": "Een voorbeeld bekijken van het Pro-thema",
  "pro.theme.status.free": "Gratis thema's zijn nu klaar. Ontgrendel Pro om elk geavanceerd thema te behouden.",
  "pro.theme.status.preview": "Een voorbeeld bekijken van een Pro thema. Ontgrendel Pro om deze te behouden nadat de preview is afgelopen.",
  "pro.theme.status.unlocked": "Alle thema's zijn ontgrendeld op deze Mac."
}
src/Apptag/Localization/nn.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Vis tips att seinare",
  "usageTips.closeReminder.message": "Du kan vise tips att i Innstillingar > Generelt ved å fjerne avmerkinga for «Skjul brukstips».",
  "usageTips.closeReminder.doNotRemind": "Greitt. Ikkje minn meg på dette neste gong eg lukkar tips.",
  "usageTips.closeReminder.previewCallout": "Fjern denne avmerkinga for å vise tips att"
  "usageTips.closeReminder.previewCallout": "Fjern denne avmerkinga for å vise tips att",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Forhåndsvisning",
  "pro.card.restore": "Gjenopprett kjøp",
  "pro.card.summary.free": "Lås opp premium-temaer, import og eksport av layout, ubegrensede notater og lagret manuell appsortering.",
  "pro.card.summary.legacy": "Takk for at du støttet TagLauncher tidlig. Din Pro-tilgang er allerede aktiv.",
  "pro.card.summary.unlocked": "Pro er aktiv på denne Mac. Premium-temaer og avanserte layoutverktøy er klare.",
  "pro.card.title.free": "Oppgrader til Pro",
  "pro.card.title.legacy": "Pro låst opp",
  "pro.card.title.unlocked": "Pro låst opp",
  "pro.card.unlock": "Lås opp Pro",
  "pro.feature.export": "Eksport av layout",
  "pro.feature.export.benefit": "Lagre gjeldende tagger, bestilling og layout som en gjenbrukbar sikkerhetskopi.",
  "pro.feature.import": "Import av layout",
  "pro.feature.import.benefit": "Gjenopprett et lagret TagLauncher-oppsett på denne Mac i ett trinn.",
  "pro.feature.notes": "Ubegrensede notater",
  "pro.feature.notes.benefit": "Lagre notater for hver app uten den gratis grensen på 5 noter.",
  "pro.feature.sorting": "Lagret appsortering",
  "pro.feature.sorting.benefit": "Behold den manuelle appbestillingen din etter at du forlater redigeringsmodus.",
  "pro.feature.themes": "Premium-temaer",
  "pro.feature.themes.benefit": "Lås opp hvert avansert App Grid-tema og behold det permanent.",
  "pro.notes.limitReached": "Gratisversjonen kan lagre notater for opptil 5 apper. Lås opp Pro for å lagre ubegrensede notater.",
  "pro.notes.remaining": "Gratisversjonen inkluderer %limit% appnotater. %remaining% gjenstår.",
  "pro.operation.cancelled": "Kjøp kansellert.",
  "pro.operation.loading": "Sjekker Pro-tilgang …",
  "pro.operation.pending": "Venter på App Store bekreftelse...",
  "pro.operation.purchasing": "Kjøper Pro...",
  "pro.operation.restoreNotFound": "Ingen tidligere Pro-kjøp ble funnet for denne Apple ID.",
  "pro.operation.restoring": "Gjenoppretter kjøpet …",
  "pro.prompt.export": "Layouteksport er en Pro-funksjon. Lås opp Pro for å sikkerhetskopiere gjeldende tagger, rekkefølge og layout.",
  "pro.prompt.import": "Layoutimport er en Pro-funksjon. Lås opp Pro for å gjenopprette et lagret oppsett på denne Mac.",
  "pro.prompt.notes": "Ubegrensede appnotater er en del av Pro. Lås opp Pro for å lagre notater for hver app.",
  "pro.prompt.sorting": "Lagret manuell appsortering er en del av Pro. Lås opp Pro for å beholde din egendefinerte rekkefølge.",
  "pro.prompt.themes": "Dette temaet er en del av Pro. Lås opp Pro for å fortsette å bruke avanserte App Grid-temaer.",
  "pro.purchase.failed": "Kan ikke fullføre kjøpet akkurat nå. Vennligst prøv igjen.",
  "pro.purchase.unavailable": "Pro kjøp er utilgjengelig akkurat nå. Vennligst prøv igjen senere.",
  "pro.purchase.verificationFailed": "App Store bekreftelse mislyktes. Vennligst prøv igjen.",
  "pro.restore.failed": "Kan ikke gjenopprette kjøp akkurat nå. Vennligst prøv igjen.",
  "pro.sorting.previewToast": "Kun forhåndsvisning. Lås opp Pro for å lagre manuell appsortering.",
  "pro.status.free": "Gratis",
  "pro.status.legacyUnlocked": "Pro · Tidlig støttespiller",
  "pro.status.unlocked": "Pro låst opp",
  "pro.theme.preview.status": "Forhåndsviser Pro-tema",
  "pro.theme.status.free": "Gratis temaer er klare nå. Lås opp Pro for å beholde alle avanserte temaer.",
  "pro.theme.status.preview": "Forhåndsvisning av et Pro-tema. Lås opp Pro for å beholde den etter at forhåndsvisningen er ferdig.",
  "pro.theme.status.unlocked": "Alle temaer er låst opp på denne Mac."
}
src/Apptag/Localization/no.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Vis tips igjen senere",
  "usageTips.closeReminder.message": "Du kan vise tips igjen i Innstillinger > Generelt ved å fjerne avmerkingen for «Skjul brukstips».",
  "usageTips.closeReminder.doNotRemind": "Skjønner. Ikke minn meg på dette neste gang jeg lukker tips.",
  "usageTips.closeReminder.previewCallout": "Fjern denne avmerkingen for å vise tips igjen"
  "usageTips.closeReminder.previewCallout": "Fjern denne avmerkingen for å vise tips igjen",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Forhåndsvisning",
  "pro.card.restore": "Gjenopprett kjøp",
  "pro.card.summary.free": "Lås opp premium-temaer, import og eksport av layout, ubegrensede notater og lagret manuell appsortering.",
  "pro.card.summary.legacy": "Takk for at du støttet TagLauncher tidlig. Din Pro-tilgang er allerede aktiv.",
  "pro.card.summary.unlocked": "Pro er aktiv på denne Mac. Premium-temaer og avanserte layoutverktøy er klare.",
  "pro.card.title.free": "Oppgrader til Pro",
  "pro.card.title.legacy": "Pro låst opp",
  "pro.card.title.unlocked": "Pro låst opp",
  "pro.card.unlock": "Lås opp Pro",
  "pro.feature.export": "Eksport av layout",
  "pro.feature.export.benefit": "Lagre gjeldende tagger, bestilling og layout som en gjenbrukbar sikkerhetskopi.",
  "pro.feature.import": "Import av layout",
  "pro.feature.import.benefit": "Gjenopprett et lagret TagLauncher-oppsett på denne Mac i ett trinn.",
  "pro.feature.notes": "Ubegrensede notater",
  "pro.feature.notes.benefit": "Lagre notater for hver app uten den gratis grensen på 5 noter.",
  "pro.feature.sorting": "Lagret appsortering",
  "pro.feature.sorting.benefit": "Behold den manuelle appbestillingen din etter at du forlater redigeringsmodus.",
  "pro.feature.themes": "Premium-temaer",
  "pro.feature.themes.benefit": "Lås opp hvert avansert App Grid-tema og behold det permanent.",
  "pro.notes.limitReached": "Gratisversjonen kan lagre notater for opptil 5 apper. Lås opp Pro for å lagre ubegrensede notater.",
  "pro.notes.remaining": "Gratisversjonen inkluderer %limit% appnotater. %remaining% gjenstår.",
  "pro.operation.cancelled": "Kjøp kansellert.",
  "pro.operation.loading": "Sjekker Pro-tilgang …",
  "pro.operation.pending": "Venter på App Store bekreftelse...",
  "pro.operation.purchasing": "Kjøper Pro...",
  "pro.operation.restoreNotFound": "Ingen tidligere Pro-kjøp ble funnet for denne Apple ID.",
  "pro.operation.restoring": "Gjenoppretter kjøpet …",
  "pro.prompt.export": "Layouteksport er en Pro-funksjon. Lås opp Pro for å sikkerhetskopiere gjeldende tagger, rekkefølge og layout.",
  "pro.prompt.import": "Layoutimport er en Pro-funksjon. Lås opp Pro for å gjenopprette et lagret oppsett på denne Mac.",
  "pro.prompt.notes": "Ubegrensede appnotater er en del av Pro. Lås opp Pro for å lagre notater for hver app.",
  "pro.prompt.sorting": "Lagret manuell appsortering er en del av Pro. Lås opp Pro for å beholde din egendefinerte rekkefølge.",
  "pro.prompt.themes": "Dette temaet er en del av Pro. Lås opp Pro for å fortsette å bruke avanserte App Grid-temaer.",
  "pro.purchase.failed": "Kan ikke fullføre kjøpet akkurat nå. Vennligst prøv igjen.",
  "pro.purchase.unavailable": "Pro kjøp er utilgjengelig akkurat nå. Vennligst prøv igjen senere.",
  "pro.purchase.verificationFailed": "App Store bekreftelse mislyktes. Vennligst prøv igjen.",
  "pro.restore.failed": "Kan ikke gjenopprette kjøp akkurat nå. Vennligst prøv igjen.",
  "pro.sorting.previewToast": "Kun forhåndsvisning. Lås opp Pro for å lagre manuell appsortering.",
  "pro.status.free": "Gratis",
  "pro.status.legacyUnlocked": "Pro · Tidlig støttespiller",
  "pro.status.unlocked": "Pro låst opp",
  "pro.theme.preview.status": "Forhåndsviser Pro-tema",
  "pro.theme.status.free": "Gratis temaer er klare nå. Lås opp Pro for å beholde alle avanserte temaer.",
  "pro.theme.status.preview": "Forhåndsvisning av et Pro-tema. Lås opp Pro for å beholde den etter at forhåndsvisningen er ferdig.",
  "pro.theme.status.unlocked": "Alle temaer er låst opp på denne Mac."
}
src/Apptag/Localization/pl.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Pokaż wskazówki ponownie później",
  "usageTips.closeReminder.message": "Możesz ponownie wyświetlić wskazówki w Ustawienia > Ogólne, odznaczając „Ukryj wskazówki użycia”.",
  "usageTips.closeReminder.doNotRemind": "Rozumiem. Nie przypominaj ponownie, gdy zamknę wskazówki.",
  "usageTips.closeReminder.previewCallout": "Odznacz tę opcję, aby ponownie pokazać wskazówki"
  "usageTips.closeReminder.previewCallout": "Odznacz tę opcję, aby ponownie pokazać wskazówki",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Zapowiedź",
  "pro.card.restore": "Przywróć zakupy",
  "pro.card.summary.free": "Odblokuj motywy premium, import i eksport układu, nieograniczoną liczbę notatek i zapisane ręczne sortowanie aplikacji.",
  "pro.card.summary.legacy": "Dziękujemy za wcześniejsze wsparcie TagLauncher. Twój dostęp do Pro jest już aktywny.",
  "pro.card.summary.unlocked": "Pro jest aktywny na tym Mac. Motywy premium i zaawansowane narzędzia do układania są gotowe.",
  "pro.card.title.free": "Uaktualnij do Pro",
  "pro.card.title.legacy": "Pro odblokowany",
  "pro.card.title.unlocked": "Pro odblokowany",
  "pro.card.unlock": "Odblokuj Pro",
  "pro.feature.export": "Eksport układu",
  "pro.feature.export.benefit": "Zapisz bieżące tagi, zamówienie i układ jako kopię zapasową do ponownego wykorzystania.",
  "pro.feature.import": "Import układu",
  "pro.feature.import.benefit": "Przywróć zapisany układ TagLauncher na tym Mac w jednym kroku.",
  "pro.feature.notes": "Nieograniczone notatki",
  "pro.feature.notes.benefit": "Zapisuj notatki dla każdej aplikacji bez bezpłatnego limitu 5 notatek.",
  "pro.feature.sorting": "Sortowanie zapisanych aplikacji",
  "pro.feature.sorting.benefit": "Zachowaj ręczną kolejność aplikacji po wyjściu z trybu edycji.",
  "pro.feature.themes": "Motywy premium",
  "pro.feature.themes.benefit": "Odblokuj każdy zaawansowany motyw App Grid i zachowaj go na stałe.",
  "pro.notes.limitReached": "Darmowa wersja może zapisywać notatki dla maksymalnie 5 aplikacji. Odblokuj Pro, aby zapisywać nieograniczoną liczbę notatek.",
  "pro.notes.remaining": "Darmowa wersja zawiera %limit% notatki dotyczące aplikacji. pozostało %remaining%.",
  "pro.operation.cancelled": "Zakup anulowany.",
  "pro.operation.loading": "Sprawdzam dostęp Pro…",
  "pro.operation.pending": "Oczekiwanie na potwierdzenie App Store…",
  "pro.operation.purchasing": "Kupuję Pro…",
  "pro.operation.restoreNotFound": "Nie znaleziono żadnego poprzedniego zakupu Pro dla tego Apple ID.",
  "pro.operation.restoring": "Przywracam zakup…",
  "pro.prompt.export": "Eksport układu to funkcja Pro. Odblokuj Pro, aby utworzyć kopię zapasową bieżących tagów, kolejności i układu.",
  "pro.prompt.import": "Import układu to funkcja Pro. Odblokuj Pro, aby przywrócić zapisany układ na tym Mac.",
  "pro.prompt.notes": "Nieograniczone notatki z aplikacji są częścią Pro. Odblokuj Pro, aby zapisywać notatki dla każdej aplikacji.",
  "pro.prompt.sorting": "Zapisane ręczne sortowanie aplikacji jest częścią Pro. Odblokuj Pro, aby zachować niestandardowe zamówienie.",
  "pro.prompt.themes": "Ten motyw jest częścią Pro. Odblokuj Pro, aby nadal korzystać z zaawansowanych motywów App Grid.",
  "pro.purchase.failed": "Nie można w tej chwili sfinalizować zakupu. Spróbuj ponownie.",
  "pro.purchase.unavailable": "Zakup Pro jest obecnie niedostępny. Spróbuj ponownie później.",
  "pro.purchase.verificationFailed": "Weryfikacja App Store nie powiodła się. Spróbuj ponownie.",
  "pro.restore.failed": "W tej chwili nie można przywrócić zakupów. Spróbuj ponownie.",
  "pro.sorting.previewToast": "Tylko podgląd. Odblokuj Pro, aby zapisać ręczne sortowanie aplikacji.",
  "pro.status.free": "Bezpłatny",
  "pro.status.legacyUnlocked": "Pro · Wczesny zwolennik",
  "pro.status.unlocked": "Pro odblokowany",
  "pro.theme.preview.status": "Podgląd motywu Pro",
  "pro.theme.status.free": "Darmowe motywy są już gotowe. Odblokuj Pro, aby zachować każdy zaawansowany motyw.",
  "pro.theme.status.preview": "Podgląd motywu Pro. Odblokuj Pro, aby zachować go po zakończeniu podglądu.",
  "pro.theme.status.unlocked": "Wszystkie motywy są odblokowane na tym Mac."
}
src/Apptag/Localization/pt-BR.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Ver dicas novamente depois",
  "usageTips.closeReminder.message": "Você pode mostrar as dicas novamente em Ajustes > Geral desmarcando “Ocultar dicas de uso”.",
  "usageTips.closeReminder.doNotRemind": "Entendi. Não lembrar de novo quando eu fechar as dicas.",
  "usageTips.closeReminder.previewCallout": "Desmarque esta opção para ver as dicas novamente"
  "usageTips.closeReminder.previewCallout": "Desmarque esta opção para ver as dicas novamente",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Visualização",
  "pro.card.restore": "Restaurar compras",
  "pro.card.summary.free": "Desbloqueie temas premium, importação e exportação de layout, notas ilimitadas e classificação manual de aplicativos salvos.",
  "pro.card.summary.legacy": "Obrigado por apoiar TagLauncher antecipadamente. Seu acesso Pro já está ativo.",
  "pro.card.summary.unlocked": "Pro está ativo neste Mac. Temas premium e ferramentas de layout avançadas estão prontos.",
  "pro.card.title.free": "Atualizar para Pro",
  "pro.card.title.legacy": "Pro desbloqueado",
  "pro.card.title.unlocked": "Pro desbloqueado",
  "pro.card.unlock": "Desbloquear Pro",
  "pro.feature.export": "Exportação de layout",
  "pro.feature.export.benefit": "Salve suas tags, pedidos e layout atuais como um backup reutilizável.",
  "pro.feature.import": "Importação de layout",
  "pro.feature.import.benefit": "Restaure um layout TagLauncher salvo neste Mac em uma única etapa.",
  "pro.feature.notes": "Notas ilimitadas",
  "pro.feature.notes.benefit": "Salve notas para cada aplicativo sem o limite gratuito de 5 notas.",
  "pro.feature.sorting": "Classificação de aplicativos salvos",
  "pro.feature.sorting.benefit": "Mantenha a ordem manual do aplicativo depois de sair do modo de edição.",
  "pro.feature.themes": "Temas premium",
  "pro.feature.themes.benefit": "Desbloqueie todos os temas avançados do App Grid e mantenha-os permanentemente.",
  "pro.notes.limitReached": "A versão gratuita pode salvar notas para até 5 aplicativos. Desbloqueie Pro para salvar notas ilimitadas.",
  "pro.notes.remaining": "A versão gratuita inclui %limit% notas do aplicativo. %remaining% restante.",
  "pro.operation.cancelled": "Compra cancelada.",
  "pro.operation.loading": "Verificando acesso Pro…",
  "pro.operation.pending": "Aguardando confirmação de App Store…",
  "pro.operation.purchasing": "Comprando Pro…",
  "pro.operation.restoreNotFound": "Nenhuma compra anterior de Pro foi encontrada para este Apple ID.",
  "pro.operation.restoring": "Restaurando compra…",
  "pro.prompt.export": "A exportação de layout é um recurso Pro. Desbloqueie Pro para fazer backup de suas tags, ordem e layout atuais.",
  "pro.prompt.import": "A importação de layout é um recurso Pro. Desbloqueie Pro para restaurar um layout salvo neste Mac.",
  "pro.prompt.notes": "Notas de aplicativos ilimitadas fazem parte do Pro. Desbloqueie Pro para salvar notas para cada aplicativo.",
  "pro.prompt.sorting": "A classificação manual de aplicativos salvos faz parte do Pro. Desbloqueie Pro para manter seu pedido personalizado.",
  "pro.prompt.themes": "Este tema faz parte do Pro. Desbloqueie Pro para continuar usando temas App Grid avançados.",
  "pro.purchase.failed": "Não é possível concluir a compra no momento. Por favor, tente novamente.",
  "pro.purchase.unavailable": "A compra de Pro não está disponível no momento. Por favor, tente novamente mais tarde.",
  "pro.purchase.verificationFailed": "A verificação de App Store falhou. Por favor, tente novamente.",
  "pro.restore.failed": "Não é possível restaurar as compras no momento. Por favor, tente novamente.",
  "pro.sorting.previewToast": "Apenas visualização. Desbloqueie Pro para salvar a classificação manual de aplicativos.",
  "pro.status.free": "Livre",
  "pro.status.legacyUnlocked": "Pro · Apoiador inicial",
  "pro.status.unlocked": "Pro desbloqueado",
  "pro.theme.preview.status": "Visualizando o tema Pro",
  "pro.theme.status.free": "Os temas gratuitos estão prontos agora. Desbloqueie Pro para manter todos os temas avançados.",
  "pro.theme.status.preview": "Visualizando um tema Pro. Desbloqueie Pro para mantê-lo após o término da visualização.",
  "pro.theme.status.unlocked": "Todos os temas são desbloqueados neste Mac."
}
src/Apptag/Localization/ro.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Afișează sfaturile mai târziu",
  "usageTips.closeReminder.message": "Poți afișa din nou sfaturile în Setări > General debifând „Ascunde sfaturile de utilizare”.",
  "usageTips.closeReminder.doNotRemind": "Am înțeles. Nu-mi mai aminti când închid sfaturile.",
  "usageTips.closeReminder.previewCallout": "Debifează această opțiune pentru a afișa sfaturile"
  "usageTips.closeReminder.previewCallout": "Debifează această opțiune pentru a afișa sfaturile",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Previzualizare",
  "pro.card.restore": "Restabiliți achizițiile",
  "pro.card.summary.free": "Deblocați teme premium, import și export de aspect, note nelimitate și sortarea manuală a aplicațiilor salvate.",
  "pro.card.summary.legacy": "Vă mulțumim că ați susținut TagLauncher devreme. Accesul dvs. Pro este deja activ.",
  "pro.card.summary.unlocked": "Pro este activ pe acest Mac. Temele premium și instrumentele avansate de aspect sunt gata.",
  "pro.card.title.free": "Faceți upgrade la Pro",
  "pro.card.title.legacy": "Pro deblocat",
  "pro.card.title.unlocked": "Pro deblocat",
  "pro.card.unlock": "Deblocați Pro",
  "pro.feature.export": "Export de aspect",
  "pro.feature.export.benefit": "Salvați etichetele actuale, comanda și aspectul ca o copie de rezervă reutilizabilă.",
  "pro.feature.import": "import aspect",
  "pro.feature.import.benefit": "Restaurați un aspect TagLauncher salvat pe acest Mac într-un singur pas.",
  "pro.feature.notes": "Note nelimitate",
  "pro.feature.notes.benefit": "Salvați notele pentru fiecare aplicație fără limita gratuită de 5 note.",
  "pro.feature.sorting": "sortarea aplicațiilor salvate",
  "pro.feature.sorting.benefit": "Păstrați ordinea manuală a aplicațiilor după ce părăsiți modul de editare.",
  "pro.feature.themes": "Teme premium",
  "pro.feature.themes.benefit": "Deblocați fiecare temă avansată App Grid și păstrați-o permanent.",
  "pro.notes.limitReached": "Versiunea gratuită poate salva note pentru până la 5 aplicații. Deblocați Pro pentru a salva note nelimitate.",
  "pro.notes.remaining": "versiunea gratuită include %limit% note de aplicație. %remaining% rămas.",
  "pro.operation.cancelled": "Achiziție anulată.",
  "pro.operation.loading": "Se verifică accesul Pro...",
  "pro.operation.pending": "Se așteaptă confirmarea App Store...",
  "pro.operation.purchasing": "Cumpărând Pro…",
  "pro.operation.restoreNotFound": "Nu a fost găsită nicio achiziție anterioară Pro pentru acest Apple ID.",
  "pro.operation.restoring": "Restabilirea achiziției...",
  "pro.prompt.export": "Exportul aspectului este o caracteristică Pro. Deblocați Pro pentru a face backup pentru etichetele, ordinea și aspectul actual.",
  "pro.prompt.import": "Importul aspectului este o caracteristică Pro. Deblocați Pro pentru a restabili un aspect salvat pe acest Mac.",
  "pro.prompt.notes": "Notele nelimitate ale aplicației fac parte din Pro. Deblocați Pro pentru a salva note pentru fiecare aplicație.",
  "pro.prompt.sorting": "sortarea manuală a aplicațiilor salvate face parte din Pro. Deblocați Pro pentru a vă păstra comanda personalizată.",
  "pro.prompt.themes": "Această temă face parte din Pro. Deblocați Pro pentru a continua să utilizați teme avansate App Grid.",
  "pro.purchase.failed": "Nu se poate finaliza achiziția acum. Vă rugăm să încercați din nou.",
  "pro.purchase.unavailable": "achiziția Pro nu este disponibilă în acest moment. Vă rugăm să încercați din nou mai târziu.",
  "pro.purchase.verificationFailed": "verificarea App Store a eșuat. Vă rugăm să încercați din nou.",
  "pro.restore.failed": "Nu se pot restabili achizițiile acum. Vă rugăm să încercați din nou.",
  "pro.sorting.previewToast": "Doar previzualizare. Deblocați Pro pentru a salva sortarea manuală a aplicațiilor.",
  "pro.status.free": "Gratuit",
  "pro.status.legacyUnlocked": "Pro · Susținător timpuriu",
  "pro.status.unlocked": "Pro deblocat",
  "pro.theme.preview.status": "Previzualizarea temei Pro",
  "pro.theme.status.free": "Temele gratuite sunt gata acum. Deblocați Pro pentru a păstra fiecare temă avansată.",
  "pro.theme.status.preview": "Previzualizarea unei teme Pro. Deblocați Pro pentru a-l păstra după terminarea previzualizării.",
  "pro.theme.status.unlocked": "Toate temele sunt deblocate pe acest Mac."
}
src/Apptag/Localization/ru.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Показать советы позже",
  "usageTips.closeReminder.message": "Советы можно снова включить в Настройки > Общие, сняв флажок «Скрывать советы по использованию».",
  "usageTips.closeReminder.doNotRemind": "Понятно. Больше не напоминать при закрытии советов.",
  "usageTips.closeReminder.previewCallout": "Снимите этот флажок, чтобы снова показать советы"
  "usageTips.closeReminder.previewCallout": "Снимите этот флажок, чтобы снова показать советы",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Предварительный просмотр",
  "pro.card.restore": "Восстановить покупки",
  "pro.card.summary.free": "Разблокируйте премиум-темы, импорт и экспорт макетов, неограниченное количество заметок и сохраненную сортировку приложений вручную.",
  "pro.card.summary.legacy": "Спасибо за раннюю поддержку TagLauncher. Ваш доступ к Pro уже активен.",
  "pro.card.summary.unlocked": "Pro активен на этом Mac. Премиум-темы и расширенные инструменты макетирования готовы.",
  "pro.card.title.free": "Обновите до Pro",
  "pro.card.title.legacy": "Pro разблокирован",
  "pro.card.title.unlocked": "Pro разблокирован",
  "pro.card.unlock": "Разблокировать Pro",
  "pro.feature.export": "Экспорт макета",
  "pro.feature.export.benefit": "Сохраните текущие теги, порядок и макет в качестве резервной копии многократного использования.",
  "pro.feature.import": "Импорт макета",
  "pro.feature.import.benefit": "Восстановите сохраненный макет TagLauncher на этом Mac за один шаг.",
  "pro.feature.notes": "Неограниченное количество заметок",
  "pro.feature.notes.benefit": "Сохраняйте заметки для каждого приложения без бесплатного ограничения в 5 заметок.",
  "pro.feature.sorting": "Сортировка сохраненных приложений",
  "pro.feature.sorting.benefit": "Сохраняйте порядок приложений вручную после выхода из режима редактирования.",
  "pro.feature.themes": "Премиум темы",
  "pro.feature.themes.benefit": "Разблокируйте все расширенные темы App Grid и сохраните их навсегда.",
  "pro.notes.limitReached": "Бесплатная версия может сохранять заметки для 5 приложений. Разблокируйте Pro, чтобы сохранять неограниченное количество заметок.",
  "pro.notes.remaining": "Бесплатная версия включает в себя %limit% заметок приложения. Осталось %remaining%.",
  "pro.operation.cancelled": "Покупка отменена.",
  "pro.operation.loading": "Проверка доступа к Pro…",
  "pro.operation.pending": "Ожидание подтверждения App Store…",
  "pro.operation.purchasing": "Покупка Pro…",
  "pro.operation.restoreNotFound": "Для этого Apple ID не было обнаружено предыдущих покупок Pro.",
  "pro.operation.restoring": "Восстановление покупки…",
  "pro.prompt.export": "Экспорт макетов — это функция Pro. Разблокируйте Pro, чтобы создавать резервные копии текущих тегов, порядка и макета.",
  "pro.prompt.import": "Импорт макетов — это функция Pro. Разблокируйте Pro, чтобы восстановить сохраненный макет на этом Mac.",
  "pro.prompt.notes": "Неограниченное количество заметок приложений является частью Pro. Разблокируйте Pro, чтобы сохранять заметки для каждого приложения.",
  "pro.prompt.sorting": "Сохраненная сортировка приложений вручную является частью Pro. Разблокируйте Pro, чтобы сохранить свой индивидуальный заказ.",
  "pro.prompt.themes": "Эта тема является частью Pro. Разблокируйте Pro, чтобы продолжать использовать расширенные темы App Grid.",
  "pro.purchase.failed": "Невозможно совершить покупку прямо сейчас. Пожалуйста, попробуйте еще раз.",
  "pro.purchase.unavailable": "Покупка Pro сейчас недоступна. Пожалуйста, повторите попытку позже.",
  "pro.purchase.verificationFailed": "Проверка App Store не удалась. Пожалуйста, попробуйте еще раз.",
  "pro.restore.failed": "Сейчас невозможно восстановить покупки. Пожалуйста, попробуйте еще раз.",
  "pro.sorting.previewToast": "Только предварительный просмотр. Разблокируйте Pro, чтобы сохранить ручную сортировку приложений.",
  "pro.status.free": "Бесплатно",
  "pro.status.legacyUnlocked": "Pro · Ранний сторонник",
  "pro.status.unlocked": "Pro разблокирован",
  "pro.theme.preview.status": "Предварительный просмотр темы Pro",
  "pro.theme.status.free": "Бесплатные темы уже готовы. Разблокируйте Pro, чтобы сохранить все расширенные темы.",
  "pro.theme.status.preview": "Предварительный просмотр темы Pro. Разблокируйте Pro, чтобы сохранить его после завершения предварительного просмотра.",
  "pro.theme.status.unlocked": "На этом Mac разблокированы все темы."
}
src/Apptag/Localization/sr-Cyrl.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Поново прикажи савете касније",
  "usageTips.closeReminder.message": "Савете можеш поново приказати у Подешавања > Опште тако што ћеш искључити „Сакриј савете за коришћење”.",
  "usageTips.closeReminder.doNotRemind": "Разумем. Не подсећај ме следећи пут када затворим савете.",
  "usageTips.closeReminder.previewCallout": "Искључи ову опцију да поново видиш савете"
  "usageTips.closeReminder.previewCallout": "Искључи ову опцију да поново видиш савете",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Преглед",
  "pro.card.restore": "Ресторе Пурцхасес",
  "pro.card.summary.free": "Откључајте премијум теме, увоз и извоз изгледа, неограничене белешке и сачувано ручно сортирање апликација.",
  "pro.card.summary.legacy": "Хвала што сте рано подржали TagLauncher. Ваш Pro приступ је већ активан.",
  "pro.card.summary.unlocked": "Pro је активан на овом Mac-у. Премиум теме и напредни алати за распоред су спремни.",
  "pro.card.title.free": "Надоградите на Pro",
  "pro.card.title.legacy": "Pro откључан",
  "pro.card.title.unlocked": "Pro откључан",
  "pro.card.unlock": "Откључај Pro",
  "pro.feature.export": "Извоз распореда",
  "pro.feature.export.benefit": "Сачувајте своје тренутне ознаке, редослед и изглед као резервну копију за вишекратну употребу.",
  "pro.feature.import": "Увоз распореда",
  "pro.feature.import.benefit": "Вратите сачувани изглед TagLauncher-а на овом Mac-у у једном кораку.",
  "pro.feature.notes": "Неограничене белешке",
  "pro.feature.notes.benefit": "Сачувајте белешке за сваку апликацију без бесплатног ограничења од 5 нота.",
  "pro.feature.sorting": "Сачувано сортирање апликација",
  "pro.feature.sorting.benefit": "Задржите ручну наруџбу апликација након што напустите режим уређивања.",
  "pro.feature.themes": "Премиум теме",
  "pro.feature.themes.benefit": "Откључајте све напредне теме App Grid и задржите их трајно.",
  "pro.notes.limitReached": "Бесплатна верзија може да сачува белешке за до 5 апликација. Откључајте Pro да бисте сачували неограничене белешке.",
  "pro.notes.remaining": "Бесплатна верзија укључује %limit% белешки о апликацијама. Преостало је %remaining%.",
  "pro.operation.cancelled": "Куповина је отказана.",
  "pro.operation.loading": "Проверава се Pro приступ…",
  "pro.operation.pending": "Чека се потврда App Store-а…",
  "pro.operation.purchasing": "Куповина Pro…",
  "pro.operation.restoreNotFound": "За овај Apple ID није пронађена претходна Pro куповина.",
  "pro.operation.restoring": "Враћање куповине…",
  "pro.prompt.export": "Извоз распореда је Pro функција. Откључајте Pro да бисте направили резервну копију тренутних ознака, редоследа и изгледа.",
  "pro.prompt.import": "Увоз распореда је Pro функција. Откључајте Pro да бисте вратили сачувани изглед на овом Mac-у.",
  "pro.prompt.notes": "Неограничене белешке о апликацијама су део Pro. Откључајте Pro да бисте сачували белешке за сваку апликацију.",
  "pro.prompt.sorting": "Сачувано ручно сортирање апликација је део Pro. Откључајте Pro да бисте задржали свој прилагођени редослед.",
  "pro.prompt.themes": "Ова тема је део Pro. Откључајте Pro да бисте наставили да користите напредне теме App Grid.",
  "pro.purchase.failed": "Тренутно није могуће довршити куповину. Покушајте поново.",
  "pro.purchase.unavailable": "Pro куповина тренутно није доступна. Покушајте поново касније.",
  "pro.purchase.verificationFailed": "App Store верификација није успела. Покушајте поново.",
  "pro.restore.failed": "Тренутно није могуће вратити куповине. Покушајте поново.",
  "pro.sorting.previewToast": "Само преглед. Откључајте Pro да бисте сачували ручно сортирање апликација.",
  "pro.status.free": "Бесплатно",
  "pro.status.legacyUnlocked": "Pro · Рани присталица",
  "pro.status.unlocked": "Pro откључан",
  "pro.theme.preview.status": "Преглед Pro теме",
  "pro.theme.status.free": "Бесплатне теме су сада спремне. Откључајте Pro да бисте задржали сваку напредну тему.",
  "pro.theme.status.preview": "Прегледате Pro тему. Откључајте Pro да бисте је задржали након завршетка прегледа.",
  "pro.theme.status.unlocked": "Све теме су откључане на овом Mac-у."
}
src/Apptag/Localization/sv.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Visa tips igen senare",
  "usageTips.closeReminder.message": "Du kan visa tips igen i Inställningar > Allmänt genom att avmarkera “Dölj användningstips”.",
  "usageTips.closeReminder.doNotRemind": "Jag förstår. Påminn mig inte igen när jag stänger tips.",
  "usageTips.closeReminder.previewCallout": "Avmarkera detta för att visa tips igen"
  "usageTips.closeReminder.previewCallout": "Avmarkera detta för att visa tips igen",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Förhandsvisning",
  "pro.card.restore": "Återställ inköp",
  "pro.card.summary.free": "Lås upp premiumteman, layoutimport och export, obegränsat antal anteckningar och sparad manuell appsortering.",
  "pro.card.summary.legacy": "Tack för att du stödde TagLauncher tidigt. Din Pro-åtkomst är redan aktiv.",
  "pro.card.summary.unlocked": "Pro är aktiv på denna Mac. Premiumteman och avancerade layoutverktyg är klara.",
  "pro.card.title.free": "Uppgradera till Pro",
  "pro.card.title.legacy": "Pro upplåst",
  "pro.card.title.unlocked": "Pro upplåst",
  "pro.card.unlock": "Lås upp Pro",
  "pro.feature.export": "Layoutexport",
  "pro.feature.export.benefit": "Spara dina nuvarande taggar, beställning och layout som en återanvändbar backup.",
  "pro.feature.import": "Layoutimport",
  "pro.feature.import.benefit": "Återställ en sparad TagLauncher layout på denna Mac i ett steg.",
  "pro.feature.notes": "Obegränsade anteckningar",
  "pro.feature.notes.benefit": "Spara anteckningar för varje app utan den kostnadsfria gränsen på 5 noter.",
  "pro.feature.sorting": "Sparad appsortering",
  "pro.feature.sorting.benefit": "Behåll din manuella appordning efter att du lämnat redigeringsläget.",
  "pro.feature.themes": "Premium-teman",
  "pro.feature.themes.benefit": "Lås upp alla avancerade App Grid-tema och behåll dem permanent.",
  "pro.notes.limitReached": "Gratisversionen kan spara anteckningar för upp till 5 appar. Lås upp Pro för att spara obegränsade anteckningar.",
  "pro.notes.remaining": "Gratisversionen inkluderar %limit% appanteckningar. %remaining% återstår.",
  "pro.operation.cancelled": "Köpet annullerats.",
  "pro.operation.loading": "Kontrollerar åtkomst till Pro...",
  "pro.operation.pending": "Väntar på App Store bekräftelse...",
  "pro.operation.purchasing": "Köper Pro...",
  "pro.operation.restoreNotFound": "Inget tidigare Pro-köp hittades för denna Apple ID.",
  "pro.operation.restoring": "Återställer köp...",
  "pro.prompt.export": "Layoutexport är en Pro-funktion. Lås upp Pro för att säkerhetskopiera dina nuvarande taggar, beställning och layout.",
  "pro.prompt.import": "Layoutimport är en Pro-funktion. Lås upp Pro för att återställa en sparad layout på denna Mac.",
  "pro.prompt.notes": "Obegränsade appanteckningar är en del av Pro. Lås upp Pro för att spara anteckningar för varje app.",
  "pro.prompt.sorting": "Sparad manuell appsortering är en del av Pro. Lås upp Pro för att behålla din anpassade ordning.",
  "pro.prompt.themes": "Detta tema är en del av Pro. Lås upp Pro för att fortsätta använda avancerade App Grid-teman.",
  "pro.purchase.failed": "Det går inte att slutföra köpet just nu. Försök igen.",
  "pro.purchase.unavailable": "Pro köp är inte tillgängligt just nu. Försök igen senare.",
  "pro.purchase.verificationFailed": "App Store verifiering misslyckades. Försök igen.",
  "pro.restore.failed": "Det går inte att återställa köp just nu. Försök igen.",
  "pro.sorting.previewToast": "Endast förhandsgranskning. Lås upp Pro för att spara manuell appsortering.",
  "pro.status.free": "Gratis",
  "pro.status.legacyUnlocked": "Pro · Tidig supporter",
  "pro.status.unlocked": "Pro upplåst",
  "pro.theme.preview.status": "Förhandsgranskning av Pro-tema",
  "pro.theme.status.free": "Gratis teman är klara nu. Lås upp Pro för att behålla alla avancerade teman.",
  "pro.theme.status.preview": "Förhandsgranskning av ett Pro-tema. Lås upp Pro för att behålla den efter att förhandsvisningen är slut.",
  "pro.theme.status.unlocked": "Alla teman är upplåsta på denna Mac."
}
src/Apptag/Localization/th.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "ดูเคล็ดลับอีกครั้งภายหลัง",
  "usageTips.closeReminder.message": "คุณสามารถแสดงเคล็ดลับอีกครั้งได้ใน การตั้งค่า > ทั่วไป โดยเอาเครื่องหมายถูกออกจาก “ซ่อนเคล็ดลับการใช้งาน”",
  "usageTips.closeReminder.doNotRemind": "เข้าใจแล้ว ไม่ต้องเตือนอีกเมื่อฉันปิดเคล็ดลับ",
  "usageTips.closeReminder.previewCallout": "เอาเครื่องหมายถูกออกเพื่อแสดงเคล็ดลับอีกครั้ง"
  "usageTips.closeReminder.previewCallout": "เอาเครื่องหมายถูกออกเพื่อแสดงเคล็ดลับอีกครั้ง",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "ดูตัวอย่าง",
  "pro.card.restore": "กู้คืนการซื้อ",
  "pro.card.summary.free": "ปลดล็อกธีมพรีเมียม นำเข้าและส่งออกเลย์เอาต์ บันทึกย่อไม่จำกัด และการจัดเรียงแอปที่บันทึกไว้ด้วยตนเอง",
  "pro.card.summary.legacy": "ขอขอบคุณที่สนับสนุน TagLauncher ก่อนกำหนด การเข้าถึง Pro ของคุณเปิดใช้งานแล้ว",
  "pro.card.summary.unlocked": "Pro ใช้งานอยู่ใน Mac นี้ ธีมพรีเมียมและเครื่องมือเค้าโครงขั้นสูงพร้อมแล้ว",
  "pro.card.title.free": "อัปเกรดเป็น Pro",
  "pro.card.title.legacy": "Pro ปลดล็อคแล้ว",
  "pro.card.title.unlocked": "Pro ปลดล็อคแล้ว",
  "pro.card.unlock": "ปลดล็อค Pro",
  "pro.feature.export": "ส่งออกเค้าโครง",
  "pro.feature.export.benefit": "บันทึกแท็ก ลำดับ และเลย์เอาต์ปัจจุบันของคุณเป็นข้อมูลสำรองที่นำมาใช้ซ้ำได้",
  "pro.feature.import": "นำเข้าเค้าโครง",
  "pro.feature.import.benefit": "กู้คืนเค้าโครง TagLauncher ที่บันทึกไว้บน Mac นี้ในขั้นตอนเดียว",
  "pro.feature.notes": "บันทึกไม่จำกัด",
  "pro.feature.notes.benefit": "บันทึกโน้ตสำหรับทุกแอปโดยไม่มีขีดจำกัด 5 โน้ตฟรี",
  "pro.feature.sorting": "การจัดเรียงแอปที่บันทึกไว้",
  "pro.feature.sorting.benefit": "เก็บลำดับแอปที่คุณกำหนดเองไว้หลังจากออกจากโหมดแก้ไข",
  "pro.feature.themes": "ธีมพรีเมียม",
  "pro.feature.themes.benefit": "ปลดล็อกธีม App Grid ขั้นสูงทุกอันและเก็บไว้อย่างถาวร",
  "pro.notes.limitReached": "เวอร์ชันฟรีสามารถบันทึกโน้ตได้สูงสุด 5 แอป ปลดล็อก Pro เพื่อบันทึกโน้ตได้ไม่จำกัด",
  "pro.notes.remaining": "เวอร์ชันฟรีมีบันทึกย่อของแอป %limit% รายการ เหลือ %remaining% รายการ",
  "pro.operation.cancelled": "การซื้อถูกยกเลิก",
  "pro.operation.loading": "กำลังตรวจสอบการเข้าถึง Pro...",
  "pro.operation.pending": "กำลังรอการยืนยัน App Store...",
  "pro.operation.purchasing": "กำลังซื้อ Pro...",
  "pro.operation.restoreNotFound": "ไม่พบการซื้อ Pro ก่อนหน้านี้สำหรับ Apple ID นี้",
  "pro.operation.restoring": "กำลังกู้คืนการซื้อ...",
  "pro.prompt.export": "การส่งออกเลย์เอาต์เป็นคุณสมบัติ Pro ปลดล็อก Pro เพื่อสำรองแท็ก ลำดับ และเลย์เอาต์ปัจจุบันของคุณ",
  "pro.prompt.import": "การนำเข้าเค้าโครงเป็นคุณลักษณะ Pro ปลดล็อก Pro เพื่อคืนค่าเค้าโครงที่บันทึกไว้ใน Mac นี้",
  "pro.prompt.notes": "บันทึกแอปไม่จำกัดเป็นส่วนหนึ่งของ Pro ปลดล็อก Pro เพื่อบันทึกโน้ตสำหรับทุกแอป",
  "pro.prompt.sorting": "การเรียงลำดับแอปด้วยตนเองที่บันทึกไว้เป็นส่วนหนึ่งของ Pro ปลดล็อก Pro เพื่อรักษาคำสั่งซื้อที่คุณกำหนดเอง",
  "pro.prompt.themes": "ธีมนี้เป็นส่วนหนึ่งของ Pro ปลดล็อก Pro เพื่อใช้ธีม App Grid ขั้นสูงต่อไป",
  "pro.purchase.failed": "ไม่สามารถดำเนินการซื้อให้เสร็จสิ้นได้ในขณะนี้ โปรดลองอีกครั้ง",
  "pro.purchase.unavailable": "Pro การซื้อไม่สามารถใช้งานได้ในขณะนี้ โปรดลองอีกครั้งในภายหลัง",
  "pro.purchase.verificationFailed": "App Store การตรวจสอบล้มเหลว โปรดลองอีกครั้ง",
  "pro.restore.failed": "ไม่สามารถกู้คืนการซื้อได้ในขณะนี้ โปรดลองอีกครั้ง",
  "pro.sorting.previewToast": "ดูตัวอย่างเท่านั้น ปลดล็อก Pro เพื่อบันทึกการเรียงลำดับแอปด้วยตนเอง",
  "pro.status.free": "ฟรี",
  "pro.status.legacyUnlocked": "Pro · ผู้สนับสนุนในช่วงแรก",
  "pro.status.unlocked": "Pro ปลดล็อคแล้ว",
  "pro.theme.preview.status": "กำลังดูตัวอย่างธีม Pro",
  "pro.theme.status.free": "ธีมฟรีพร้อมใช้งานแล้ว ปลดล็อก Pro เพื่อเก็บธีมขั้นสูงไว้ทุกอัน",
  "pro.theme.status.preview": "การดูตัวอย่างธีม Pro ปลดล็อก Pro เพื่อเก็บไว้หลังจากการดูตัวอย่างสิ้นสุดลง",
  "pro.theme.status.unlocked": "ปลดล็อคธีมทั้งหมดแล้วใน Mac นี้"
}
src/Apptag/Localization/tr.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "İpuçlarını daha sonra tekrar göster",
  "usageTips.closeReminder.message": "İpuçlarını Ayarlar > Genel bölümünde “Kullanım ipuçlarını gizle” seçimini kaldırarak tekrar gösterebilirsin.",
  "usageTips.closeReminder.doNotRemind": "Anladım. İpuçlarını kapatırken tekrar hatırlatma.",
  "usageTips.closeReminder.previewCallout": "İpuçlarını tekrar görmek için bu seçimi kaldır"
  "usageTips.closeReminder.previewCallout": "İpuçlarını tekrar görmek için bu seçimi kaldır",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Önizleme",
  "pro.card.restore": "Satın Alımları Geri Yükle",
  "pro.card.summary.free": "Premium temaların, düzenin içe ve dışa aktarımının, sınırsız notların ve kaydedilmiş manuel uygulama sıralamasının kilidini açın.",
  "pro.card.summary.legacy": "TagLauncher'ı erkenden desteklediğiniz için teşekkürler. Pro erişiminiz zaten etkin.",
  "pro.card.summary.unlocked": "Pro bu Mac üzerinde aktif. Premium temalar ve gelişmiş düzen araçları hazır.",
  "pro.card.title.free": "Pro'ya yükseltin",
  "pro.card.title.legacy": "Pro kilidi açıldı",
  "pro.card.title.unlocked": "Pro kilidi açıldı",
  "pro.card.unlock": "Pro kilidini açın",
  "pro.feature.export": "Düzeni dışa aktarma",
  "pro.feature.export.benefit": "Mevcut etiketlerinizi, sıranızı ve düzeninizi yeniden kullanılabilir bir yedek olarak kaydedin.",
  "pro.feature.import": "Düzeni içe aktarma",
  "pro.feature.import.benefit": "Bu Mac üzerinde kaydedilmiş bir TagLauncher düzenini tek adımda geri yükleyin.",
  "pro.feature.notes": "Sınırsız notlar",
  "pro.feature.notes.benefit": "Ücretsiz 5 nota sınırı olmadan her uygulama için not kaydedin.",
  "pro.feature.sorting": "Kayıtlı uygulama sıralaması",
  "pro.feature.sorting.benefit": "Düzenleme modundan çıktıktan sonra manuel uygulama sıranızı koruyun.",
  "pro.feature.themes": "Premium temalar",
  "pro.feature.themes.benefit": "Tüm gelişmiş App Grid temalarının kilidini açın ve onu kalıcı olarak saklayın.",
  "pro.notes.limitReached": "Ücretsiz sürüm 5 uygulamaya kadar not kaydedebilir. Sınırsız not kaydetmek için Pro kilidini açın.",
  "pro.notes.remaining": "Ücretsiz sürüm %limit% uygulama notu içerir. %remaining% kaldı.",
  "pro.operation.cancelled": "Satın alma iptal edildi.",
  "pro.operation.loading": "Pro erişimi kontrol ediliyor…",
  "pro.operation.pending": "App Store onayı bekleniyor…",
  "pro.operation.purchasing": "Pro satın alınıyor…",
  "pro.operation.restoreNotFound": "Bu Apple ID için daha önce herhangi bir Pro satın alımı bulunamadı.",
  "pro.operation.restoring": "Satın alma işlemi geri yükleniyor…",
  "pro.prompt.export": "Düzeni dışa aktarma bir Pro özelliğidir. Mevcut etiketlerinizi, siparişinizi ve düzeninizi yedeklemek için Pro kilidini açın.",
  "pro.prompt.import": "Düzeni içe aktarma bir Pro özelliğidir. Bu Mac üzerinde kayıtlı bir düzeni geri yüklemek için Pro kilidini açın.",
  "pro.prompt.notes": "Sınırsız uygulama notları Pro'nun bir parçasıdır. Her uygulamaya ilişkin notları kaydetmek için Pro kilidini açın.",
  "pro.prompt.sorting": "Kaydedilen manuel uygulama sıralaması Pro'nun bir parçasıdır. Özel siparişinizi korumak için Pro kilidini açın.",
  "pro.prompt.themes": "Bu tema Pro'nun bir parçasıdır. Gelişmiş App Grid temalarını kullanmaya devam etmek için Pro kilidini açın.",
  "pro.purchase.failed": "Satın alma işlemi şu anda tamamlanamıyor. Lütfen tekrar deneyin.",
  "pro.purchase.unavailable": "Pro satın alımı şu anda yapılamıyor. Lütfen daha sonra tekrar deneyin.",
  "pro.purchase.verificationFailed": "App Store doğrulaması başarısız oldu. Lütfen tekrar deneyin.",
  "pro.restore.failed": "Satın alınanlar şu anda geri yüklenemiyor. Lütfen tekrar deneyin.",
  "pro.sorting.previewToast": "Yalnızca önizleme. Manuel uygulama sıralamasından tasarruf etmek için Pro kilidini açın.",
  "pro.status.free": "Ücretsiz",
  "pro.status.legacyUnlocked": "Pro · Erken destekçi",
  "pro.status.unlocked": "Pro kilidi açıldı",
  "pro.theme.preview.status": "Pro temasının önizlemesi yapılıyor",
  "pro.theme.status.free": "Ücretsiz temalar artık hazır. Tüm gelişmiş temaları korumak için Pro'nun kilidini açın.",
  "pro.theme.status.preview": "Bir Pro temasının önizlenmesi. Önizleme sona erdikten sonra onu korumak için Pro kilidini açın.",
  "pro.theme.status.unlocked": "Bu Mac üzerinde tüm temaların kilidi açıldı."
}
src/Apptag/Localization/uk.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Показати поради пізніше",
  "usageTips.closeReminder.message": "Поради можна знову показати в Параметри > Загальні, знявши позначку «Приховувати поради з використання».",
  "usageTips.closeReminder.doNotRemind": "Зрозуміло. Більше не нагадувати, коли я закриваю поради.",
  "usageTips.closeReminder.previewCallout": "Зніміть цей прапорець, щоб знову показати поради"
  "usageTips.closeReminder.previewCallout": "Зніміть цей прапорець, щоб знову показати поради",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "попередній перегляд",
  "pro.card.restore": "Відновити покупки",
  "pro.card.summary.free": "Розблокуйте преміум-теми, імпорт і експорт макетів, необмежену кількість нотаток і збережене ручне сортування програм.",
  "pro.card.summary.legacy": "Дякуємо за першу підтримку TagLauncher. Ваш доступ до Pro вже активний.",
  "pro.card.summary.unlocked": "Pro активний на цьому Mac. Преміум-теми та розширені інструменти компонування готові.",
  "pro.card.title.free": "Оновити до Pro",
  "pro.card.title.legacy": "Pro розблоковано",
  "pro.card.title.unlocked": "Pro розблоковано",
  "pro.card.unlock": "розблокувати Pro",
  "pro.feature.export": "експорт макета",
  "pro.feature.export.benefit": "збережіть поточні теги, порядок і макет як багаторазову резервну копію.",
  "pro.feature.import": "Імпорт макета",
  "pro.feature.import.benefit": "відновіть збережений макет TagLauncher на цьому Mac одним кроком.",
  "pro.feature.notes": "Необмежена кількість нотаток",
  "pro.feature.notes.benefit": "Зберігайте нотатки для кожної програми без обмеження на 5 безкоштовних нотаток.",
  "pro.feature.sorting": "сортування збережених програм",
  "pro.feature.sorting.benefit": "зберігайте свій ручний порядок додатків після виходу з режиму редагування.",
  "pro.feature.themes": "преміум-теми",
  "pro.feature.themes.benefit": "розблокуйте кожну розширену тему App Grid та зберігайте її назавжди.",
  "pro.notes.limitReached": "безкоштовна версія може зберігати нотатки до 5 програм. Розблокуйте Pro, щоб зберігати необмежену кількість нотаток.",
  "pro.notes.remaining": "безкоштовна версія містить %limit% приміток до програми. Залишилося %remaining%.",
  "pro.operation.cancelled": "покупку скасовано.",
  "pro.operation.loading": "Перевірка доступу до Pro…",
  "pro.operation.pending": "Очікування підтвердження App Store…",
  "pro.operation.purchasing": "Придбання Pro…",
  "pro.operation.restoreNotFound": "для цього Apple ID не знайдено попередніх покупок Pro.",
  "pro.operation.restoring": "Відновлення покупки…",
  "pro.prompt.export": "експорт макета є функцією Pro. Розблокуйте Pro, щоб створити резервну копію поточних тегів, порядку та макета.",
  "pro.prompt.import": "Імпорт макета є функцією Pro. Розблокуйте Pro, щоб відновити збережений макет на цьому Mac.",
  "pro.prompt.notes": "Необмежена кількість приміток до програми є частиною Pro. Розблокуйте Pro, щоб зберігати нотатки для кожної програми.",
  "pro.prompt.sorting": "Збережене ручне сортування програм є частиною Pro. Розблокуйте Pro, щоб зберегти свій власний порядок.",
  "pro.prompt.themes": "Ця тема є частиною Pro. Розблокуйте Pro, щоб продовжувати використовувати розширені теми App Grid.",
  "pro.purchase.failed": "зараз неможливо завершити покупку. Спробуйте ще раз.",
  "pro.purchase.unavailable": "покупка Pro зараз недоступна. Спробуйте пізніше.",
  "pro.purchase.verificationFailed": "не вдалося перевірити App Store. Спробуйте ще раз.",
  "pro.restore.failed": "зараз неможливо відновити покупки. Спробуйте ще раз.",
  "pro.sorting.previewToast": "лише попередній перегляд. Розблокуйте Pro, щоб зберегти ручне сортування програм.",
  "pro.status.free": "безкоштовно",
  "pro.status.legacyUnlocked": "Pro · Ранній прибічник",
  "pro.status.unlocked": "Pro розблоковано",
  "pro.theme.preview.status": "попередній перегляд теми Pro",
  "pro.theme.status.free": "безкоштовні теми готові. Розблокуйте Pro, щоб зберегти всі розширені теми.",
  "pro.theme.status.preview": "попередній перегляд теми Pro. Розблокуйте Pro, щоб зберегти його після закінчення попереднього перегляду.",
  "pro.theme.status.unlocked": "усі теми розблоковано на цьому Mac."
}
src/Apptag/Localization/vi.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "Xem lại mẹo sau",
  "usageTips.closeReminder.message": "Bạn có thể xem lại mẹo trong Cài đặt > Chung bằng cách bỏ chọn “Ẩn mẹo sử dụng”.",
  "usageTips.closeReminder.doNotRemind": "Đã hiểu. Đừng nhắc lại khi tôi đóng mẹo.",
  "usageTips.closeReminder.previewCallout": "Bỏ chọn mục này để xem lại mẹo"
  "usageTips.closeReminder.previewCallout": "Bỏ chọn mục này để xem lại mẹo",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "Xem trước",
  "pro.card.restore": "Khôi phục mua hàng",
  "pro.card.summary.free": "Mở khóa các chủ đề cao cấp, nhập và xuất bố cục, ghi chú không giới hạn và phân loại ứng dụng thủ công được lưu.",
  "pro.card.summary.legacy": "Cảm ơn đã ủng hộ TagLauncher sớm. Quyền truy cập Pro của bạn đã hoạt động.",
  "pro.card.summary.unlocked": "Pro đang hoạt động trên Mac này. Các chủ đề cao cấp và công cụ bố cục nâng cao đã sẵn sàng.",
  "pro.card.title.free": "Nâng cấp lên Pro",
  "pro.card.title.legacy": "Pro đã mở khóa",
  "pro.card.title.unlocked": "Pro đã mở khóa",
  "pro.card.unlock": "Mở khóa Pro",
  "pro.feature.export": "Xuất bố cục",
  "pro.feature.export.benefit": "Lưu các thẻ, thứ tự và bố cục hiện tại của bạn làm bản sao lưu có thể sử dụng lại.",
  "pro.feature.import": "Nhập bố cục",
  "pro.feature.import.benefit": "Khôi phục bố cục TagLauncher đã lưu trên Mac này trong một bước.",
  "pro.feature.notes": "Ghi chú không giới hạn",
  "pro.feature.notes.benefit": "Lưu ghi chú cho mọi ứng dụng mà không giới hạn 5 ghi chú miễn phí.",
  "pro.feature.sorting": "Sắp xếp ứng dụng đã lưu",
  "pro.feature.sorting.benefit": "Giữ nguyên thứ tự ứng dụng thủ công sau khi bạn thoát khỏi chế độ chỉnh sửa.",
  "pro.feature.themes": "Chủ đề cao cấp",
  "pro.feature.themes.benefit": "Mở khóa mọi chủ đề App Grid nâng cao và giữ nó vĩnh viễn.",
  "pro.notes.limitReached": "Phiên bản miễn phí có thể lưu ghi chú cho tối đa 5 ứng dụng. Mở khóa Pro để lưu ghi chú không giới hạn.",
  "pro.notes.remaining": "Phiên bản miễn phí bao gồm %limit% ghi chú ứng dụng. Còn lại %remaining%.",
  "pro.operation.cancelled": "Giao dịch mua bị hủy.",
  "pro.operation.loading": "Đang kiểm tra quyền truy cập Pro…",
  "pro.operation.pending": "Đang chờ xác nhận App Store…",
  "pro.operation.purchasing": "Mua Pro…",
  "pro.operation.restoreNotFound": "Không tìm thấy giao dịch mua Pro nào trước đây cho Apple ID này.",
  "pro.operation.restoring": "Khôi phục giao dịch mua…",
  "pro.prompt.export": "Xuất bố cục là một tính năng Pro. Mở khóa Pro để sao lưu thẻ, thứ tự và bố cục hiện tại của bạn.",
  "pro.prompt.import": "Nhập bố cục là một tính năng Pro. Mở khóa Pro để khôi phục bố cục đã lưu trên Mac này.",
  "pro.prompt.notes": "Ghi chú ứng dụng không giới hạn là một phần của Pro. Mở khóa Pro để lưu ghi chú cho mọi ứng dụng.",
  "pro.prompt.sorting": "Việc sắp xếp ứng dụng thủ công đã lưu là một phần của Pro. Mở khóa Pro để giữ thứ tự tùy chỉnh của bạn.",
  "pro.prompt.themes": "Chủ đề này là một phần của Pro. Mở khóa Pro để tiếp tục sử dụng các chủ đề App Grid nâng cao.",
  "pro.purchase.failed": "Không thể hoàn tất việc mua hàng ngay bây giờ. Vui lòng thử lại.",
  "pro.purchase.unavailable": "Pro hiện không thể mua được. Vui lòng thử lại sau.",
  "pro.purchase.verificationFailed": "App Store xác minh không thành công. Vui lòng thử lại.",
  "pro.restore.failed": "Không thể khôi phục giao dịch mua ngay bây giờ. Vui lòng thử lại.",
  "pro.sorting.previewToast": "Chỉ xem trước. Mở khóa Pro để lưu cách sắp xếp ứng dụng thủ công.",
  "pro.status.free": "Miễn phí",
  "pro.status.legacyUnlocked": "Pro · Người ủng hộ sớm",
  "pro.status.unlocked": "Pro đã mở khóa",
  "pro.theme.preview.status": "Xem trước chủ đề Pro",
  "pro.theme.status.free": "Các chủ đề miễn phí hiện đã sẵn sàng. Mở khóa Pro để giữ mọi chủ đề nâng cao.",
  "pro.theme.status.preview": "Xem trước chủ đề Pro. Mở khóa Pro để giữ nó sau khi bản xem trước kết thúc.",
  "pro.theme.status.unlocked": "Tất cả chủ đề đều được mở khóa trên Mac này."
}
src/Apptag/Localization/zh-Hans.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "以后还能重新查看使用技巧",
  "usageTips.closeReminder.message": "后续可在「设置 > 通用」去掉「隐藏使用技巧」的勾选,再次查看 tips。",
  "usageTips.closeReminder.doNotRemind": "知道了,下次关闭时不要再提醒我",
  "usageTips.closeReminder.previewCallout": "去掉这里的勾选"
  "usageTips.closeReminder.previewCallout": "去掉这里的勾选",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "预览",
  "pro.card.restore": "恢复购买",
  "pro.card.summary.free": "解锁高级主题、布局导入导出、无限备注,以及手动应用排序保存。",
  "pro.card.summary.legacy": "感谢你早期支持 TagLauncher。你的 Pro 权益已自动生效。",
  "pro.card.summary.unlocked": "这台 Mac 上的 Pro 已生效。高级主题和布局工具已可使用。",
  "pro.card.title.free": "升级到 Pro",
  "pro.card.title.legacy": "Pro 已解锁",
  "pro.card.title.unlocked": "Pro 已解锁",
  "pro.card.unlock": "解锁 Pro",
  "pro.feature.export": "导出布局",
  "pro.feature.export.benefit": "把当前标签、顺序和布局保存成可复用的备份。",
  "pro.feature.import": "导入布局",
  "pro.feature.import.benefit": "一步恢复这台 Mac 上已保存的 TagLauncher 布局。",
  "pro.feature.notes": "无限备注",
  "pro.feature.notes.benefit": "不再受免费版 5 条备注上限限制,可为每个应用保存备注。",
  "pro.feature.sorting": "排序持久化",
  "pro.feature.sorting.benefit": "离开编辑模式后仍保留你手动调整的应用顺序。",
  "pro.feature.themes": "高级主题",
  "pro.feature.themes.benefit": "解锁所有高级应用网格主题,并可永久使用。",
  "pro.notes.limitReached": "免费版最多只能为 5 个应用保存备注。解锁 Pro 后可保存无限备注。",
  "pro.notes.remaining": "免费版最多支持 %limit% 个应用备注。当前剩余 %remaining% 个。",
  "pro.operation.cancelled": "购买已取消。",
  "pro.operation.loading": "正在检查 Pro 权益…",
  "pro.operation.pending": "正在等待 App Store 确认…",
  "pro.operation.purchasing": "正在购买 Pro…",
  "pro.operation.restoreNotFound": "当前 Apple ID 下没有找到可恢复的 Pro 购买记录。",
  "pro.operation.restoring": "正在恢复购买…",
  "pro.prompt.export": "导出布局属于 Pro 功能。解锁 Pro 后可备份当前标签、顺序和布局。",
  "pro.prompt.import": "导入布局属于 Pro 功能。解锁 Pro 后可在这台 Mac 上恢复已保存的布局。",
  "pro.prompt.notes": "无限应用备注属于 Pro 功能。解锁 Pro 后可为每个应用保存备注。",
  "pro.prompt.sorting": "手动应用排序保存属于 Pro 功能。解锁 Pro 后可保留你的自定义顺序。",
  "pro.prompt.themes": "这个主题属于 Pro。解锁 Pro 后可持续使用高级应用网格主题。",
  "pro.purchase.failed": "暂时无法完成购买,请稍后再试。",
  "pro.purchase.unavailable": "当前暂时无法购买 Pro,请稍后再试。",
  "pro.purchase.verificationFailed": "App Store 校验失败,请重试。",
  "pro.restore.failed": "暂时无法恢复购买,请稍后再试。",
  "pro.sorting.previewToast": "当前仅为预览。解锁 Pro 后才可保存手动排序。",
  "pro.status.free": "免费版",
  "pro.status.legacyUnlocked": "Pro · 早期支持者",
  "pro.status.unlocked": "Pro 已解锁",
  "pro.theme.preview.status": "正在预览 Pro 主题",
  "pro.theme.status.free": "免费主题已经可用。解锁 Pro 后可长期使用全部高级主题。",
  "pro.theme.status.preview": "正在预览 Pro 主题。解锁 Pro 后可在预览结束后继续保留。",
  "pro.theme.status.unlocked": "这台 Mac 上的全部主题都已解锁。"
}
src/Apptag/Localization/zh-Hant.json
@@ -243,5 +243,50 @@
  "usageTips.closeReminder.title": "之後還能重新查看使用技巧",
  "usageTips.closeReminder.message": "之後可在「設定 > 通用」取消勾選「隱藏使用技巧」,再次查看 tips。",
  "usageTips.closeReminder.doNotRemind": "知道了,下次關閉時不要再提醒我",
  "usageTips.closeReminder.previewCallout": "取消這裡的勾選"
  "usageTips.closeReminder.previewCallout": "取消這裡的勾選",
  "pro.card.badge": "Pro",
  "pro.card.previewBadge": "預覽",
  "pro.card.restore": "恢復購買",
  "pro.card.summary.free": "解鎖進階主題、版面匯入匯出、無限備註,以及手動應用排序保存。",
  "pro.card.summary.legacy": "感謝你早期支持 TagLauncher。你的 Pro 權益已自動生效。",
  "pro.card.summary.unlocked": "這台 Mac 上的 Pro 已啟用。進階主題與版面工具已可使用。",
  "pro.card.title.free": "升級到 Pro",
  "pro.card.title.legacy": "Pro 已解鎖",
  "pro.card.title.unlocked": "Pro 已解鎖",
  "pro.card.unlock": "解鎖 Pro",
  "pro.feature.export": "匯出版面",
  "pro.feature.export.benefit": "把目前標籤、順序與版面儲存成可重複使用的備份。",
  "pro.feature.import": "匯入版面",
  "pro.feature.import.benefit": "一步恢復這台 Mac 上已儲存的 TagLauncher 版面。",
  "pro.feature.notes": "無限備註",
  "pro.feature.notes.benefit": "不再受免費版 5 筆備註上限限制,可為每個應用儲存備註。",
  "pro.feature.sorting": "排序持久化",
  "pro.feature.sorting.benefit": "離開編輯模式後仍保留你手動調整的應用順序。",
  "pro.feature.themes": "進階主題",
  "pro.feature.themes.benefit": "解鎖所有進階應用網格主題,並可永久使用。",
  "pro.notes.limitReached": "免費版最多只能為 5 個應用儲存備註。解鎖 Pro 後可儲存無限備註。",
  "pro.notes.remaining": "免費版最多支援 %limit% 個應用備註。當前剩餘 %remaining% 個。",
  "pro.operation.cancelled": "購買已取消。",
  "pro.operation.loading": "正在檢查 Pro 權益…",
  "pro.operation.pending": "正在等待 App Store 確認…",
  "pro.operation.purchasing": "正在購買 Pro…",
  "pro.operation.restoreNotFound": "目前 Apple ID 下沒有找到可恢復的 Pro 購買紀錄。",
  "pro.operation.restoring": "正在恢復購買…",
  "pro.prompt.export": "匯出版面屬於 Pro 功能。解鎖 Pro 後可備份目前標籤、順序與版面。",
  "pro.prompt.import": "匯入版面屬於 Pro 功能。解鎖 Pro 後可在這台 Mac 上恢復已儲存的版面。",
  "pro.prompt.notes": "無限應用備註屬於 Pro 功能。解鎖 Pro 後可為每個應用儲存備註。",
  "pro.prompt.sorting": "手動應用排序保存屬於 Pro 功能。解鎖 Pro 後可保留你的自訂順序。",
  "pro.prompt.themes": "這個主題屬於 Pro。解鎖 Pro 後可持續使用進階應用網格主題。",
  "pro.purchase.failed": "暫時無法完成購買,請稍後再試。",
  "pro.purchase.unavailable": "目前暫時無法購買 Pro,請稍後再試。",
  "pro.purchase.verificationFailed": "App Store 驗證失敗,請重試。",
  "pro.restore.failed": "暫時無法恢復購買,請稍後再試。",
  "pro.sorting.previewToast": "目前僅為預覽。解鎖 Pro 後才可保存手動排序。",
  "pro.status.free": "免費版",
  "pro.status.legacyUnlocked": "Pro · 早期支持者",
  "pro.status.unlocked": "Pro 已解鎖",
  "pro.theme.preview.status": "正在預覽 Pro 主題",
  "pro.theme.status.free": "免費主題已可使用。解鎖 Pro 後可長期使用全部進階主題。",
  "pro.theme.status.preview": "正在預覽 Pro 主題。解鎖 Pro 後可在預覽結束後繼續保留。",
  "pro.theme.status.unlocked": "這台 Mac 上的全部主題都已解鎖。"
}
src/Apptag/PreferencesView.swift
@@ -69,6 +69,7 @@
    @AppStorage(AppGridTheme.storageKey) private var appGridThemeID = AppDefaults.appGridThemeID
    @AppStorage("mainHotkeyRegistrationState") private var mainHotkeyRegistrationState = LauncherHotkeyRegistrationState.active.rawValue
    @AppStorage("quickSearchHotkeyRegistrationState") private var quickSearchHotkeyRegistrationState = LauncherHotkeyRegistrationState.active.rawValue
    @ObservedObject private var proEntitlement = ProEntitlementCenter.shared
    @State private var selectedLanguage = L10n.selectedLanguageCode
    @State private var isRefreshingLanguage = false
    @State private var allApps: [AppInfo] = []
@@ -83,6 +84,7 @@
    @State private var hotkeyStatusToastToken: UUID? = nil
    @State private var selectedTab: SettingsTab = .general
    @State private var selectedInitialLayoutMode: InitialLayoutMode = .smart
    @State private var settingsProPromptFeature: ProFeature? = nil
    init(initialTabRawValue: String? = nil) {
        let initialTab = initialTabRawValue.flatMap(SettingsTab.init(rawValue:)) ?? .general
@@ -113,6 +115,10 @@
    }
    private func exportTags() {
        guard proEntitlement.isUnlocked else {
            presentSettingsProPrompt(for: .layoutExport)
            return
        }
        guard prepareDataFilePanelPresentation() else { return }
        DispatchQueue.main.async {
            TagDatabase.flushPendingCategorySchemeBackupBatch()
@@ -129,6 +135,10 @@
                guard response == .OK, let url = panel.url else { return }
                do {
                    try TagDatabase.exportTo(url)
                } catch let error as ProEntitlementError {
                    if let feature = error.feature {
                        presentSettingsProPrompt(for: feature)
                    }
                } catch {
                    fputs("[TagLauncher] Export failed: \(error)\n", stderr)
                    showDataAlert(title: tr("settings.exportFailed"), message: error.localizedDescription)
@@ -138,6 +148,10 @@
    }
    private func importTags() {
        guard proEntitlement.isUnlocked else {
            presentSettingsProPrompt(for: .layoutImport)
            return
        }
        guard prepareDataFilePanelPresentation() else { return }
        DispatchQueue.main.async {
            let panel = NSOpenPanel()
@@ -154,6 +168,10 @@
                    scanApps()
                    refreshDataState()
                    notifyDataChanged()
                } catch let error as ProEntitlementError {
                    if let feature = error.feature {
                        presentSettingsProPrompt(for: feature)
                    }
                } catch {
                    fputs("[TagLauncher] Import failed: \(error)\n", stderr)
                    showDataAlert(title: tr("settings.importFailed"), message: error.localizedDescription)
@@ -550,6 +568,106 @@
        AppGridTheme(storedID: appGridThemeID)
    }
    private var effectiveAppGridTheme: AppGridTheme {
        ProEntitlementPolicy.effectiveTheme(for: selectedAppGridTheme)
    }
    private var proStatusPillStyle: ProStatusPillStyle {
        if proEntitlement.isPreviewingTheme {
            return .preview
        }
        switch proEntitlement.accessState {
        case .free:
            return .locked
        case .purchased:
            return .unlocked
        case .legacy:
            return .legacy
        }
    }
    private var proCardTitle: String {
        switch proEntitlement.accessState {
        case .free:
            return tr("pro.card.title.free")
        case .purchased:
            return tr("pro.card.title.unlocked")
        case .legacy:
            return tr("pro.card.title.legacy")
        }
    }
    private var proCardSummary: String {
        switch proEntitlement.accessState {
        case .free:
            return tr("pro.card.summary.free")
        case .purchased:
            return tr("pro.card.summary.unlocked")
        case .legacy:
            return tr("pro.card.summary.legacy")
        }
    }
    private var themeStatusSummary: String {
        if proEntitlement.isPreviewingTheme {
            return tr("pro.theme.status.preview")
        }
        switch proEntitlement.accessState {
        case .free:
            return tr("pro.theme.status.free")
        case .purchased, .legacy:
            return tr("pro.theme.status.unlocked")
        }
    }
    private func unlockButtonTitle() -> String {
        tr("pro.card.unlock")
    }
    private func handleThemeSelection(_ theme: AppGridTheme) {
        if theme.isFreeTheme || proEntitlement.isUnlocked {
            proEntitlement.stopThemePreview()
            appGridThemeID = theme.rawValue
            return
        }
        proEntitlement.startThemePreview(for: theme)
    }
    private func themeOptionAccessory(for theme: AppGridTheme) -> ThemeOptionAccessory {
        if let preview = proEntitlement.themePreviewState?.theme, preview == theme {
            return .pill(text: tr("pro.card.previewBadge"), style: .preview)
        }
        if !proEntitlement.isUnlocked && theme.requiresPro {
            return .pill(text: tr("pro.card.badge"), style: .locked)
        }
        return effectiveAppGridTheme == theme ? .checkmark : .circle
    }
    private func presentSettingsProPrompt(for feature: ProFeature) {
        proEntitlement.clearTransientOperationState()
        withAnimation(.spring(response: 0.22, dampingFraction: 0.84)) {
            settingsProPromptFeature = feature
        }
    }
    private func dismissSettingsProPrompt() {
        proEntitlement.clearTransientOperationState()
        withAnimation(.easeOut(duration: 0.18)) {
            settingsProPromptFeature = nil
        }
    }
    private func handleProAccessStateChange(_ newState: ProAccessState) {
        guard newState.isUnlocked else { return }
        if let preview = proEntitlement.themePreviewState {
            appGridThemeID = preview.theme.rawValue
            proEntitlement.stopThemePreview()
        }
        if settingsProPromptFeature != nil {
            dismissSettingsProPrompt()
        }
    }
    private func generalSettingRow<Control: View>(
        _ label: String,
        description: String,
@@ -645,6 +763,105 @@
                statusTone: hotkeyStatusTone(for: .quickSearch)
            )
        }
    }
    private var themeProStatusRow: some View {
        HStack(spacing: 12) {
            ProStatusPill(
                text: tr(proEntitlement.compactStatusTextKey),
                style: proStatusPillStyle
            )
            Text(themeStatusSummary)
                .font(.system(size: 13, weight: .medium))
                .foregroundStyle(.secondary)
                .fixedSize(horizontal: false, vertical: true)
            Spacer(minLength: 0)
            if !proEntitlement.isUnlocked {
                Button(unlockButtonTitle()) {
                    presentSettingsProPrompt(for: .premiumThemes)
                }
                .buttonStyle(.borderedProminent)
                Button(tr("pro.card.restore")) {
                    proEntitlement.restorePurchases()
                }
                .buttonStyle(.bordered)
            }
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 14)
        .background(
            RoundedRectangle(cornerRadius: 10, style: .continuous)
                .fill(Color(nsColor: .controlBackgroundColor))
        )
        .overlay(
            RoundedRectangle(cornerRadius: 10, style: .continuous)
                .stroke(Color.secondary.opacity(0.16), lineWidth: 1)
        )
    }
    private var dataProStatusCard: some View {
        HStack(alignment: .top, spacing: 18) {
            VStack(alignment: .leading, spacing: 10) {
                HStack(spacing: 10) {
                    Text(proCardTitle)
                        .font(.system(size: 20, weight: .bold))
                    ProStatusPill(
                        text: tr(proEntitlement.compactStatusTextKey),
                        style: proStatusPillStyle
                    )
                }
                Text(proCardSummary)
                    .font(.system(size: 13, weight: .medium))
                    .foregroundStyle(.secondary)
                    .lineSpacing(2)
                    .fixedSize(horizontal: false, vertical: true)
                if let price = proEntitlement.priceDisplayText, !proEntitlement.isUnlocked {
                    Text(price)
                        .font(.system(size: 12, weight: .semibold))
                        .foregroundStyle(Color.accentColor)
                }
            }
            Spacer(minLength: 16)
            VStack(alignment: .trailing, spacing: 10) {
                if !proEntitlement.isUnlocked {
                    Button(unlockButtonTitle()) {
                        proEntitlement.purchasePro()
                    }
                    .buttonStyle(.borderedProminent)
                }
                Button(tr("pro.card.restore")) {
                    proEntitlement.restorePurchases()
                }
                .buttonStyle(.bordered)
                if let statusKey = proEntitlement.operationState.statusMessageKey {
                    Text(tr(statusKey))
                        .font(.caption)
                        .foregroundStyle(.secondary)
                        .multilineTextAlignment(.trailing)
                }
            }
        }
        .padding(.horizontal, 20)
        .padding(.vertical, 18)
        .frame(width: dataPanelWidth, alignment: .leading)
        .background(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(Color(nsColor: .controlBackgroundColor))
        )
        .overlay(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .stroke(Color.secondary.opacity(0.18), lineWidth: 1)
        )
    }
    private var settingsTabBar: some View {
@@ -868,14 +1085,17 @@
                            .foregroundStyle(.secondary)
                    }
                    themeProStatusRow
                    LazyVGrid(columns: themeColumns, alignment: .leading, spacing: 12) {
                        ForEach(AppGridTheme.allCases) { theme in
                            ThemeOptionButton(
                                theme: theme,
                                title: tr(theme.titleKey),
                                isSelected: selectedAppGridTheme == theme
                                isSelected: effectiveAppGridTheme == theme,
                                accessory: themeOptionAccessory(for: theme)
                            ) {
                                appGridThemeID = theme.rawValue
                                handleThemeSelection(theme)
                            }
                        }
                    }
@@ -919,6 +1139,8 @@
                Spacer(minLength: 36)
                VStack(alignment: .center, spacing: 24) {
                    dataProStatusCard
                    dataSection(minHeight: 124) {
                        HStack(alignment: .center, spacing: 12) {
                            dataSectionTitle(tr("settings.initialLayout"))
@@ -986,10 +1208,28 @@
                                VStack(alignment: .center, spacing: 8) {
                                    HStack(spacing: 12) {
                                        Button(tr("settings.export")) { exportTags() }
                                        Button {
                                            exportTags()
                                        } label: {
                                            HStack(spacing: 8) {
                                                Text(tr("settings.export"))
                                                if !proEntitlement.isUnlocked {
                                                    ProStatusPill(text: tr("pro.card.badge"), style: .locked, compact: true)
                                                }
                                            }
                                        }
                                            .buttonStyle(.bordered)
                                            .disabled(isDataFilePanelPresented)
                                        Button(tr("settings.import")) { importTags() }
                                        Button {
                                            importTags()
                                        } label: {
                                            HStack(spacing: 8) {
                                                Text(tr("settings.import"))
                                                if !proEntitlement.isUnlocked {
                                                    ProStatusPill(text: tr("pro.card.badge"), style: .locked, compact: true)
                                                }
                                            }
                                        }
                                            .buttonStyle(.bordered)
                                            .disabled(isDataFilePanelPresented)
                                    }
@@ -1132,6 +1372,9 @@
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherDataDidChange)) { _ in
                refreshDataState()
            }
            .onChange(of: proEntitlement.accessState) { _, newState in
                handleProAccessStateChange(newState)
            }
            if isRefreshingLanguage {
                ZStack {
@@ -1179,6 +1422,28 @@
                .transition(.opacity)
            }
            if let feature = settingsProPromptFeature {
                ZStack {
                    Color.black.opacity(0.16)
                        .ignoresSafeArea()
                    ProUpgradePromptView(
                        title: tr(feature.titleKey),
                        message: tr(feature.promptMessageKey),
                        benefitText: tr(feature.benefitKey),
                        operationText: proEntitlement.operationState.statusMessageKey.map(tr),
                        unlockTitle: unlockButtonTitle(),
                        restoreTitle: tr("pro.card.restore"),
                        isBusy: proEntitlement.operationState.isBusy,
                        onClose: dismissSettingsProPrompt,
                        onUnlock: { proEntitlement.purchasePro() },
                        onRestore: { proEntitlement.restorePurchases() }
                    )
                }
                .zIndex(4)
                .transition(.opacity)
            }
            if let hotkeyStatusToast {
                VStack {
                    Spacer()
@@ -1197,7 +1462,7 @@
                        .frame(maxWidth: 620)
                        .padding(.bottom, 22)
                }
                .zIndex(4)
                .zIndex(5)
                .transition(.opacity.combined(with: .move(edge: .bottom)))
            }
        }
@@ -1391,10 +1656,17 @@
    }
}
private enum ThemeOptionAccessory {
    case checkmark
    case circle
    case pill(text: String, style: ProStatusPillStyle)
}
private struct ThemeOptionButton: View {
    let theme: AppGridTheme
    let title: String
    let isSelected: Bool
    let accessory: ThemeOptionAccessory
    let action: () -> Void
    var body: some View {
@@ -1428,9 +1700,7 @@
                Spacer(minLength: 0)
                Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
                    .font(.system(size: 16, weight: .semibold))
                    .foregroundStyle(isSelected ? Color.accentColor : Color.secondary.opacity(0.42))
                accessoryView
            }
            .padding(10)
            .frame(maxWidth: .infinity, minHeight: 64)
@@ -1454,6 +1724,22 @@
            ? Color.secondary.opacity(0.26)
            : Color.white.opacity(0.24)
    }
    @ViewBuilder
    private var accessoryView: some View {
        switch accessory {
        case .checkmark:
            Image(systemName: "checkmark.circle.fill")
                .font(.system(size: 16, weight: .semibold))
                .foregroundStyle(Color.accentColor)
        case .circle:
            Image(systemName: "circle")
                .font(.system(size: 16, weight: .semibold))
                .foregroundStyle(Color.secondary.opacity(0.42))
        case .pill(let text, let style):
            ProStatusPill(text: text, style: style, compact: true)
        }
    }
}
private struct DisplayModeOptionButton: View {
src/Apptag/ProAccessViews.swift
New file
@@ -0,0 +1,207 @@
import SwiftUI
enum ProStatusPillStyle {
    case locked
    case preview
    case unlocked
    case legacy
    case neutral
}
struct ProStatusPill: View {
    let text: String
    let style: ProStatusPillStyle
    var compact = false
    var body: some View {
        Text(text)
            .font(.system(size: compact ? 10 : 11, weight: .semibold))
            .foregroundStyle(foregroundColor)
            .lineLimit(1)
            .padding(.horizontal, compact ? 8 : 10)
            .padding(.vertical, compact ? 4 : 5)
            .background(
                Capsule(style: .continuous)
                    .fill(backgroundColor)
            )
            .overlay(
                Capsule(style: .continuous)
                    .stroke(strokeColor, lineWidth: 1)
            )
    }
    private var foregroundColor: Color {
        switch style {
        case .locked:
            return Color.accentColor
        case .preview:
            return Color(red: 0.30, green: 0.22, blue: 0.08)
        case .unlocked, .legacy:
            return Color(red: 0.10, green: 0.42, blue: 0.24)
        case .neutral:
            return .secondary
        }
    }
    private var backgroundColor: Color {
        switch style {
        case .locked:
            return Color.accentColor.opacity(0.10)
        case .preview:
            return Color.orange.opacity(0.14)
        case .unlocked, .legacy:
            return Color.green.opacity(0.12)
        case .neutral:
            return Color.secondary.opacity(0.10)
        }
    }
    private var strokeColor: Color {
        switch style {
        case .locked:
            return Color.accentColor.opacity(0.26)
        case .preview:
            return Color.orange.opacity(0.28)
        case .unlocked, .legacy:
            return Color.green.opacity(0.26)
        case .neutral:
            return Color.secondary.opacity(0.18)
        }
    }
}
extension ProFeature {
    var promptMessageKey: String {
        switch self {
        case .premiumThemes:
            return "pro.prompt.themes"
        case .layoutImport:
            return "pro.prompt.import"
        case .layoutExport:
            return "pro.prompt.export"
        case .unlimitedNotes:
            return "pro.prompt.notes"
        case .persistentAppSorting:
            return "pro.prompt.sorting"
        }
    }
}
extension ProOperationState {
    var statusMessageKey: String? {
        switch self {
        case .idle:
            return nil
        case .loading:
            return "pro.operation.loading"
        case .purchasing:
            return "pro.operation.purchasing"
        case .restoring:
            return "pro.operation.restoring"
        case .pending:
            return "pro.operation.pending"
        case .cancelled:
            return "pro.operation.cancelled"
        case .restoreNotFound:
            return "pro.operation.restoreNotFound"
        case .failed(let messageKey):
            return messageKey
        }
    }
}
struct ProUpgradePromptView: View {
    let title: String
    let message: String
    let benefitText: String
    let operationText: String?
    let unlockTitle: String
    let restoreTitle: String
    let isBusy: Bool
    let onClose: () -> Void
    let onUnlock: () -> Void
    let onRestore: () -> Void
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            HStack(alignment: .top, spacing: 12) {
                VStack(alignment: .leading, spacing: 10) {
                    HStack(spacing: 8) {
                        Text(title)
                            .font(.system(size: 26, weight: .bold))
                            .foregroundStyle(.primary)
                        ProStatusPill(text: tr("pro.card.badge"), style: .locked)
                    }
                    Text(message)
                        .font(.system(size: 14, weight: .medium))
                        .foregroundStyle(Color.primary.opacity(0.78))
                        .lineSpacing(2)
                        .fixedSize(horizontal: false, vertical: true)
                }
                Spacer(minLength: 16)
                Button(action: onClose) {
                    Image(systemName: "xmark")
                        .font(.system(size: 12, weight: .bold))
                        .foregroundStyle(.secondary)
                        .frame(width: 28, height: 28)
                        .background(
                            Circle()
                                .fill(Color.primary.opacity(0.08))
                        )
                }
                .buttonStyle(.plain)
                .help(tr("settings.cancel"))
            }
            HStack(alignment: .top, spacing: 10) {
                Image(systemName: "sparkles")
                    .font(.system(size: 14, weight: .semibold))
                    .foregroundStyle(Color.accentColor)
                    .frame(width: 18, height: 18)
                Text(benefitText)
                    .font(.system(size: 13, weight: .medium))
                    .foregroundStyle(Color.primary.opacity(0.76))
                    .lineSpacing(2)
                    .fixedSize(horizontal: false, vertical: true)
            }
            .padding(.top, 18)
            if let operationText {
                Text(operationText)
                    .font(.system(size: 12, weight: .semibold))
                    .foregroundStyle(Color.secondary)
                    .padding(.top, 14)
            }
            HStack(spacing: 10) {
                Button(tr("settings.cancel"), action: onClose)
                    .buttonStyle(.bordered)
                Spacer(minLength: 0)
                Button(restoreTitle, action: onRestore)
                    .buttonStyle(.bordered)
                    .disabled(isBusy)
                Button(unlockTitle, action: onUnlock)
                    .buttonStyle(.borderedProminent)
                    .disabled(isBusy)
            }
            .padding(.top, 20)
        }
        .padding(22)
        .frame(width: 460)
        .background(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(Color(nsColor: .windowBackgroundColor))
        )
        .overlay(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .stroke(Color.primary.opacity(0.14), lineWidth: 1)
        )
        .shadow(color: .black.opacity(0.24), radius: 24, x: 0, y: 12)
    }
}
src/Apptag/ProEntitlement.swift
New file
@@ -0,0 +1,639 @@
import Foundation
import StoreKit
import SwiftUI
enum ProFeature: String, CaseIterable {
    case premiumThemes
    case layoutImport
    case layoutExport
    case unlimitedNotes
    case persistentAppSorting
    var titleKey: String {
        switch self {
        case .premiumThemes:
            return "pro.feature.themes"
        case .layoutImport:
            return "pro.feature.import"
        case .layoutExport:
            return "pro.feature.export"
        case .unlimitedNotes:
            return "pro.feature.notes"
        case .persistentAppSorting:
            return "pro.feature.sorting"
        }
    }
    var benefitKey: String {
        switch self {
        case .premiumThemes:
            return "pro.feature.themes.benefit"
        case .layoutImport:
            return "pro.feature.import.benefit"
        case .layoutExport:
            return "pro.feature.export.benefit"
        case .unlimitedNotes:
            return "pro.feature.notes.benefit"
        case .persistentAppSorting:
            return "pro.feature.sorting.benefit"
        }
    }
}
enum ProAccessState: String, Codable, Equatable {
    case free
    case purchased
    case legacy
    var isUnlocked: Bool {
        self != .free
    }
}
enum ProOperationState: Equatable {
    case idle
    case loading
    case purchasing
    case restoring
    case pending
    case cancelled
    case restoreNotFound
    case failed(messageKey: String)
    var isBusy: Bool {
        switch self {
        case .loading, .purchasing, .restoring:
            return true
        case .idle, .pending, .cancelled, .restoreNotFound, .failed:
            return false
        }
    }
}
enum ProEntitlementError: Error, Equatable {
    case featureLocked(ProFeature)
    case noteQuotaExceeded(ProNoteQuotaStatus)
    var feature: ProFeature? {
        switch self {
        case .featureLocked(let feature):
            return feature
        case .noteQuotaExceeded:
            return .unlimitedNotes
        }
    }
    var noteQuotaStatus: ProNoteQuotaStatus? {
        switch self {
        case .featureLocked:
            return nil
        case .noteQuotaExceeded(let status):
            return status
        }
    }
}
struct ProThemePreviewState: Equatable {
    let theme: AppGridTheme
    let startedAt: Date
    let endsAt: Date
}
struct ProNoteQuotaStatus: Equatable {
    let isUnlimited: Bool
    let used: Int
    let limit: Int
    var remaining: Int {
        max(0, limit - used)
    }
}
enum ProNoteSaveDecision: Equatable {
    case allow(ProNoteQuotaStatus)
    case blocked(ProNoteQuotaStatus)
}
enum ProEntitlementConfig {
    // 发布前只改这里,不允许把商品信息和边界版本散落到业务模块。
    static let lifetimeProductID = "com.taglauncher.pro.lifetime"
    static let firstFreeProVersion = "8.1.0"
    static let legacyFallbackOriginalPurchaseDateISO8601: String? = nil
    static let freeThemes: Set<AppGridTheme> = [.defaultLight, .deepBlue, .black]
    static let freeNoteLimit = 5
    static let defaultThemePreviewDuration: TimeInterval = 5 * 60
    static let qaStateEnvKey = "TAGLAUNCHER_QA_PRO_STATE"
    static let qaThemePreviewSecondsEnvKey = "TAGLAUNCHER_QA_THEME_PREVIEW_SECONDS"
    static var legacyFallbackOriginalPurchaseDate: Date? {
        guard let value = legacyFallbackOriginalPurchaseDateISO8601 else { return nil }
        return iso8601Formatter.date(from: value)
    }
    static var themePreviewDuration: TimeInterval {
        let environment = ProcessInfo.processInfo.environment
        if let raw = environment[qaThemePreviewSecondsEnvKey],
           let seconds = TimeInterval(raw),
           seconds > 0 {
            return seconds
        }
        return defaultThemePreviewDuration
    }
    private static let iso8601Formatter: ISO8601DateFormatter = {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime]
        return formatter
    }()
}
private struct ProEntitlementCache: Codable {
    var accessState: ProAccessState
    var updatedAt: Date
    var originalAppVersion: String?
    var originalPurchaseDate: Date?
}
private struct ProEntitlementSnapshot {
    var accessState: ProAccessState = .free
    var previewThemeID: String? = nil
}
private enum ProEntitlementSnapshotStore {
    private static let lock = NSLock()
    private static var snapshot = ProEntitlementSnapshot()
    static func update(_ block: (inout ProEntitlementSnapshot) -> Void) {
        lock.lock()
        block(&snapshot)
        lock.unlock()
    }
    static func read() -> ProEntitlementSnapshot {
        lock.lock()
        let value = snapshot
        lock.unlock()
        return value
    }
}
enum ProEntitlementPolicy {
    static func accessState() -> ProAccessState {
        ProEntitlementSnapshotStore.read().accessState
    }
    static func lockedErrorIfNeeded(for feature: ProFeature) -> ProEntitlementError? {
        isUnlocked(feature) ? nil : .featureLocked(feature)
    }
    static func requireUnlocked(_ feature: ProFeature) throws {
        if let error = lockedErrorIfNeeded(for: feature) {
            throw error
        }
    }
    static func isUnlocked(_ feature: ProFeature) -> Bool {
        switch feature {
        case .premiumThemes, .layoutImport, .layoutExport, .unlimitedNotes, .persistentAppSorting:
            return accessState().isUnlocked
        }
    }
    static func canUseTheme(_ theme: AppGridTheme) -> Bool {
        ProEntitlementConfig.freeThemes.contains(theme) || isUnlocked(.premiumThemes)
    }
    static func effectiveTheme(for storedTheme: AppGridTheme) -> AppGridTheme {
        if let previewID = ProEntitlementSnapshotStore.read().previewThemeID,
           let previewTheme = AppGridTheme(rawValue: previewID) {
            return previewTheme
        }
        guard canUseTheme(storedTheme) else {
            return .defaultLight
        }
        return storedTheme
    }
    static func noteQuotaStatus(in store: TagDatabase.Store? = nil) -> ProNoteQuotaStatus {
        guard !isUnlocked(.unlimitedNotes) else {
            return ProNoteQuotaStatus(isUnlimited: true, used: 0, limit: Int.max)
        }
        let used = TagDatabase.manualNonEmptyNoteCount(in: store)
        return ProNoteQuotaStatus(
            isUnlimited: false,
            used: used,
            limit: ProEntitlementConfig.freeNoteLimit
        )
    }
    static func noteSaveDecision(note: String, for path: String, in store: TagDatabase.Store? = nil) -> ProNoteSaveDecision {
        let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)
        let effectiveStore = store ?? TagDatabase.load()
        let currentQuota = noteQuotaStatus(in: effectiveStore)
        guard !currentQuota.isUnlimited else { return .allow(currentQuota) }
        let manualNotePaths = TagDatabase.manualNonEmptyNotePaths(in: effectiveStore)
        let hasExistingManualNote = manualNotePaths.contains(path)
        let willPersistManualNote = !trimmed.isEmpty
        let limit = ProEntitlementConfig.freeNoteLimit
        let resultingUsed: Int
        switch (hasExistingManualNote, willPersistManualNote) {
        case (true, true):
            resultingUsed = manualNotePaths.count
        case (true, false):
            resultingUsed = max(0, manualNotePaths.count - 1)
        case (false, true):
            guard manualNotePaths.count < limit else {
                return .blocked(currentQuota)
            }
            resultingUsed = manualNotePaths.count + 1
        case (false, false):
            resultingUsed = manualNotePaths.count
        }
        return .allow(
            ProNoteQuotaStatus(
                isUnlimited: false,
                used: resultingUsed,
                limit: limit
            )
        )
    }
    static func requireNoteSave(note: String, for path: String, in store: TagDatabase.Store? = nil) throws -> ProNoteQuotaStatus {
        switch noteSaveDecision(note: note, for: path, in: store) {
        case .allow(let status):
            return status
        case .blocked(let status):
            throw ProEntitlementError.noteQuotaExceeded(status)
        }
    }
}
@MainActor
final class ProEntitlementCenter: ObservableObject {
    static let shared = ProEntitlementCenter()
    @Published private(set) var accessState: ProAccessState = .free
    @Published private(set) var operationState: ProOperationState = .idle
    @Published private(set) var priceDisplayText: String? = nil
    @Published private(set) var themePreviewState: ProThemePreviewState? = nil
    private var hasStarted = false
    private var cachedProduct: Product?
    private var updateTask: Task<Void, Never>?
    private var themePreviewTask: Task<Void, Never>?
    private init() {}
    deinit {
        updateTask?.cancel()
        themePreviewTask?.cancel()
    }
    var isUnlocked: Bool {
        accessState.isUnlocked
    }
    var isPreviewingTheme: Bool {
        themePreviewState != nil
    }
    var compactStatusTextKey: String {
        if themePreviewState != nil {
            return "pro.theme.preview.status"
        }
        switch accessState {
        case .free:
            return "pro.status.free"
        case .purchased:
            return "pro.status.unlocked"
        case .legacy:
            return "pro.status.legacyUnlocked"
        }
    }
    func start() {
        guard !hasStarted else { return }
        hasStarted = true
        if let override = qaOverrideAccessState {
            applyAccessState(override, persistCache: false)
            operationState = .idle
            return
        }
        if let cached = loadCache() {
            applyAccessState(cached.accessState, persistCache: false)
        }
        operationState = .loading
        startTransactionUpdates()
        Task {
            _ = try? await loadProductIfNeeded()
            await refreshEntitlements()
        }
    }
    func purchasePro() {
        Task {
            operationState = .purchasing
            do {
                guard let product = try await loadProductIfNeeded() else {
                    operationState = .failed(messageKey: "pro.purchase.unavailable")
                    return
                }
                let result = try await product.purchase()
                switch result {
                case .success(let verification):
                    switch verification {
                    case .verified(let transaction):
                        await transaction.finish()
                        await refreshEntitlements()
                        operationState = .idle
                    case .unverified:
                        operationState = .failed(messageKey: "pro.purchase.verificationFailed")
                    }
                case .pending:
                    operationState = .pending
                case .userCancelled:
                    operationState = .cancelled
                @unknown default:
                    operationState = .failed(messageKey: "pro.purchase.failed")
                }
            } catch {
                operationState = .failed(messageKey: "pro.purchase.failed")
            }
        }
    }
    func restorePurchases() {
        Task {
            operationState = .restoring
            do {
                try await AppStore.sync()
                await refreshEntitlements()
                if accessState.isUnlocked {
                    operationState = .idle
                } else {
                    operationState = .restoreNotFound
                }
            } catch {
                operationState = .failed(messageKey: "pro.restore.failed")
            }
        }
    }
    func clearTransientOperationState() {
        switch operationState {
        case .pending, .cancelled, .restoreNotFound, .failed:
            operationState = .idle
        case .idle, .loading, .purchasing, .restoring:
            break
        }
    }
    func startThemePreview(for theme: AppGridTheme) {
        guard !ProEntitlementConfig.freeThemes.contains(theme) else { return }
        guard !accessState.isUnlocked else { return }
        themePreviewTask?.cancel()
        let preview = ProThemePreviewState(
            theme: theme,
            startedAt: Date(),
            endsAt: Date().addingTimeInterval(ProEntitlementConfig.themePreviewDuration)
        )
        applyThemePreview(preview)
        themePreviewTask = Task { [weak self] in
            let duration = max(1, ProEntitlementConfig.themePreviewDuration)
            try? await Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))
            guard !Task.isCancelled else { return }
            self?.stopThemePreview()
        }
    }
    func stopThemePreview() {
        themePreviewTask?.cancel()
        themePreviewTask = nil
        applyThemePreview(nil)
    }
    private func startTransactionUpdates() {
        guard updateTask == nil else { return }
        updateTask = Task { [weak self] in
            for await update in Transaction.updates {
                guard let self else { return }
                if case .verified(let transaction) = update,
                   transaction.productID == ProEntitlementConfig.lifetimeProductID {
                    await transaction.finish()
                }
                await self.refreshEntitlements()
            }
        }
    }
    @discardableResult
    private func loadProductIfNeeded() async throws -> Product? {
        if let cachedProduct {
            return cachedProduct
        }
        let products = try await Product.products(for: [ProEntitlementConfig.lifetimeProductID])
        let product = products.first
        cachedProduct = product
        priceDisplayText = product?.displayPrice
        return product
    }
    private func refreshEntitlements() async {
        if let override = qaOverrideAccessState {
            applyAccessState(override, persistCache: false)
            operationState = .idle
            return
        }
        let purchased = await hasPurchasedLifetimePro()
        let appTransaction = await verifiedAppTransaction()
        let legacy = appTransaction.map(isLegacyPaidUser(appTransaction:)) ?? false
        let state: ProAccessState
        if purchased {
            state = .purchased
        } else if legacy {
            state = .legacy
        } else {
            state = .free
        }
        applyAccessState(
            state,
            persistCache: true,
            originalAppVersion: appTransaction?.originalAppVersion,
            originalPurchaseDate: appTransaction?.originalPurchaseDate
        )
        if case .loading = operationState {
            operationState = .idle
        }
    }
    private func hasPurchasedLifetimePro() async -> Bool {
        for await verification in Transaction.currentEntitlements {
            switch verification {
            case .verified(let transaction):
                if transaction.productID == ProEntitlementConfig.lifetimeProductID,
                   transaction.revocationDate == nil {
                    return true
                }
            case .unverified:
                continue
            }
        }
        return false
    }
    private func verifiedAppTransaction() async -> AppTransaction? {
        do {
            let verification = try await AppTransaction.shared
            switch verification {
            case .verified(let transaction):
                if #available(macOS 13.0, *) {
                    if transaction.revocationDate != nil {
                        return nil
                    }
                }
                return transaction
            case .unverified:
                return nil
            }
        } catch {
            return nil
        }
    }
    private func isLegacyPaidUser(appTransaction: AppTransaction) -> Bool {
        if let current = SemanticVersion(appTransaction.originalAppVersion),
           let cutoff = SemanticVersion(ProEntitlementConfig.firstFreeProVersion) {
            return current < cutoff
        }
        guard let fallbackDate = ProEntitlementConfig.legacyFallbackOriginalPurchaseDate else {
            return false
        }
        return appTransaction.originalPurchaseDate < fallbackDate
    }
    private func applyAccessState(
        _ state: ProAccessState,
        persistCache: Bool,
        originalAppVersion: String? = nil,
        originalPurchaseDate: Date? = nil
    ) {
        accessState = state
        ProEntitlementSnapshotStore.update { snapshot in
            snapshot.accessState = state
        }
        if persistCache {
            writeCache(
                ProEntitlementCache(
                    accessState: state,
                    updatedAt: Date(),
                    originalAppVersion: originalAppVersion,
                    originalPurchaseDate: originalPurchaseDate
                )
            )
        }
    }
    private func applyThemePreview(_ preview: ProThemePreviewState?) {
        themePreviewState = preview
        ProEntitlementSnapshotStore.update { snapshot in
            snapshot.previewThemeID = preview?.theme.rawValue
        }
    }
    private var cacheURL: URL {
        AppIdentity.applicationSupportDirectory
            .appendingPathComponent("pro-entitlement-cache.json")
    }
    private func loadCache() -> ProEntitlementCache? {
        guard let data = try? Data(contentsOf: cacheURL) else { return nil }
        return try? JSONDecoder().decode(ProEntitlementCache.self, from: data)
    }
    private func writeCache(_ cache: ProEntitlementCache) {
        try? FileManager.default.createDirectory(
            at: AppIdentity.applicationSupportDirectory,
            withIntermediateDirectories: true,
            attributes: nil
        )
        guard let data = try? JSONEncoder().encode(cache) else { return }
        try? data.write(to: cacheURL, options: .atomic)
    }
    private var qaOverrideAccessState: ProAccessState? {
        guard let raw = ProcessInfo.processInfo.environment[ProEntitlementConfig.qaStateEnvKey] else {
            return nil
        }
        switch raw {
        case "free":
            return .free
        case "pro":
            return .purchased
        case "legacyPro":
            return .legacy
        default:
            return nil
        }
    }
}
private struct SemanticVersion: Comparable {
    private let components: [Int]
    init?(_ raw: String) {
        let values = raw
            .split(separator: ".")
            .compactMap { Int($0.trimmingCharacters(in: .whitespacesAndNewlines)) }
        guard !values.isEmpty else { return nil }
        components = values
    }
    static func < (lhs: SemanticVersion, rhs: SemanticVersion) -> Bool {
        let maxCount = max(lhs.components.count, rhs.components.count)
        for index in 0..<maxCount {
            let left = index < lhs.components.count ? lhs.components[index] : 0
            let right = index < rhs.components.count ? rhs.components[index] : 0
            if left != right {
                return left < right
            }
        }
        return false
    }
}
extension TagDatabase {
    static func manualNonEmptyNotePaths(in store: Store) -> Set<String> {
        Set(
            store.appNotes.compactMap { path, note in
                let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)
                guard !trimmed.isEmpty,
                      store.appNoteMetadata[path]?.origin == .manual
                else { return nil }
                return path
            }
        )
    }
    static func manualNonEmptyNoteCount(in store: Store? = nil) -> Int {
        let effectiveStore = store ?? load()
        return manualNonEmptyNotePaths(in: effectiveStore).count
    }
}
src/CHANGELOG.md
@@ -2,6 +2,15 @@
## [Unreleased]
## [8.1.0] — 2026-06-26
- 新增免费版 + Pro 一次性买断解锁体系:核心标签整理、基础启动和 Quick Search 保持免费可用,高级主题、导入 / 导出布局、无限自定义备注和应用图标排序持久化纳入 Pro
- 新增 StoreKit 2 Pro 权益中心,支持购买、恢复购买、交易更新、离线缓存和 QA 状态注入;早于免费 + Pro 首个版本的历史付费用户会自动获得 Pro
- 主题页和数据页新增克制的 Pro 状态区与小型 Pro 胶囊:免费用户点击高级主题可限时预览但不写入真实主题,点击导入 / 导出会先看到 Pro 解锁提示,不会提前打开文件面板
- 备注和排序增加免费版保护:免费用户最多新增 5 个非空手写备注,已有备注仍可编辑;应用排序可临时预览但不会写入真实排序数据,避免免费预览污染用户布局
- 补齐 29 个语言包的 Pro 文案,并新增 `Scripts/pro_localization_qa.sh` 和 `Scripts/pro_feature_gate_qa.sh`,覆盖 Pro 文案完整性、StoreKit/老用户权益、主题门禁、导入导出硬拦截、备注额度和排序预览
- 版本号更新为 `8.1.0`,Build 更新为 `20260626.1726`
## [8.0.6] — 2026-06-25
- 修复非中文语言下初始化标签仍显示英文的问题:补齐 29 个语言包中的 `smart.category.*` 初始化分类标签文案,覆盖 SmartStart 默认分类、初次安装默认标签和标签导航显示
src/Docs/Requirements/2026-06-26-free-pro-implementation-todo.md
New file
@@ -0,0 +1,155 @@
# 免费版 + Pro 解锁实施 TODO
日期:2026-06-26
范围:TagLauncher 首期“免费下载 + Pro 一次性买断解锁”
## 已冻结的实现原则
1. 免费版保留完整核心价值:
   - 标签整理
   - 基础启动
   - Quick Search
2. 首期 Pro 功能范围:
   - 全部高级主题
   - 导入 / 导出布局
   - 无限自定义备注
   - 手动自定义应用图标排序结果持久化
3. 老用户权益规则:
   - `originalAppVersion` 早于“免费 + Pro 首个正式版本”则自动获得 Pro
   - `originalPurchaseDate` 仅在版本信息缺失或不可用时兜底
   - 该“首个正式版本”阈值必须收口到单一配置点,发布前再冻结,不允许散落在各业务模块
4. 免费预览绝不能写脏真实数据:
   - 主题预览不写 `appGridThemeID`
   - 排序预览不写 `containerAppOrder`
   - 免费用户触发导入 / 导出时不打开文件面板
   - 免费用户第 6 个新备注不写 `appNotes` / `appNoteMetadata`
5. UI 方案:
   - 不做独立 Pro 页签
   - `Data` 页顶部放完整 Pro 状态区
   - `Theme` 页顶部放轻量镜像状态条
   - 高级功能入口使用小型 `Pro` 胶囊,不做全局 `V` 锁
   - 遵循现有设置页语言:克制、原生、低营销
## 已落地的发布参数
1. Pro 商品 ID:`com.taglauncher.pro.lifetime`,集中在 `ProEntitlementConfig.lifetimeProductID`
2. Pro 价格展示:运行时读取 StoreKit product display price;提交 ASC 时仍需按线上价格最终确认
3. 免费 + Pro 首个正式版本号:`8.1.0`,集中在 `ProEntitlementConfig.firstFreeProVersion`
4. `originalPurchaseDate` 兜底日期:当前保持 `nil`,仅当版本阈值无法判断且后续明确兜底日期时启用
## Phase 1:权益中心与购买基础能力
- [x] 新增统一权益中心 `ProEntitlementCenter`
- [x] 建立状态模型:
  - [x] 免费用户
  - [x] 购买获得 Pro
  - [x] 老付费自动 Pro
  - [x] 正在购买 / 正在恢复
  - [x] 购买失败 / 取消 / 无可恢复购买
- [x] 接入 StoreKit 2 当前权益查询
- [x] 接入 `Transaction.updates`
- [x] 接入购买 Pro
- [x] 接入恢复购买
- [x] 加入离线缓存,避免离线启动误丢 Pro
- [x] 加入 QA 注入开关:
  - [x] `TAGLAUNCHER_QA_PRO_STATE=free|pro|legacyPro`
  - [x] `TAGLAUNCHER_QA_THEME_PREVIEW_SECONDS`
## Phase 2:Pro 功能门禁
### 主题
- [x] 免费主题固定为:默认、深蓝、黑色
- [x] 其它主题点击进入预览,不直接写持久化主题
- [x] 预览默认 5 分钟,QA 可缩短
- [x] 预览结束恢复进入前真实主题
- [x] Theme 页增加轻量状态条
- [x] Pro 主题卡增加 `Pro` 胶囊
### 备注
- [x] 统一计算“有非空手写备注的应用数量”
- [x] 免费用户允许编辑已有备注
- [x] 免费用户新增第 6 个备注时阻止保存
- [x] 删除 / 清空备注后释放额度
- [x] 备注提示文案与剩余额度联动
### 排序
- [x] 免费用户允许拖拽排序预览
- [x] 预览只改内存态,不写 `TagEditor.reorderApps`
- [x] 退出预览恢复进入前顺序
- [x] Pro 用户按现有逻辑正常持久化
- [x] 明确预览提示,不误导用户“已保存”
### 导入 / 导出
- [x] 免费用户点击导入 / 导出时直接拦截
- [x] 不打开 `NSSavePanel` / `NSOpenPanel`
- [x] 设置页按钮旁增加低噪音 `Pro` 标识
- [x] `TagDatabase.exportTo/importFrom` 也补硬门禁,防止绕过
## Phase 3:设置页与提示 UI
- [x] `Data` 页顶部新增完整 Pro 状态区
- [x] 免费态展示:
  - [x] 权益摘要
  - [x] 解锁 Pro
  - [x] 恢复购买
- [x] 已解锁态展示:
  - [x] `Pro 已解锁`
  - [x] 如属老用户,显示“感谢早期支持”
- [x] 处理中状态不弹二级窗,按钮原位 loading
- [x] 失败 / 取消结果用内联反馈,不打断
- [x] 拦截弹窗统一语法:
  - [x] 功能名
  - [x] 用户收益
  - [x] 解锁 Pro
  - [x] 恢复购买
  - [x] 关闭
## Phase 4:多语言与 QA
- [x] 补齐 29 个语言包 Pro 文案
- [x] 禁止任一语种出现其他语种残留
- [x] 增加静态 QA:
  - [x] 文案键完整性
  - [x] 非中文语言禁止混入中文
  - [x] 非英文语言禁止直接回退英文关键 Pro 文案
- [x] 增加功能 QA:
  - [x] 免费 / Pro / legacy Pro 三态源码门禁
  - [x] 购买 / 恢复 / 无恢复 / 失败 / 取消状态源码门禁
  - [x] 主题预览不写脏数据
  - [x] 排序预览不写脏数据
  - [x] 备注额度边界
  - [x] 导入导出拦截
## 本轮首期实现范围
本轮先做:
1. 权益中心骨架与 QA 注入
2. 旧付费用户自动 Pro 判定链路
3. 设置页 `Data` / `Theme` 的 Pro UI
4. 主题、导入导出、备注、排序四类门禁接入
5. 最小必要 QA 脚本与 changelog 记录
## 2026-06-26 实施记录
- 新增 `src/Apptag/ProEntitlement.swift`:统一 Pro 权益中心、StoreKit 2 当前权益 / 交易更新 / 购买 / 恢复、离线缓存、老用户自动 Pro 和 QA 状态注入。
- 新增 `src/Apptag/ProAccessViews.swift`:统一 Pro 胶囊和解锁提示视图。
- 接入 `ApptagApp.swift`、`AppGridTheme.swift`、`ContentView.swift`、`PreferencesView.swift`、`DataLayer.swift`、`L10n.swift`,覆盖主题、导入导出、备注额度、排序持久化和设置页状态入口。
- 补齐 29 个语言包 `pro.*` 文案。
- 新增 `Scripts/pro_localization_qa.sh`:静态检查 29 语种 Pro 文案键完整、占位符完整、无生成污染和无英文兜底复制。
- 新增 `Scripts/pro_feature_gate_qa.sh`:源码门禁 Pro 权益、老用户自动 Pro、主题门禁、导入导出硬拦截、备注额度、排序预览和 Pro UI 提示。
本轮不做:
1. ASC / IAP 上架资料提交
2. TestFlight 真机购买证据
3. 官网 / 支持页口径更新
4. 后续新 Pro 功能扩展
src/Scripts/pro_feature_gate_qa.sh
New file
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
python3 - "$ROOT_DIR" <<'PY'
import re
import sys
from pathlib import Path
root = Path(sys.argv[1])
app_dir = root / "Apptag"
def fail(message: str) -> None:
    raise SystemExit(f"FAIL: {message}")
def read(name: str) -> str:
    path = app_dir / name
    try:
        return path.read_text(encoding="utf-8")
    except Exception as error:
        fail(f"could not read {path}: {error}")
def require(source: str, needle: str, message: str) -> None:
    if needle not in source:
        fail(message)
def function_body(source: str, signature: str) -> str:
    start = source.find(signature)
    if start == -1:
        fail(f"missing function signature: {signature}")
    brace = source.find("{", start)
    if brace == -1:
        fail(f"missing function body for: {signature}")
    depth = 0
    for index in range(brace, len(source)):
        char = source[index]
        if char == "{":
            depth += 1
        elif char == "}":
            depth -= 1
            if depth == 0:
                return source[brace + 1:index]
    fail(f"unterminated function body for: {signature}")
pro = read("ProEntitlement.swift")
data_layer = read("DataLayer.swift")
app = read("ApptagApp.swift")
theme = read("AppGridTheme.swift")
preferences = read("PreferencesView.swift")
content = read("ContentView.swift")
access_views = read("ProAccessViews.swift")
for case in [
    "premiumThemes",
    "layoutImport",
    "layoutExport",
    "unlimitedNotes",
    "persistentAppSorting",
]:
    require(pro, f"case {case}", f"missing ProFeature.{case}")
require(pro, 'static let lifetimeProductID = "com.taglauncher.pro.lifetime"', "StoreKit lifetime product id changed or missing")
require(pro, 'static let firstFreeProVersion = "8.1.0"', "legacy unlock cutoff must be 8.1.0")
require(pro, 'static let qaStateEnvKey = "TAGLAUNCHER_QA_PRO_STATE"', "QA Pro-state override is missing")
require(pro, "static let freeNoteLimit = 5", "free note limit must remain explicit")
require(pro, "static let freeThemes: Set<AppGridTheme> = [.defaultLight, .deepBlue, .black]", "free theme set must be default/deepBlue/black")
require(pro, "Product.products(for: [ProEntitlementConfig.lifetimeProductID])", "StoreKit product lookup is missing")
require(pro, "Transaction.currentEntitlements", "current entitlement restore/read path is missing")
require(pro, "Transaction.updates", "transaction update listener is missing")
require(pro, "AppTransaction.shared", "legacy original app version path is missing")
require(pro, "appTransaction.originalAppVersion", "legacy originalAppVersion check is missing")
require(pro, "appTransaction.originalPurchaseDate", "legacy originalPurchaseDate fallback is missing")
require(pro, "SemanticVersion(ProEntitlementConfig.firstFreeProVersion)", "legacy semantic version cutoff is missing")
require(pro, "ProEntitlementSnapshotStore", "sync entitlement snapshot for data-layer policy is missing")
require(pro, "manualNonEmptyNoteCount", "note quota must count existing manual notes")
require(pro, "manualNonEmptyNotePaths", "note quota must avoid double-counting existing notes")
effective_theme = function_body(pro, "static func effectiveTheme(for storedTheme: AppGridTheme) -> AppGridTheme")
require(effective_theme, "return .defaultLight", "locked premium theme must fall back to defaultLight")
require(effective_theme, "canUseTheme(storedTheme)", "effectiveTheme must check Pro theme entitlement")
require(app, "ProEntitlementCenter.shared.start()", "App startup does not start Pro entitlement center")
require(theme, "var requiresPro: Bool", "AppGridTheme does not expose Pro requirement")
require(theme, "ProEntitlementConfig.freeThemes.contains(self)", "theme Pro requirement must use central free theme set")
require(theme, "static var premiumThemes: [AppGridTheme]", "theme premium list is missing")
export_body = function_body(data_layer, "static func exportTo(_ url: URL) throws")
require(export_body, "try ProEntitlementPolicy.requireUnlocked(.layoutExport)", "layout export is not locked at data layer")
if export_body.find("try ProEntitlementPolicy.requireUnlocked(.layoutExport)") > export_body.find("data.write"):
    fail("layout export lock must run before writing data")
import_body = function_body(data_layer, "static func importFrom(_ url: URL) throws -> Store")
require(import_body, "try ProEntitlementPolicy.requireUnlocked(.layoutImport)", "layout import is not locked at data layer")
if import_body.find("try ProEntitlementPolicy.requireUnlocked(.layoutImport)") > import_body.find("Data(contentsOf: url)"):
    fail("layout import lock must run before reading user-selected file")
reorder_body = function_body(data_layer, "static func reorderApps(inContainer containerID: String, orderedPaths: [String])")
require(reorder_body, "guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return }", "app sorting persistence is not locked")
if reorder_body.find("guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return }") > reorder_body.find("var store = TagDatabase.load()"):
    fail("app sorting lock must run before loading/writing store")
note_body = function_body(data_layer, "static func setAppNote(_ note: String, for path: String) -> ProNoteSaveDecision")
require(note_body, "ProEntitlementPolicy.noteSaveDecision", "note save does not check Pro quota")
require(note_body, "guard case .allow = decision else", "blocked note save must return without mutating store")
if note_body.find("guard case .allow = decision else") > note_body.find("let previousStore = store"):
    fail("note quota decision must happen before store mutation snapshot")
export_ui = function_body(preferences, "private func exportTags()")
require(export_ui, "guard proEntitlement.isUnlocked else", "export UI must prompt Pro before file panel")
require(export_ui, "presentSettingsProPrompt(for: .layoutExport)", "export UI prompt feature is missing")
if export_ui.find("guard proEntitlement.isUnlocked else") > export_ui.find("prepareDataFilePanelPresentation()"):
    fail("export UI must check Pro before opening file panel")
import_ui = function_body(preferences, "private func importTags()")
require(import_ui, "guard proEntitlement.isUnlocked else", "import UI must prompt Pro before file panel")
require(import_ui, "presentSettingsProPrompt(for: .layoutImport)", "import UI prompt feature is missing")
if import_ui.find("guard proEntitlement.isUnlocked else") > import_ui.find("prepareDataFilePanelPresentation()"):
    fail("import UI must check Pro before opening file panel")
require(preferences, "ProStatusPill(text: tr(\"pro.card.badge\"), style: .locked, compact: true)", "locked data actions must show compact Pro pill")
require(preferences, "presentSettingsProPrompt(for: .premiumThemes)", "premium theme selection prompt is missing")
require(preferences, "proEntitlement.startThemePreview(for: theme)", "premium theme free preview is missing")
require(preferences, "proEntitlement.stopThemePreview()", "theme preview cleanup is missing")
require(content, "@ObservedObject private var proEntitlement = ProEntitlementCenter.shared", "ContentView does not observe Pro entitlement")
require(content, "ProEntitlementPolicy.effectiveTheme(for: appGridTheme)", "ContentView does not apply effective Pro theme")
require(content, "presentProPrompt(for: .unlimitedNotes", "note quota prompt is missing from AppGrid")
require(content, "tr(\"pro.sorting.previewToast\")", "free sorting preview toast is missing")
require(content, "if let preview = proEntitlement.themePreviewState", "theme preview acceptance path is missing")
require(content, "ProUpgradePromptView(", "AppGrid Pro upgrade prompt view is missing")
require(access_views, "struct ProStatusPill", "ProStatusPill view is missing")
require(access_views, "struct ProUpgradePromptView", "ProUpgradePromptView is missing")
require(access_views, "let onUnlock: () -> Void", "Pro upgrade prompt purchase callback is missing")
require(access_views, "let onRestore: () -> Void", "Pro upgrade prompt restore callback is missing")
require(content, "onUnlock: { proEntitlement.purchasePro() }", "AppGrid Pro prompt is not wired to purchase action")
require(content, "onRestore: { proEntitlement.restorePurchases() }", "AppGrid Pro prompt is not wired to restore action")
print("PASS Pro feature gate QA: StoreKit entitlement, legacy unlock, theme gating, import/export locks, note quota, sorting preview, and Pro UI prompts are wired")
PY
src/Scripts/pro_localization_qa.sh
New file
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
python3 - "$ROOT_DIR" <<'PY'
import json
import pathlib
import re
import sys
root = pathlib.Path(sys.argv[1])
l10n_swift = root / "Apptag" / "L10n.swift"
localization_dir = root / "Apptag" / "Localization"
english_path = localization_dir / "en.json"
EXPECTED_LANGUAGE_COUNT = 29
ALLOWED_ENGLISH_COPY_KEYS = {
    "pro.card.badge",
}
REQUIRED_PLACEHOLDERS = {
    "pro.notes.remaining": {"%limit%", "%remaining%"},
}
def fail(message: str) -> None:
    print(f"FAIL: {message}", file=sys.stderr)
    sys.exit(1)
def load_json(path: pathlib.Path) -> dict[str, str]:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception as error:
        fail(f"{path} is not valid JSON: {error}")
    if not isinstance(data, dict):
        fail(f"{path} must contain a JSON object")
    return data
def extract_language_codes() -> list[str]:
    text = l10n_swift.read_text(encoding="utf-8")
    match = re.search(
        r"static\s+let\s+supported\s*:\s*\[\(code:\s*String,\s*name:\s*String\)\]\s*=\s*\[(.*?)\n\s*\]",
        text,
        re.S,
    )
    if not match:
        fail("could not find L10n.supported language list")
    codes = re.findall(r'\("([^"]+)",\s*"[^"]+"\)', match.group(1))
    if len(codes) != EXPECTED_LANGUAGE_COUNT:
        fail(f"L10n.supported has {len(codes)} languages, expected {EXPECTED_LANGUAGE_COUNT}")
    if len(set(codes)) != len(codes):
        fail("L10n.supported contains duplicate language codes")
    return codes
language_codes = extract_language_codes()
english = load_json(english_path)
pro_keys = [key for key in english if key.startswith("pro.")]
if not pro_keys:
    fail("en.json has no pro.* keys")
for code in language_codes:
    path = localization_dir / f"{code}.json"
    if not path.is_file():
        fail(f"missing localization file: {path}")
    data = load_json(path)
    missing = [
        key for key in pro_keys
        if not isinstance(data.get(key), str) or not data.get(key, "").strip()
    ]
    if missing:
        fail(f"{code}.json is missing pro translations: {', '.join(missing)}")
    badge = data["pro.card.badge"]
    if data.get("pro.card.title.free") == badge:
        fail(f"{code}.json uses the badge text as the upgrade title for pro.card.title.free")
    dirty_keys = [key for key in pro_keys if "__" in data[key] or "###FILE" in data[key]]
    if dirty_keys:
        fail(f"{code}.json still contains placeholder artifacts: {', '.join(dirty_keys)}")
    placeholder_errors = []
    for key, placeholders in REQUIRED_PLACEHOLDERS.items():
        value = data.get(key, "")
        missing_placeholders = sorted(placeholder for placeholder in placeholders if placeholder not in value)
        if missing_placeholders:
            placeholder_errors.append(f"{key} missing {', '.join(missing_placeholders)}")
    if placeholder_errors:
        fail(f"{code}.json has broken Pro placeholders: {'; '.join(placeholder_errors)}")
    if code != "en":
        copied_english = [
            key for key in pro_keys
            if key not in ALLOWED_ENGLISH_COPY_KEYS and data.get(key) == english.get(key)
        ]
        if copied_english:
            fail(f"{code}.json copies English Pro copy: {', '.join(copied_english)}")
print(
    "PASS Pro localization QA: "
    f"{len(language_codes)} languages, {len(pro_keys)} keys, "
    "no placeholder artifacts or English fallback copies"
)
PY
src/Scripts/theme_settings_qa.sh
@@ -83,7 +83,14 @@
    raise SystemExit(f"AppGridTheme missing edit-mode contrast tokens: {missing_tokens}")
if "private var renderedAppGridTheme: AppGridTheme" not in content_source:
    raise SystemExit("ContentView is missing the runtime theme override")
if "editPhase == .none ? appGridTheme : .defaultLight" not in content_source:
if not re.search(
    r"private\s+var\s+renderedAppGridTheme\s*:\s*AppGridTheme\s*\{.*?"
    r"editPhase\s*==\s*\.none.*?"
    r"ProEntitlementPolicy\.effectiveTheme\(for:\s*appGridTheme\).*?"
    r":\s*\.defaultLight",
    content_source,
    re.S,
):
    raise SystemExit("edit mode must temporarily render with the default light AppGrid theme")
if "let theme = renderedAppGridTheme" not in content_source:
    raise SystemExit("AppGrid background does not use the runtime rendered theme")
src/Scripts/window_logic_qa.sh
@@ -16,6 +16,8 @@
SHOW_DOCK_ICON_VALUE=""
APP_LANGUAGE_WAS_SET=false
APP_LANGUAGE_VALUE=""
PRO_STATE_ENV_WAS_SET=false
PRO_STATE_ENV_VALUE=""
FULLSCREEN_QA_PID=""
CLICK_TOOL="${CLICK_TOOL:-$(command -v cliclick || true)}"
@@ -47,6 +49,14 @@
  fi
}
backup_qa_environment() {
  local value
  if value="$(launchctl getenv TAGLAUNCHER_QA_PRO_STATE 2>/dev/null)" && [[ -n "$value" ]]; then
    PRO_STATE_ENV_WAS_SET=true
    PRO_STATE_ENV_VALUE="$value"
  fi
}
restore_user_defaults() {
  if [[ "$SHOW_DOCK_ICON_WAS_SET" == true ]]; then
    if [[ "$SHOW_DOCK_ICON_VALUE" == "1" || "$SHOW_DOCK_ICON_VALUE" == "true" || "$SHOW_DOCK_ICON_VALUE" == "TRUE" ]]; then
@@ -75,6 +85,14 @@
    fi
  else
    rm -f "$LAUNCH_AGENT_PLIST"
  fi
}
restore_qa_environment() {
  if [[ "$PRO_STATE_ENV_WAS_SET" == true ]]; then
    launchctl setenv TAGLAUNCHER_QA_PRO_STATE "$PRO_STATE_ENV_VALUE" >/dev/null 2>&1 || true
  else
    launchctl unsetenv TAGLAUNCHER_QA_PRO_STATE >/dev/null 2>&1 || true
  fi
}
@@ -219,6 +237,7 @@
  fi
  kill_all_taglauncher_instances
  restore_user_defaults
  restore_qa_environment
  restore_launch_agent_plist
  if [[ "$RESTORE_LAUNCH_AGENT" == true && -f "$LAUNCH_AGENT_PLIST" ]]; then
    launchctl bootstrap "$USER_GUI_DOMAIN" "$LAUNCH_AGENT_PLIST" >/dev/null 2>&1 || true
@@ -227,6 +246,7 @@
trap cleanup EXIT
backup_user_defaults
backup_launch_agent_plist
backup_qa_environment
kill_all_taglauncher_instances() {
  osascript -e 'tell application "TagLauncher" to quit' >/dev/null 2>&1 || true
@@ -1113,6 +1133,20 @@
  return 1
}
assert_quick_search_hover_safe() {
  local output=""
  if output="$(wait_swift_assert quick-search 2>&1)"; then
    printf '%s\n' "$output"
    return 0
  fi
  if output="$(wait_swift_assert no-overlay 2>&1)"; then
    printf 'PASS quick search hover closed without resurrecting App Grid\n'
    return 0
  fi
  printf '%s\n' "$output" >&2
  return 1
}
open_quick_search_with_retry() {
  local output=""
  for _ in {1..6}; do
@@ -1224,7 +1258,10 @@
hover_quick_search_results() {
  local coords x y
  coords="$(quick_search_result_coords_with_retry)"
  if ! coords="$(quick_search_result_coords_with_retry 2>/dev/null)"; then
    log "INFO quick search closed before hover coordinates were available; checking it did not resurrect App Grid"
    return 0
  fi
  read -r x y <<<"$coords"
  for offset in 0 8 16 8 0; do
    move_xy "$x" "$((y + offset))"
@@ -1318,6 +1355,7 @@
log "==> Preparing QA defaults"
defaults write "$DEFAULTS_DOMAIN" showDockIcon -bool true
defaults write "$DEFAULTS_DOMAIN" appLanguage -string en
launchctl setenv TAGLAUNCHER_QA_PRO_STATE pro
reset_dock_for_qa
log "==> Starting clean app instance"
@@ -1375,7 +1413,7 @@
open_quick_search_with_retry
hover_quick_search_results
sleep 0.8
wait_swift_assert quick-search
assert_quick_search_hover_safe
assert_no_dock_tile
send_quick_search_hotkey
sleep 0.8