Release 8.1.0 free Pro unlock
41 files modified
5 files added
| | |
| | | # TagLauncher CODEGRAPH |
| | | |
| | | 最后更新:2026-06-25 |
| | | 最后更新:2026-06-26 |
| | | |
| | | 用途:本文件是 TagLauncher 的长期工程地图。修改用户可见行为、状态模型、构建/发布入口、核心源码或 QA 入口前,应先读本文件,再读指向的真实源码。若本文件与源码不一致,以源码为准,并在同一任务内更新本文件。 |
| | | |
| | |
| | | - `TagLauncherApp`:SwiftUI App 入口。 |
| | | - `AppDelegate`:菜单栏状态项、Dock 显示策略、热键注册、窗口打开、Quick Search 触发、App 生命周期。 |
| | | - 支持从标签导航双击进入设置页的 Tags tab。 |
| | | - 启动时初始化 `ProEntitlementCenter`,保证免费 / Pro / 老用户自动 Pro 状态进入全局同步快照。 |
| | | - `src/Apptag/OverlayWindowController.swift` |
| | | - `OverlayPanel` / `OverlayWindowController`:AppGrid 浮层窗口与显示/隐藏控制。 |
| | | - `src/Apptag/ContentView.swift` |
| | |
| | | - `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` |
| | |
| | | - 语言选择、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/` |
| | |
| | | - 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` |
| | |
| | | - 标签双击进入标签设置页回归。 |
| | | - `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` |
| | |
| | | - 不得擅自改变源码根目录边界:App 源码只在 `src/` 下维护。 |
| | | - 不得把主题、视觉偏好、窗口状态混入 `TagDatabase` 的分类/标签业务数据。 |
| | | - 主题变化只允许影响 AppGrid 视觉,不得改变标签数据、排序、备注、SmartStart/category scheme、Quick Search、拖拽语义或导入导出。 |
| | | - Pro 门禁不得写脏免费用户真实数据:主题预览不写 `appGridThemeID`,排序预览不写 `containerAppOrder`,第 6 个新备注不写 `appNotes` / `appNoteMetadata`,导入 / 导出拦截必须早于文件面板和文件读写。 |
| | | - 免费主题固定为默认、深蓝、黑色;其它主题只能预览或在 Pro 解锁后持久化。 |
| | | - `originalAppVersion` 早于免费 + Pro 首个版本的历史用户必须自动获得 Pro;`originalPurchaseDate` 只能作为版本不可判断时的兜底。 |
| | | - 默认主题必须保留原浅色毛玻璃 AppGrid。 |
| | | - 黑色主题背景必须为 100% 纯黑;深蓝和黑色使用深色玻璃;粉色、紫色、绿色、蓝色、炫彩使用浅色玻璃以保证明亮和可读。 |
| | | - 单个 AppGrid 容器保持统一半透明玻璃面板,不做容器内部渐变。 |
| | |
| | | - AppGrid 启动或重开不应因为新 `ContentView` 初始 `allApps.isEmpty` 立刻显示转圈;应先复用最近一次完整快照,并延迟显示 loading。 |
| | | - Quick Search 显示标题应优先使用解析后的 `AppInfo.displayName`,内部 bundle 名只作为搜索字段。 |
| | | - Apple 默认应用备注必须按当前语言显示,不允许 A 语言出现 B 语言备注。 |
| | | - SmartStart/default system tag 初始化标签必须按当前语言显示;升级用户启动后应重刷带 `systemCategoryID` 的系统标签,不能长期保留旧英文初始化标签。 |
| | | - 标签导航单击必须立即滚动;hover 必须保持受控自动滚动;长按排序必须保留;双击可以打开标签设置页但不得替代前三者。 |
| | | - 容器外空白拖放才可触发解除标签;容器内部空白不算解除标签。 |
| | | - 拖到解除标签区域的碎纸预览只能是视觉反馈,不能跳过放手后的原确认流程。 |
| | |
| | | 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 |
| | |
| | | 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) |
| | |
| | | |
| | | 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 } |
| | | |
| | |
| | | self == .defaultLight |
| | | } |
| | | |
| | | var isFreeTheme: Bool { |
| | | ProEntitlementConfig.freeThemes.contains(self) |
| | | } |
| | | |
| | | var requiresPro: Bool { |
| | | !isFreeTheme |
| | | } |
| | | |
| | | var isPremiumTheme: Bool { |
| | | requiresPro |
| | | } |
| | | |
| | | var usesVisualEffectBackdrop: Bool { |
| | | self != .black |
| | | } |
| | |
| | | func applicationDidFinishLaunching(_ notification: Notification) { |
| | | AppDefaults.register() |
| | | L10n.setup() |
| | | ProEntitlementCenter.shared.start() |
| | | migrateDefaultGroupName() |
| | | TagDatabase.seedDefaultTags() |
| | | _ = TagDatabase.relocalizeSystemTagsForCurrentLanguage() |
| | |
| | | |
| | | enum SettingsTabTarget { |
| | | static let userInfoKey = "tab" |
| | | static let theme = "theme" |
| | | static let tags = "tags" |
| | | static let data = "data" |
| | | } |
| | | |
| | | // MARK: - Edit Phase |
| | |
| | | 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 |
| | |
| | | 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] = [:] |
| | |
| | | || appGridInteraction.pendingUncategorizedDrop != nil |
| | | || appGridInteraction.pendingTagRemovalDrop != nil |
| | | || appGridInteraction.usageTipsCloseReminderVisible |
| | | || appGridInteraction.proPrompt != nil |
| | | } |
| | | private var appGridHighlightedGroupName: String? { |
| | | appGridInteraction.tagNavigationHoveredGroupName |
| | |
| | | } |
| | | |
| | | private var renderedAppGridTheme: AppGridTheme { |
| | | editPhase == .none ? appGridTheme : .defaultLight |
| | | editPhase == .none |
| | | ? ProEntitlementPolicy.effectiveTheme(for: appGridTheme) |
| | | : .defaultLight |
| | | } |
| | | |
| | | private var isSideLayout: Bool { |
| | |
| | | } |
| | | |
| | | 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 |
| | |
| | | |
| | | 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] |
| | | ) |
| | | } |
| | | } |
| | | |
| | |
| | | uncategorizedDropConfirmOverlay |
| | | tagRemovalDropConfirmOverlay |
| | | usageTipsCloseReminderOverlay |
| | | proAccessPromptOverlay |
| | | } |
| | | |
| | | @ViewBuilder |
| | |
| | | ) |
| | | .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) |
| | | } |
| | | } |
| | | |
| | |
| | | || appGridInteraction.pendingUncategorizedDrop != nil |
| | | || appGridInteraction.pendingTagRemovalDrop != nil |
| | | || appGridInteraction.usageTipsCloseReminderVisible |
| | | || appGridInteraction.proPrompt != nil |
| | | ] |
| | | ) |
| | | } |
| | |
| | | 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, |
| | |
| | | |
| | | 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) |
| | |
| | | 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 |
| | | } |
| | |
| | | |
| | | 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 } |
| | |
| | | |
| | | 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) |
| | | } |
| | |
| | | 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)) { |
| | |
| | | // 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) |
| | |
| | | |
| | | /// 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 |
| | |
| | | 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( |
| | |
| | | previous: previousStore, |
| | | reason: "edit-app-note" |
| | | ) |
| | | return decision |
| | | } |
| | | |
| | | static func moveApp(path: String, from sourceTag: String, to targetTag: String, color: Int, copy: Bool) { |
| | |
| | | <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> |
| | |
| | | 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" |
| | | |
| | |
| | | "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 هذا." |
| | | } |
| | |
| | | "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 هذا." |
| | | } |
| | |
| | | "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á." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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 ではすべてのテーマのロックが解除されています。" |
| | | } |
| | |
| | | "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에서 잠금 해제됩니다." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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 разблокированы все темы." |
| | | } |
| | |
| | | "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-у." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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 นี้" |
| | | } |
| | |
| | | "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ı." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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." |
| | | } |
| | |
| | | "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 上的全部主题都已解锁。" |
| | | } |
| | |
| | | "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 上的全部主題都已解鎖。" |
| | | } |
| | |
| | | @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] = [] |
| | |
| | | @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 |
| | |
| | | } |
| | | |
| | | private func exportTags() { |
| | | guard proEntitlement.isUnlocked else { |
| | | presentSettingsProPrompt(for: .layoutExport) |
| | | return |
| | | } |
| | | guard prepareDataFilePanelPresentation() else { return } |
| | | DispatchQueue.main.async { |
| | | TagDatabase.flushPendingCategorySchemeBackupBatch() |
| | |
| | | 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) |
| | |
| | | } |
| | | |
| | | private func importTags() { |
| | | guard proEntitlement.isUnlocked else { |
| | | presentSettingsProPrompt(for: .layoutImport) |
| | | return |
| | | } |
| | | guard prepareDataFilePanelPresentation() else { return } |
| | | DispatchQueue.main.async { |
| | | let panel = NSOpenPanel() |
| | |
| | | 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) |
| | |
| | | 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, |
| | |
| | | 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 { |
| | |
| | | .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) |
| | | } |
| | | } |
| | | } |
| | |
| | | Spacer(minLength: 36) |
| | | |
| | | VStack(alignment: .center, spacing: 24) { |
| | | dataProStatusCard |
| | | |
| | | dataSection(minHeight: 124) { |
| | | HStack(alignment: .center, spacing: 12) { |
| | | dataSectionTitle(tr("settings.initialLayout")) |
| | |
| | | |
| | | 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) |
| | | } |
| | |
| | | .onReceive(NotificationCenter.default.publisher(for: .tagLauncherDataDidChange)) { _ in |
| | | refreshDataState() |
| | | } |
| | | .onChange(of: proEntitlement.accessState) { _, newState in |
| | | handleProAccessStateChange(newState) |
| | | } |
| | | |
| | | if isRefreshingLanguage { |
| | | ZStack { |
| | |
| | | .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() |
| | |
| | | .frame(maxWidth: 620) |
| | | .padding(.bottom, 22) |
| | | } |
| | | .zIndex(4) |
| | | .zIndex(5) |
| | | .transition(.opacity.combined(with: .move(edge: .bottom))) |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | 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 { |
| | |
| | | |
| | | 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) |
| | |
| | | ? 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 { |
| New file |
| | |
| | | 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) |
| | | } |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| | |
| | | |
| | | ## [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 默认分类、初次安装默认标签和标签导航显示 |
| New file |
| | |
| | | # 免费版 + 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 功能扩展 |
| New file |
| | |
| | | #!/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 |
| New file |
| | |
| | | #!/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 |
| | |
| | | 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") |
| | |
| | | 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)}" |
| | | |
| | |
| | | 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 |
| | |
| | | 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 |
| | | } |
| | | |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | |
| | | 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))" |
| | |
| | | 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" |
| | |
| | | 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 |