From 2c36a4a39da126c30a5a5daedf501229412b2e1a Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 26 Jun 2026 17:42:34 +0800
Subject: [PATCH] Release 8.1.0 free Pro unlock
---
src/Apptag/Localization/fr.json | 47
src/Apptag/Localization/ru.json | 47
src/Apptag/AppGridSupport.swift | 14
src/Apptag/Localization/ar.json | 47
src/Apptag/Localization/ro.json | 47
src/Apptag/Localization/en.json | 47
src/Apptag/Localization/sv.json | 47
src/Apptag/Localization/zh-Hant.json | 47
src/Apptag/ApptagApp.swift | 1
src/Apptag/Localization/pl.json | 47
src/Apptag/Localization/es.json | 47
src/Apptag/Localization/th.json | 47
src/Apptag/Localization/nn.json | 47
src/Apptag/ProEntitlement.swift | 639 +++++++++++
src/CHANGELOG.md | 9
src/Apptag/Info.plist | 4
src/Apptag/Localization/cs.json | 47
src/Apptag/Localization/ms.json | 47
src/Apptag/DataLayer.swift | 20
src/Apptag/Localization/it.json | 47
src/Apptag/Localization/tr.json | 47
src/Scripts/pro_feature_gate_qa.sh | 147 ++
src/Docs/Requirements/2026-06-26-free-pro-implementation-todo.md | 155 ++
src/Apptag/Localization/da.json | 47
src/Apptag/Localization/id.json | 47
src/Scripts/window_logic_qa.sh | 42
src/Apptag/Localization/ja.json | 47
src/Apptag/PreferencesView.swift | 302 +++++
src/Apptag/Localization/de.json | 47
src/Scripts/theme_settings_qa.sh | 9
src/Apptag/L10n.swift | 8
src/Apptag/AppGridTheme.swift | 19
src/Apptag/Localization/pt-BR.json | 47
CODEGRAPH.md | 22
src/Apptag/ProAccessViews.swift | 207 +++
src/Apptag/Localization/ar-Najdi.json | 47
src/Apptag/Localization/nb.json | 47
src/Scripts/pro_localization_qa.sh | 106 +
src/Apptag/Localization/ko.json | 47
src/Apptag/Localization/no.json | 47
src/Apptag/ContentView.swift | 379 ++++-
src/Apptag/Localization/sr-Cyrl.json | 47
src/Apptag/Localization/zh-Hans.json | 47
src/Apptag/Localization/nl.json | 47
src/Apptag/Localization/uk.json | 47
src/Apptag/Localization/vi.json | 47
46 files changed, 3,294 insertions(+), 152 deletions(-)
diff --git a/CODEGRAPH.md b/CODEGRAPH.md
index 1c42ac7..df98068 100644
--- a/CODEGRAPH.md
+++ b/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 必须保持受控自动滚动;长按排序必须保留;双击可以打开标签设置页但不得替代前三者。
- 容器外空白拖放才可触发解除标签;容器内部空白不算解除标签。
- 拖到解除标签区域的碎纸预览只能是视觉反馈,不能跳过放手后的原确认流程。
diff --git a/src/Apptag/AppGridSupport.swift b/src/Apptag/AppGridSupport.swift
index b12256d..8ff238e 100644
--- a/src/Apptag/AppGridSupport.swift
+++ b/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)
diff --git a/src/Apptag/AppGridTheme.swift b/src/Apptag/AppGridTheme.swift
index 03aafcf..f345a5c 100644
--- a/src/Apptag/AppGridTheme.swift
+++ b/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
}
diff --git a/src/Apptag/ApptagApp.swift b/src/Apptag/ApptagApp.swift
index 5367416..c5b38a3 100644
--- a/src/Apptag/ApptagApp.swift
+++ b/src/Apptag/ApptagApp.swift
@@ -193,6 +193,7 @@
func applicationDidFinishLaunching(_ notification: Notification) {
AppDefaults.register()
L10n.setup()
+ ProEntitlementCenter.shared.start()
migrateDefaultGroupName()
TagDatabase.seedDefaultTags()
_ = TagDatabase.relocalizeSystemTagsForCurrentLanguage()
diff --git a/src/Apptag/ContentView.swift b/src/Apptag/ContentView.swift
index 7ab2869..80fb99e 100644
--- a/src/Apptag/ContentView.swift
+++ b/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)) {
diff --git a/src/Apptag/DataLayer.swift b/src/Apptag/DataLayer.swift
index d05beda..6177fd6 100644
--- a/src/Apptag/DataLayer.swift
+++ b/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) {
diff --git a/src/Apptag/Info.plist b/src/Apptag/Info.plist
index e5cc555..903dc0f 100644
--- a/src/Apptag/Info.plist
+++ b/src/Apptag/Info.plist
@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>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>
diff --git a/src/Apptag/L10n.swift b/src/Apptag/L10n.swift
index 0d1f1b3..bd35a7b 100644
--- a/src/Apptag/L10n.swift
+++ b/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"
diff --git a/src/Apptag/Localization/ar-Najdi.json b/src/Apptag/Localization/ar-Najdi.json
index 222be31..2e559b2 100644
--- a/src/Apptag/Localization/ar-Najdi.json
+++ b/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 هذا."
}
diff --git a/src/Apptag/Localization/ar.json b/src/Apptag/Localization/ar.json
index 6866bc3..7f0410f 100644
--- a/src/Apptag/Localization/ar.json
+++ b/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 هذا."
}
diff --git a/src/Apptag/Localization/cs.json b/src/Apptag/Localization/cs.json
index 66d0b98..01e49be 100644
--- a/src/Apptag/Localization/cs.json
+++ b/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á."
}
diff --git a/src/Apptag/Localization/da.json b/src/Apptag/Localization/da.json
index 18d4086..68d9249 100644
--- a/src/Apptag/Localization/da.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/de.json b/src/Apptag/Localization/de.json
index a36a22e..1bf72f8 100644
--- a/src/Apptag/Localization/de.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/en.json b/src/Apptag/Localization/en.json
index 285c321..e546bdf 100644
--- a/src/Apptag/Localization/en.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/es.json b/src/Apptag/Localization/es.json
index 10d100f..ae0f75f 100644
--- a/src/Apptag/Localization/es.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/fr.json b/src/Apptag/Localization/fr.json
index dd71457..7c74c73 100644
--- a/src/Apptag/Localization/fr.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/id.json b/src/Apptag/Localization/id.json
index f51e450..d8a8208 100644
--- a/src/Apptag/Localization/id.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/it.json b/src/Apptag/Localization/it.json
index 810e4be..30583c6 100644
--- a/src/Apptag/Localization/it.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/ja.json b/src/Apptag/Localization/ja.json
index 316a2de..258d99d 100644
--- a/src/Apptag/Localization/ja.json
+++ b/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 ではすべてのテーマのロックが解除されています。"
}
diff --git a/src/Apptag/Localization/ko.json b/src/Apptag/Localization/ko.json
index f3f1c0b..f4dd987 100644
--- a/src/Apptag/Localization/ko.json
+++ b/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에서 잠금 해제됩니다."
}
diff --git a/src/Apptag/Localization/ms.json b/src/Apptag/Localization/ms.json
index bccc4eb..c15a6d2 100644
--- a/src/Apptag/Localization/ms.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/nb.json b/src/Apptag/Localization/nb.json
index e16a869..69b4ca6 100644
--- a/src/Apptag/Localization/nb.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/nl.json b/src/Apptag/Localization/nl.json
index c653b6a..07c4b32 100644
--- a/src/Apptag/Localization/nl.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/nn.json b/src/Apptag/Localization/nn.json
index 7697d66..316807d 100644
--- a/src/Apptag/Localization/nn.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/no.json b/src/Apptag/Localization/no.json
index e16a869..69b4ca6 100644
--- a/src/Apptag/Localization/no.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/pl.json b/src/Apptag/Localization/pl.json
index 1cbcb52..0c7bb7d 100644
--- a/src/Apptag/Localization/pl.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/pt-BR.json b/src/Apptag/Localization/pt-BR.json
index 404963a..d1b05eb 100644
--- a/src/Apptag/Localization/pt-BR.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/ro.json b/src/Apptag/Localization/ro.json
index c3c07a2..c0f8c8e 100644
--- a/src/Apptag/Localization/ro.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/ru.json b/src/Apptag/Localization/ru.json
index cbb4583..ad88403 100644
--- a/src/Apptag/Localization/ru.json
+++ b/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 разблокированы все темы."
}
diff --git a/src/Apptag/Localization/sr-Cyrl.json b/src/Apptag/Localization/sr-Cyrl.json
index 6a8a033..7fa91bd 100644
--- a/src/Apptag/Localization/sr-Cyrl.json
+++ b/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-у."
}
diff --git a/src/Apptag/Localization/sv.json b/src/Apptag/Localization/sv.json
index bfd3595..82af41b 100644
--- a/src/Apptag/Localization/sv.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/th.json b/src/Apptag/Localization/th.json
index 92bdb07..cb42deb 100644
--- a/src/Apptag/Localization/th.json
+++ b/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 นี้"
}
diff --git a/src/Apptag/Localization/tr.json b/src/Apptag/Localization/tr.json
index 329c5ca..49663a6 100644
--- a/src/Apptag/Localization/tr.json
+++ b/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ı."
}
diff --git a/src/Apptag/Localization/uk.json b/src/Apptag/Localization/uk.json
index 64f715d..d2ac906 100644
--- a/src/Apptag/Localization/uk.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/vi.json b/src/Apptag/Localization/vi.json
index 15ed915..bb046b0 100644
--- a/src/Apptag/Localization/vi.json
+++ b/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."
}
diff --git a/src/Apptag/Localization/zh-Hans.json b/src/Apptag/Localization/zh-Hans.json
index 953f989..679c46e 100644
--- a/src/Apptag/Localization/zh-Hans.json
+++ b/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 上的全部主题都已解锁。"
}
diff --git a/src/Apptag/Localization/zh-Hant.json b/src/Apptag/Localization/zh-Hant.json
index a0457db..c205797 100644
--- a/src/Apptag/Localization/zh-Hant.json
+++ b/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 上的全部主題都已解鎖。"
}
diff --git a/src/Apptag/PreferencesView.swift b/src/Apptag/PreferencesView.swift
index f14552a..0ac3ceb 100644
--- a/src/Apptag/PreferencesView.swift
+++ b/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 {
diff --git a/src/Apptag/ProAccessViews.swift b/src/Apptag/ProAccessViews.swift
new file mode 100644
index 0000000..1d4bcd6
--- /dev/null
+++ b/src/Apptag/ProAccessViews.swift
@@ -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)
+ }
+}
diff --git a/src/Apptag/ProEntitlement.swift b/src/Apptag/ProEntitlement.swift
new file mode 100644
index 0000000..e1c8c5e
--- /dev/null
+++ b/src/Apptag/ProEntitlement.swift
@@ -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
+ }
+}
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 06bd072..5ec896c 100644
--- a/src/CHANGELOG.md
+++ b/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 默认分类、初次安装默认标签和标签导航显示
diff --git a/src/Docs/Requirements/2026-06-26-free-pro-implementation-todo.md b/src/Docs/Requirements/2026-06-26-free-pro-implementation-todo.md
new file mode 100644
index 0000000..3e49206
--- /dev/null
+++ b/src/Docs/Requirements/2026-06-26-free-pro-implementation-todo.md
@@ -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 功能扩展
diff --git a/src/Scripts/pro_feature_gate_qa.sh b/src/Scripts/pro_feature_gate_qa.sh
new file mode 100755
index 0000000..ce07fc2
--- /dev/null
+++ b/src/Scripts/pro_feature_gate_qa.sh
@@ -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
diff --git a/src/Scripts/pro_localization_qa.sh b/src/Scripts/pro_localization_qa.sh
new file mode 100755
index 0000000..1555425
--- /dev/null
+++ b/src/Scripts/pro_localization_qa.sh
@@ -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
diff --git a/src/Scripts/theme_settings_qa.sh b/src/Scripts/theme_settings_qa.sh
index 868816d..bbc5896 100755
--- a/src/Scripts/theme_settings_qa.sh
+++ b/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")
diff --git a/src/Scripts/window_logic_qa.sh b/src/Scripts/window_logic_qa.sh
index 6c06ecb..81962ab 100755
--- a/src/Scripts/window_logic_qa.sh
+++ b/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
--
Gitblit v1.9.3